From 3cbb687d80025febf184f9d31b75b15ac0701d13 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 31 Jan 2022 11:49:10 +0000 Subject: [PATCH 001/319] docs: move whatsnew (#4540) * docs: move whatsnew * rebase changes * rebase latest changes --- .../documenting/whats_new_contributions.rst | 21 +- docs/src/developers_guide/release.rst | 23 +- docs/src/index.rst | 10 +- docs/src/whatsnew/dev.rst | 382 +++++++++++++++++ .../{latest.rst.template => dev.rst.template} | 0 docs/src/whatsnew/index.rst | 2 +- docs/src/whatsnew/latest.rst | 383 +----------------- 7 files changed, 415 insertions(+), 406 deletions(-) create mode 100644 docs/src/whatsnew/dev.rst rename docs/src/whatsnew/{latest.rst.template => dev.rst.template} (100%) mode change 100644 => 120000 docs/src/whatsnew/latest.rst diff --git a/docs/src/developers_guide/documenting/whats_new_contributions.rst b/docs/src/developers_guide/documenting/whats_new_contributions.rst index ebb553024b..576fc5f6a6 100644 --- a/docs/src/developers_guide/documenting/whats_new_contributions.rst +++ b/docs/src/developers_guide/documenting/whats_new_contributions.rst @@ -4,16 +4,21 @@ Contributing a "What's New" Entry ================================= -Iris uses a file named ``latest.rst`` to keep a draft of upcoming changes -that will form the next release. Contributions to the :ref:`iris_whatsnew` +Iris uses a file named ``dev.rst`` to keep a draft of upcoming development changes +that will form the next stable release. Contributions to the :ref:`iris_whatsnew` document are written by the developer most familiar with the change made. The contribution should be included as part of the Iris Pull Request that introduces the change. -The ``latest.rst`` and the past release notes are kept in -``docs/src/whatsnew/``. If you are writing the first contribution after -an Iris release: **create the new** ``latest.rst`` by copying the content from -``latest.rst.template`` in the same directory. +The ``dev.rst`` and the past release notes are kept in the +``docs/src/whatsnew/`` directory. If you are writing the first contribution after +an Iris release: **create the new** ``dev.rst`` by copying the content from +``dev.rst.template`` in the same directory. + +.. note:: + + Ensure that the symbolic link ``latest.rst`` references the ``dev.rst`` file + within the ``docs/src/whatsnew`` directory. Since the `Contribution categories`_ include Internal changes, **all** Iris Pull Requests should be accompanied by a "What's New" contribution. @@ -22,7 +27,7 @@ Pull Requests should be accompanied by a "What's New" contribution. Git Conflicts ============= -If changes to ``latest.rst`` are being suggested in several simultaneous +If changes to ``dev.rst`` are being suggested in several simultaneous Iris Pull Requests, Git will likely encounter merge conflicts. If this situation is thought likely (large PR, high repo activity etc.): @@ -43,7 +48,7 @@ situation is thought likely (large PR, high repo activity etc.): * PR reviewer: review the "What's New" PR, merge once acceptable -These measures should mean the suggested ``latest.rst`` changes are outstanding +These measures should mean the suggested ``dev.rst`` changes are outstanding for the minimum time, minimising conflicts and minimising the need to rebase or merge from trunk. diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index 09b884302b..f4d44781fc 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -183,9 +183,9 @@ back onto the ``SciTools/iris`` ``main`` branch. To achieve this, first cut a local branch from the latest ``main`` branch, and `git merge` the :literal:`.x` release branch into it. Ensure that the -``iris.__version__``, ``docs/src/whatsnew/index.rst`` and ``docs/src/whatsnew/latest.rst`` -are correct, before committing these changes and then proposing a pull-request -on the ``main`` branch of ``SciTools/iris``. +``iris.__version__``, ``docs/src/whatsnew/index.rst``, ``docs/src/whatsnew/dev.rst``, +and ``docs/src/whatsnew/latest.rst`` are correct, before committing these changes +and then proposing a pull-request on the ``main`` branch of ``SciTools/iris``. Point Releases @@ -218,9 +218,11 @@ Release Steps #. Update the ``iris.__init__.py`` version string e.g., to ``1.9.0`` #. Update the ``whatsnew`` for the release: - * Use ``git`` to rename ``docs/src/whatsnew/latest.rst`` to the release + * Use ``git`` to rename ``docs/src/whatsnew/dev.rst`` to the release version file ``v1.9.rst`` - * Use ``git`` to delete the ``docs/src/whatsnew/latest.rst.template`` file + * Update the symbolic link ``latest.rst`` to reference the latest + whatsnew ``v1.9.rst`` + * Use ``git`` to delete the ``docs/src/whatsnew/dev.rst.template`` file * In ``v1.9.rst`` remove the ``[unreleased]`` caption from the page title. Note that, the Iris version and release date are updated automatically when the documentation is built @@ -229,11 +231,11 @@ Release Steps dropdown at the top of the file, which provides extra detail on notable changes * Use ``git`` to add and commit all changes, including removal of - ``latest.rst.template`` + ``dev.rst.template`` and update to the ``latest.rst`` symbolic link. #. Update the ``whatsnew`` index ``docs/src/whatsnew/index.rst`` - * Remove the reference to ``latest.rst`` + * Remove the reference to ``dev.rst`` * Add a reference to ``v1.9.rst`` to the top of the list #. Check your changes by building the documentation and reviewing @@ -254,13 +256,6 @@ Post Release Steps `Read The Docs`_ to ensure that the appropriate versions are ``Active`` and/or ``Hidden``. To do this ``Edit`` the appropriate version e.g., see `Editing v3.0.0rc0`_ (must be logged into Read the Docs). -#. Copy ``docs/src/whatsnew/latest.rst.template`` to - ``docs/src/whatsnew/latest.rst``. This will reset - the file with the ``unreleased`` heading and placeholders for the - ``whatsnew`` headings -#. Add back in the reference to ``latest.rst`` to the ``whatsnew`` index - ``docs/src/whatsnew/index.rst`` -#. Update ``iris.__init__.py`` version string to show as ``1.10.dev0`` #. Merge back to ``main`` diff --git a/docs/src/index.rst b/docs/src/index.rst index d6fc5f2f7e..e6a787a220 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -98,6 +98,15 @@ For **Iris 2.4** and earlier documentation please see the generated/gallery/index +.. toctree:: + :maxdepth: 1 + :caption: What's New in Iris + :hidden: + + whatsnew/latest + Archive + + .. toctree:: :maxdepth: 1 :caption: User Guide @@ -154,6 +163,5 @@ For **Iris 2.4** and earlier documentation please see the :hidden: generated/api/iris - whatsnew/index techpapers/index copyright diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst new file mode 100644 index 0000000000..e2d4c2bc0b --- /dev/null +++ b/docs/src/whatsnew/dev.rst @@ -0,0 +1,382 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`report` |iris_version| Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this minor release of Iris include: + + * We've added experimental support for + :ref:`Meshes `, which can now be loaded and + attached to a cube. Mesh support is based on the based on `CF-UGRID`_ + model. + * We've also dropped support for ``Python 3.7``. + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +📢 Announcements +================ + +#. Welcome to `@wjbenfold`_, `@tinyendian`_, `@larsbarring`_, `@bsherratt`_ and + `@aaronspring`_ who made their first contributions to Iris. The first of + many we hope! +#. Congratulations to `@wjbenfold`_ who has become a core developer for Iris! 🎉 + + +✨ Features +=========== + +#. `@bjlittle`_, `@pp-mo`_, `@trexfeathers`_ and `@stephenworsley`_ added + support for :ref:`unstructured meshes `. This involved + adding a data model (:pull:`3968`, :pull:`4014`, :pull:`4027`, :pull:`4036`, + :pull:`4053`, :pull:`4439`) and API (:pull:`4063`, :pull:`4064`), and + supporting representation (:pull:`4033`, :pull:`4054`) of data on meshes. + Most of this new API can be found in :mod:`iris.experimental.ugrid`. The key + objects introduced are :class:`iris.experimental.ugrid.mesh.Mesh`, + :class:`iris.experimental.ugrid.mesh.MeshCoord` and + :obj:`iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD`. + A :class:`~iris.experimental.ugrid.mesh.Mesh` contains a full description of a UGRID + type mesh. :class:`~iris.experimental.ugrid.mesh.MeshCoord`\ s are coordinates that + reference and represent a :class:`~iris.experimental.ugrid.mesh.Mesh` for use + on a :class:`~iris.cube.Cube`. :class:`~iris.cube.Cube`\ s are also given the + property :attr:`~iris.cube.Cube.mesh` which returns a + :class:`~iris.experimental.ugrid.mesh.Mesh` if one is attached to the + :class:`~iris.cube.Cube` via a :class:`~iris.experimental.ugrid.mesh.MeshCoord`. + +#. `@trexfeathers`_ added support for loading unstructured mesh data from netcdf data, + for files using the `CF-UGRID`_ conventions. + The context manager :obj:`~iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD` + provides a way to load UGRID files so that :class:`~iris.cube.Cube`\ s can be + returned with a :class:`~iris.experimental.ugrid.mesh.Mesh` attached. + (:pull:`4058`). + +#. `@pp-mo`_ added support to save cubes with :ref:`meshes ` to netcdf + files, using the `CF-UGRID`_ conventions. + The existing :meth:`iris.save` function now does this, when saving cubes with meshes. + A routine :meth:`iris.experimental.ugrid.save.save_mesh` allows saving + :class:`~iris.experimental.ugrid.mesh.Mesh` objects to netcdf *without* any associated data + (i.e. not attached to cubes). + (:pull:`4318` and :pull:`4339`). + +#. `@trexfeathers`_ added :meth:`iris.experimental.ugrid.mesh.Mesh.from_coords` + for inferring a :class:`~iris.experimental.ugrid.mesh.Mesh` from an + appropriate collection of :class:`iris.coords.Coord`\ s. + +#. `@larsbarring`_ updated :func:`~iris.util.equalise_attributes` to return a list of dictionaries + containing the attributes removed from each :class:`~iris.cube.Cube`. (:pull:`4357`) + +#. `@trexfeathers`_ enabled streaming of **all** lazy arrays when saving to + NetCDF files (was previously just :class:`~iris.cube.Cube` + :attr:`~iris.cube.Cube.data`). This is + important given the much greater size of + :class:`~iris.coords.AuxCoord` :attr:`~iris.coords.AuxCoord.points` and + :class:`~iris.experimental.ugrid.mesh.Connectivity` + :attr:`~iris.experimental.ugrid.mesh.Connectivity.indices` under the + :ref:`mesh model `. (:pull:`4375`) + +#. `@bsherratt`_ added a ``threshold`` parameter to + :meth:`~iris.cube.Cube.intersection` (:pull:`4363`) + +#. `@wjbenfold`_ added test data to ci benchmarks so that it is accessible to + benchmark scripts. Also added a regridding benchmark that uses this data + (:pull:`4402`) + +#. `@pp-mo`_ updated to the latest CF Standard Names Table ``v78`` (21 Sept 2021). + (:issue:`4479`, :pull:`4483`) + +#. `@SimonPeatman`_ added support for filenames in the form of a :class:`~pathlib.PurePath` + in :func:`~iris.load`, :func:`~iris.load_cube`, :func:`~iris.load_cubes`, + :func:`~iris.load_raw` and :func:`~iris.save` (:issue:`3411`, :pull:`3917`). + Support for :class:`~pathlib.PurePath` is yet to be implemented across the rest + of Iris (:issue:`4523`). + +#. `@pp-mo`_ removed broken tooling for deriving Iris metadata translations + from `Metarelate`_. From now we intend to manage phenonemon translation + in Iris itself. (:pull:`4484`) + +#. `@pp-mo`_ improved printout of various cube data component objects : + :class:`~iris.coords.Coord`, :class:`~iris.coords.CellMeasure`, + :class:`~iris.coords.AncillaryVariable`, + :class:`~iris.experimental.ugrid.mesh.MeshCoord` and + :class:`~iris.experimental.ugrid.mesh.Mesh`. + These now all provide a more controllable ``summary()`` method, and + more convenient and readable ``str()`` and ``repr()`` output in the style of + the :class:`iris.cube.Cube`. + They also no longer realise lazy data. (:pull:`4499`). + + +🐛 Bugs Fixed +============= + +#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.intersection` for special cases where + one cell's bounds align with the requested maximum and negative minimum, fixing + :issue:`4221`. (:pull:`4278`) + +#. `@bsherratt`_ fixed further edge cases in + :meth:`~iris.cube.Cube.intersection`, including :issue:`3698` (:pull:`4363`) + +#. `@tinyendian`_ fixed the error message produced by :meth:`~iris.cube.CubeList.concatenate_cube` + when a cube list contains cubes with different names, which will no longer report + "Cube names differ: var1 != var1" if var1 appears multiple times in the list + (:issue:`4342`, :pull:`4345`) + +#. `@larsbarring`_ fixed :class:`~iris.coord_systems.GeoCS` to handle spherical ellipsoid + parameter inverse_flattening = 0 (:issue:`4146`, :pull:`4348`) + +#. `@pdearnshaw`_ fixed an error in the call to :class:`cftime.datetime` in + :mod:`~iris.fileformats.pp_save_rules` that prevented the saving to PP of climate + means for DJF (:pull:`4391`) + +#. `@wjbenfold`_ improved the error message for failure of :meth:`~iris.cube.CubeList.concatenate` + to indicate that the value of a scalar coordinate may be mismatched, rather than the metadata + (:issue:`4096`, :pull:`4387`) + +#. `@bsherratt`_ fixed a regression to the NAME file loader introduced in 3.0.4, + as well as some long-standing bugs with vertical coordinates and number + formats. (:pull:`4411`) + +#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.subset` to alway return ``None`` if + no value match is found. (:pull:`4417`) + +#. `@wjbenfold`_ changed :meth:`iris.util.points_step` to stop it from warning + when applied to a single point (:issue:`4250`, :pull:`4367`) + +#. `@trexfeathers`_ changed :class:`~iris.coords._DimensionalMetadata` and + :class:`~iris.experimental.ugrid.Connectivity` equality methods to preserve + array laziness, allowing efficient comparisons even with larger-than-memory + objects. (:pull:`4439`) + +#. `@rcomer`_ modified :meth:`~iris.cube.Cube.aggregated_by` to calculate new + coordinate bounds using minimum and maximum for unordered coordinates, + fixing :issue:`1528`. (:pull:`4315`) + +#. `@wjbenfold`_ changed how a delayed unit conversion is performed on a cube + so that a cube with lazy data awaiting a unit conversion can be pickled. + (:issue:`4354`, :pull:`4377`) + +#. `@pp-mo`_ fixed a bug in netcdf loading, whereby *any* rotated latlon coordinate + was mistakenly interpreted as a latitude, usually resulting in two 'latitude's + instead of one latitude and one longitude. + (:issue:`4460`, :pull:`4470`) + +#. `@wjbenfold`_ stopped :meth:`iris.coord_systems.GeogCS.as_cartopy_projection` + from assuming the globe to be the Earth (:issue:`4408`, :pull:`4497`) + +#. `@rcomer`_ corrected the ``long_name`` mapping from UM stash code ``m01s09i215`` + to indicate cloud fraction greater than 7.9 oktas, rather than 7.5 + (:issue:`3305`, :pull:`4535`) + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. `@wjbenfold`_ resolved an issue that previously caused regridding with lazy + data to take significantly longer than with real data. Benchmark + :class:`benchmarks.HorizontalChunkedRegridding` shows a time decrease + from >10s to 625ms. (:issue:`4280`, :pull:`4400`) + +#. `@bjlittle`_ included an optimisation to :class:`~iris.cube.Cube.coord_dims` + to avoid unnecessary processing whenever a coordinate instance that already + exists within the cube is provided. (:pull:`4549`) + + +🔥 Deprecations +=============== + +#. `@wjbenfold`_ removed :mod:`iris.experimental.equalise_cubes`. In ``v3.0`` + the experimental ``equalise_attributes`` functionality was moved to the + :mod:`iris.util.equalise_attributes` function. Since then, calling the + :func:`iris.experimental.equalise_cubes.equalise_attributes` function raised + an exception. (:issue:`3528`, :pull:`4496`) + +#. `@wjbenfold`_ deprecated :func:`iris.util.approx_equal` in preference for + :func:`math.isclose`. The :func:`~iris.util.approx_equal` function will be + removed in a future release of Iris. (:pull:`4514`) + +#. `@wjbenfold`_ deprecated :mod:`iris.experimental.raster` as it is not + believed to still be in use. The deprecation warnings invite users to contact + the Iris Developers if this isn't the case. (:pull:`4525`) + +#. `@wjbenfold`_ deprecated :mod:`iris.fileformats.abf` and + :mod:`iris.fileformats.dot` as they are not believed to still be in use. The + deprecation warnings invite users to contact the Iris Developers if this + isn't the case. (:pull:`4515`) + +#. `@wjbenfold`_ removed the :func:`iris.util.as_compatible_shape` function, + which was deprecated in ``v3.0``. Instead use + :class:`iris.common.resolve.Resolve`. For example, rather than calling + ``as_compatible_shape(src_cube, target_cube)`` replace with + ``Resolve(src_cube, target_cube)(target_cube.core_data())``. (:pull:`4513`) + +#. `@wjbenfold`_ deprecated :func:`iris.analysis.maths.intersection_of_cubes` in + preference for :meth:`iris.cube.CubeList.extract_overlapping`. The + :func:`~iris.analysis.maths.intersection_of_cubes` function will be removed in + a future release of Iris. (:pull:`4541`) + +#. `@pp-mo`_ deprecated :mod:`iris.experimental.regrid_conservative`. This is + now replaced by `iris-emsf-regrid`_. (:pull:`4551`) + +#. `@pp-mo`_ deprecated everything in :mod:`iris.experimental.regrid`. + Most features have a preferred exact alternative, as suggested, *except* + :class:`iris.experimental.regrid.ProjectedUnstructuredLinear` : that has no + identical equivalent, but :class:`iris.analysis.UnstructuredNearest` is + suggested as being quite close (though possibly slower). (:pull:`4548`) + + +🔗 Dependencies +=============== + +#. `@bjlittle`_ introduced the ``cartopy >=0.20`` minimum pin. + (:pull:`4331`) + +#. `@trexfeathers`_ introduced the ``cf-units >=3`` and ``nc-time-axis >=1.3`` + minimum pins. (:pull:`4356`) + +#. `@bjlittle`_ introduced the ``numpy >=1.19`` minimum pin, in + accordance with `NEP-29`_ deprecation policy. (:pull:`4386`) + +#. `@bjlittle`_ dropped support for ``Python 3.7``, as per the `NEP-29`_ + backwards compatibility and deprecation policy schedule. (:pull:`4481`) + + +📚 Documentation +================ + +#. `@rcomer`_ updated the "Plotting Wind Direction Using Quiver" Gallery + example. (:pull:`4120`) + +#. `@trexfeathers`_ included `Iris GitHub Discussions`_ in + :ref:`get involved `. (:pull:`4307`) + +#. `@wjbenfold`_ improved readability in :ref:`userguide interpolation + section `. (:pull:`4314`) + +#. `@wjbenfold`_ added explanation about the absence of | operator for + :class:`iris.Constraint` to :ref:`userguide loading section + ` and to api reference documentation. (:pull:`4321`) + +#. `@trexfeathers`_ added more detail on making `iris-test-data`_ available + during :ref:`developer_running_tests`. (:pull:`4359`) + +#. `@lbdreyer`_ added a section to the release documentation outlining the role + of the :ref:`release_manager`. (:pull:`4413`) + +#. `@trexfeathers`_ encouraged contributors to include type hinting in code + they are working on - :ref:`code_formatting`. (:pull:`4390`) + +#. `@wjbenfold`_ updated Cartopy documentation links to point to the renamed + :class:`cartopy.mpl.geoaxes.GeoAxes`. (:pull:`4464`) + +#. `@wjbenfold`_ clarified behaviour of :func:`iris.load` in :ref:`userguide + loading section `. (:pull:`4462`) + +#. `@bjlittle`_ migrated readthedocs to use mambaforge for `faster documentation building`_. + (:pull:`4476`) + +#. `@wjbenfold`_ contributed `@alastair-gemmell`_'s :ref:`step-by-step guide to + contributing to the docs ` to the docs. + (:pull:`4461`) + +#. `@pp-mo`_ improved and corrected docstrings of + :class:`iris.analysis.PointInCell`, making it clear what is the actual + calculation performed. (:pull:`4548`) + +#. `@pp-mo`_ removed reference in docstring of + :class:`iris.analysis.UnstructuredNearest` to the obsolete (deprecated) + :class:`iris.experimental.regrid.ProjectedUnstructuredNearest`. + (:pull:`4548`) + + +💼 Internal +=========== + +#. `@trexfeathers`_ set the linkcheck to ignore + http://www.nationalarchives.gov.uk/doc/open-government-licence since this + always works locally, but never within CI. (:pull:`4307`) + +#. `@wjbenfold`_ netCDF integration tests now skip ``TestConstrainedLoad`` if + test data is missing (:pull:`4319`) + +#. `@wjbenfold`_ excluded ``Good First Issue`` labelled issues from being + marked stale. (:pull:`4317`) + +#. `@tkknight`_ added additional make targets for reducing the time of the + documentation build including ``html-noapi`` and ``html-quick``. + Useful for development purposes only. For more information see + :ref:`contributing.documentation.building` the documentation. (:pull:`4333`) + +#. `@rcomer`_ modified the ``animation`` test to prevent it throwing a warning + that sometimes interferes with unrelated tests. (:pull:`4330`) + +#. `@rcomer`_ removed a now redundant workaround in :func:`~iris.plot.contourf`. + (:pull:`4349`) + +#. `@trexfeathers`_ refactored :mod:`iris.experimental.ugrid` into sub-modules. + (:pull:`4347`). + +#. `@bjlittle`_ enabled the `sort-all`_ `pre-commit`_ hook to automatically + sort ``__all__`` entries into alphabetical order. (:pull:`4353`) + +#. `@rcomer`_ modified a NetCDF saver test to prevent it triggering a numpy + deprecation warning. (:issue:`4374`, :pull:`4376`) + +#. `@akuhnregnier`_ removed addition of period from + :func:`~iris.analysis.cartography.wrap_lons` and updated affected tests + using ``assertArrayAllClose`` following :issue:`3993`. + (:pull:`4421`) + +#. `@rcomer`_ updated some tests to work with Matplotlib v3.5. (:pull:`4428`) + +#. `@rcomer`_ applied minor fixes to some regridding tests. (:pull:`4432`) + +#. `@lbdreyer`_ corrected the license PyPI classifier. (:pull:`4435`) + +#. `@aaronspring `_ exchanged ``dask`` with + ``dask-core`` in testing environments reducing the number of dependencies + installed for testing. (:pull:`4434`) + +#. `@wjbenfold`_ prevented github action runs in forks (:issue:`4441`, + :pull:`4444`) + +#. `@wjbenfold`_ fixed tests for hybrid formulae that weren't being found by + nose (:issue:`4431`, :pull:`4450`) + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + +.. _@aaronspring: https://github.com/aaronspring +.. _@akuhnregnier: https://github.com/akuhnregnier +.. _@bsherratt: https://github.com/bsherratt +.. _@larsbarring: https://github.com/larsbarring +.. _@pdearnshaw: https://github.com/pdearnshaw +.. _@SimonPeatman: https://github.com/SimonPeatman +.. _@tinyendian: https://github.com/tinyendian + +.. comment + Whatsnew resources in alphabetical order: + +.. _NEP-29: https://numpy.org/neps/nep-0029-deprecation_policy.html +.. _Metarelate: http://www.metarelate.net/ +.. _UGRID: http://ugrid-conventions.github.io/ugrid-conventions/ +.. _iris-emsf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid +.. _faster documentation building: https://docs.readthedocs.io/en/stable/guides/conda.html#making-builds-faster-with-mamba +.. _sort-all: https://github.com/aio-libs/sort-all diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/dev.rst.template similarity index 100% rename from docs/src/whatsnew/latest.rst.template rename to docs/src/whatsnew/dev.rst.template diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index fabb056484..51f03e8d8f 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -10,7 +10,7 @@ Iris versions. .. toctree:: :maxdepth: 1 - latest.rst + dev.rst 3.1.rst 3.0.rst 2.4.rst diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst deleted file mode 100644 index e2d4c2bc0b..0000000000 --- a/docs/src/whatsnew/latest.rst +++ /dev/null @@ -1,382 +0,0 @@ -.. include:: ../common_links.inc - -|iris_version| |build_date| [unreleased] -**************************************** - -This document explains the changes made to Iris for this release -(:doc:`View all changes `.) - - -.. dropdown:: :opticon:`report` |iris_version| Release Highlights - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - :open: - - The highlights for this minor release of Iris include: - - * We've added experimental support for - :ref:`Meshes `, which can now be loaded and - attached to a cube. Mesh support is based on the based on `CF-UGRID`_ - model. - * We've also dropped support for ``Python 3.7``. - - And finally, get in touch with us on :issue:`GitHub` if you have - any issues or feature requests for improving Iris. Enjoy! - - -📢 Announcements -================ - -#. Welcome to `@wjbenfold`_, `@tinyendian`_, `@larsbarring`_, `@bsherratt`_ and - `@aaronspring`_ who made their first contributions to Iris. The first of - many we hope! -#. Congratulations to `@wjbenfold`_ who has become a core developer for Iris! 🎉 - - -✨ Features -=========== - -#. `@bjlittle`_, `@pp-mo`_, `@trexfeathers`_ and `@stephenworsley`_ added - support for :ref:`unstructured meshes `. This involved - adding a data model (:pull:`3968`, :pull:`4014`, :pull:`4027`, :pull:`4036`, - :pull:`4053`, :pull:`4439`) and API (:pull:`4063`, :pull:`4064`), and - supporting representation (:pull:`4033`, :pull:`4054`) of data on meshes. - Most of this new API can be found in :mod:`iris.experimental.ugrid`. The key - objects introduced are :class:`iris.experimental.ugrid.mesh.Mesh`, - :class:`iris.experimental.ugrid.mesh.MeshCoord` and - :obj:`iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD`. - A :class:`~iris.experimental.ugrid.mesh.Mesh` contains a full description of a UGRID - type mesh. :class:`~iris.experimental.ugrid.mesh.MeshCoord`\ s are coordinates that - reference and represent a :class:`~iris.experimental.ugrid.mesh.Mesh` for use - on a :class:`~iris.cube.Cube`. :class:`~iris.cube.Cube`\ s are also given the - property :attr:`~iris.cube.Cube.mesh` which returns a - :class:`~iris.experimental.ugrid.mesh.Mesh` if one is attached to the - :class:`~iris.cube.Cube` via a :class:`~iris.experimental.ugrid.mesh.MeshCoord`. - -#. `@trexfeathers`_ added support for loading unstructured mesh data from netcdf data, - for files using the `CF-UGRID`_ conventions. - The context manager :obj:`~iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD` - provides a way to load UGRID files so that :class:`~iris.cube.Cube`\ s can be - returned with a :class:`~iris.experimental.ugrid.mesh.Mesh` attached. - (:pull:`4058`). - -#. `@pp-mo`_ added support to save cubes with :ref:`meshes ` to netcdf - files, using the `CF-UGRID`_ conventions. - The existing :meth:`iris.save` function now does this, when saving cubes with meshes. - A routine :meth:`iris.experimental.ugrid.save.save_mesh` allows saving - :class:`~iris.experimental.ugrid.mesh.Mesh` objects to netcdf *without* any associated data - (i.e. not attached to cubes). - (:pull:`4318` and :pull:`4339`). - -#. `@trexfeathers`_ added :meth:`iris.experimental.ugrid.mesh.Mesh.from_coords` - for inferring a :class:`~iris.experimental.ugrid.mesh.Mesh` from an - appropriate collection of :class:`iris.coords.Coord`\ s. - -#. `@larsbarring`_ updated :func:`~iris.util.equalise_attributes` to return a list of dictionaries - containing the attributes removed from each :class:`~iris.cube.Cube`. (:pull:`4357`) - -#. `@trexfeathers`_ enabled streaming of **all** lazy arrays when saving to - NetCDF files (was previously just :class:`~iris.cube.Cube` - :attr:`~iris.cube.Cube.data`). This is - important given the much greater size of - :class:`~iris.coords.AuxCoord` :attr:`~iris.coords.AuxCoord.points` and - :class:`~iris.experimental.ugrid.mesh.Connectivity` - :attr:`~iris.experimental.ugrid.mesh.Connectivity.indices` under the - :ref:`mesh model `. (:pull:`4375`) - -#. `@bsherratt`_ added a ``threshold`` parameter to - :meth:`~iris.cube.Cube.intersection` (:pull:`4363`) - -#. `@wjbenfold`_ added test data to ci benchmarks so that it is accessible to - benchmark scripts. Also added a regridding benchmark that uses this data - (:pull:`4402`) - -#. `@pp-mo`_ updated to the latest CF Standard Names Table ``v78`` (21 Sept 2021). - (:issue:`4479`, :pull:`4483`) - -#. `@SimonPeatman`_ added support for filenames in the form of a :class:`~pathlib.PurePath` - in :func:`~iris.load`, :func:`~iris.load_cube`, :func:`~iris.load_cubes`, - :func:`~iris.load_raw` and :func:`~iris.save` (:issue:`3411`, :pull:`3917`). - Support for :class:`~pathlib.PurePath` is yet to be implemented across the rest - of Iris (:issue:`4523`). - -#. `@pp-mo`_ removed broken tooling for deriving Iris metadata translations - from `Metarelate`_. From now we intend to manage phenonemon translation - in Iris itself. (:pull:`4484`) - -#. `@pp-mo`_ improved printout of various cube data component objects : - :class:`~iris.coords.Coord`, :class:`~iris.coords.CellMeasure`, - :class:`~iris.coords.AncillaryVariable`, - :class:`~iris.experimental.ugrid.mesh.MeshCoord` and - :class:`~iris.experimental.ugrid.mesh.Mesh`. - These now all provide a more controllable ``summary()`` method, and - more convenient and readable ``str()`` and ``repr()`` output in the style of - the :class:`iris.cube.Cube`. - They also no longer realise lazy data. (:pull:`4499`). - - -🐛 Bugs Fixed -============= - -#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.intersection` for special cases where - one cell's bounds align with the requested maximum and negative minimum, fixing - :issue:`4221`. (:pull:`4278`) - -#. `@bsherratt`_ fixed further edge cases in - :meth:`~iris.cube.Cube.intersection`, including :issue:`3698` (:pull:`4363`) - -#. `@tinyendian`_ fixed the error message produced by :meth:`~iris.cube.CubeList.concatenate_cube` - when a cube list contains cubes with different names, which will no longer report - "Cube names differ: var1 != var1" if var1 appears multiple times in the list - (:issue:`4342`, :pull:`4345`) - -#. `@larsbarring`_ fixed :class:`~iris.coord_systems.GeoCS` to handle spherical ellipsoid - parameter inverse_flattening = 0 (:issue:`4146`, :pull:`4348`) - -#. `@pdearnshaw`_ fixed an error in the call to :class:`cftime.datetime` in - :mod:`~iris.fileformats.pp_save_rules` that prevented the saving to PP of climate - means for DJF (:pull:`4391`) - -#. `@wjbenfold`_ improved the error message for failure of :meth:`~iris.cube.CubeList.concatenate` - to indicate that the value of a scalar coordinate may be mismatched, rather than the metadata - (:issue:`4096`, :pull:`4387`) - -#. `@bsherratt`_ fixed a regression to the NAME file loader introduced in 3.0.4, - as well as some long-standing bugs with vertical coordinates and number - formats. (:pull:`4411`) - -#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.subset` to alway return ``None`` if - no value match is found. (:pull:`4417`) - -#. `@wjbenfold`_ changed :meth:`iris.util.points_step` to stop it from warning - when applied to a single point (:issue:`4250`, :pull:`4367`) - -#. `@trexfeathers`_ changed :class:`~iris.coords._DimensionalMetadata` and - :class:`~iris.experimental.ugrid.Connectivity` equality methods to preserve - array laziness, allowing efficient comparisons even with larger-than-memory - objects. (:pull:`4439`) - -#. `@rcomer`_ modified :meth:`~iris.cube.Cube.aggregated_by` to calculate new - coordinate bounds using minimum and maximum for unordered coordinates, - fixing :issue:`1528`. (:pull:`4315`) - -#. `@wjbenfold`_ changed how a delayed unit conversion is performed on a cube - so that a cube with lazy data awaiting a unit conversion can be pickled. - (:issue:`4354`, :pull:`4377`) - -#. `@pp-mo`_ fixed a bug in netcdf loading, whereby *any* rotated latlon coordinate - was mistakenly interpreted as a latitude, usually resulting in two 'latitude's - instead of one latitude and one longitude. - (:issue:`4460`, :pull:`4470`) - -#. `@wjbenfold`_ stopped :meth:`iris.coord_systems.GeogCS.as_cartopy_projection` - from assuming the globe to be the Earth (:issue:`4408`, :pull:`4497`) - -#. `@rcomer`_ corrected the ``long_name`` mapping from UM stash code ``m01s09i215`` - to indicate cloud fraction greater than 7.9 oktas, rather than 7.5 - (:issue:`3305`, :pull:`4535`) - - -💣 Incompatible Changes -======================= - -#. N/A - - -🚀 Performance Enhancements -=========================== - -#. `@wjbenfold`_ resolved an issue that previously caused regridding with lazy - data to take significantly longer than with real data. Benchmark - :class:`benchmarks.HorizontalChunkedRegridding` shows a time decrease - from >10s to 625ms. (:issue:`4280`, :pull:`4400`) - -#. `@bjlittle`_ included an optimisation to :class:`~iris.cube.Cube.coord_dims` - to avoid unnecessary processing whenever a coordinate instance that already - exists within the cube is provided. (:pull:`4549`) - - -🔥 Deprecations -=============== - -#. `@wjbenfold`_ removed :mod:`iris.experimental.equalise_cubes`. In ``v3.0`` - the experimental ``equalise_attributes`` functionality was moved to the - :mod:`iris.util.equalise_attributes` function. Since then, calling the - :func:`iris.experimental.equalise_cubes.equalise_attributes` function raised - an exception. (:issue:`3528`, :pull:`4496`) - -#. `@wjbenfold`_ deprecated :func:`iris.util.approx_equal` in preference for - :func:`math.isclose`. The :func:`~iris.util.approx_equal` function will be - removed in a future release of Iris. (:pull:`4514`) - -#. `@wjbenfold`_ deprecated :mod:`iris.experimental.raster` as it is not - believed to still be in use. The deprecation warnings invite users to contact - the Iris Developers if this isn't the case. (:pull:`4525`) - -#. `@wjbenfold`_ deprecated :mod:`iris.fileformats.abf` and - :mod:`iris.fileformats.dot` as they are not believed to still be in use. The - deprecation warnings invite users to contact the Iris Developers if this - isn't the case. (:pull:`4515`) - -#. `@wjbenfold`_ removed the :func:`iris.util.as_compatible_shape` function, - which was deprecated in ``v3.0``. Instead use - :class:`iris.common.resolve.Resolve`. For example, rather than calling - ``as_compatible_shape(src_cube, target_cube)`` replace with - ``Resolve(src_cube, target_cube)(target_cube.core_data())``. (:pull:`4513`) - -#. `@wjbenfold`_ deprecated :func:`iris.analysis.maths.intersection_of_cubes` in - preference for :meth:`iris.cube.CubeList.extract_overlapping`. The - :func:`~iris.analysis.maths.intersection_of_cubes` function will be removed in - a future release of Iris. (:pull:`4541`) - -#. `@pp-mo`_ deprecated :mod:`iris.experimental.regrid_conservative`. This is - now replaced by `iris-emsf-regrid`_. (:pull:`4551`) - -#. `@pp-mo`_ deprecated everything in :mod:`iris.experimental.regrid`. - Most features have a preferred exact alternative, as suggested, *except* - :class:`iris.experimental.regrid.ProjectedUnstructuredLinear` : that has no - identical equivalent, but :class:`iris.analysis.UnstructuredNearest` is - suggested as being quite close (though possibly slower). (:pull:`4548`) - - -🔗 Dependencies -=============== - -#. `@bjlittle`_ introduced the ``cartopy >=0.20`` minimum pin. - (:pull:`4331`) - -#. `@trexfeathers`_ introduced the ``cf-units >=3`` and ``nc-time-axis >=1.3`` - minimum pins. (:pull:`4356`) - -#. `@bjlittle`_ introduced the ``numpy >=1.19`` minimum pin, in - accordance with `NEP-29`_ deprecation policy. (:pull:`4386`) - -#. `@bjlittle`_ dropped support for ``Python 3.7``, as per the `NEP-29`_ - backwards compatibility and deprecation policy schedule. (:pull:`4481`) - - -📚 Documentation -================ - -#. `@rcomer`_ updated the "Plotting Wind Direction Using Quiver" Gallery - example. (:pull:`4120`) - -#. `@trexfeathers`_ included `Iris GitHub Discussions`_ in - :ref:`get involved `. (:pull:`4307`) - -#. `@wjbenfold`_ improved readability in :ref:`userguide interpolation - section `. (:pull:`4314`) - -#. `@wjbenfold`_ added explanation about the absence of | operator for - :class:`iris.Constraint` to :ref:`userguide loading section - ` and to api reference documentation. (:pull:`4321`) - -#. `@trexfeathers`_ added more detail on making `iris-test-data`_ available - during :ref:`developer_running_tests`. (:pull:`4359`) - -#. `@lbdreyer`_ added a section to the release documentation outlining the role - of the :ref:`release_manager`. (:pull:`4413`) - -#. `@trexfeathers`_ encouraged contributors to include type hinting in code - they are working on - :ref:`code_formatting`. (:pull:`4390`) - -#. `@wjbenfold`_ updated Cartopy documentation links to point to the renamed - :class:`cartopy.mpl.geoaxes.GeoAxes`. (:pull:`4464`) - -#. `@wjbenfold`_ clarified behaviour of :func:`iris.load` in :ref:`userguide - loading section `. (:pull:`4462`) - -#. `@bjlittle`_ migrated readthedocs to use mambaforge for `faster documentation building`_. - (:pull:`4476`) - -#. `@wjbenfold`_ contributed `@alastair-gemmell`_'s :ref:`step-by-step guide to - contributing to the docs ` to the docs. - (:pull:`4461`) - -#. `@pp-mo`_ improved and corrected docstrings of - :class:`iris.analysis.PointInCell`, making it clear what is the actual - calculation performed. (:pull:`4548`) - -#. `@pp-mo`_ removed reference in docstring of - :class:`iris.analysis.UnstructuredNearest` to the obsolete (deprecated) - :class:`iris.experimental.regrid.ProjectedUnstructuredNearest`. - (:pull:`4548`) - - -💼 Internal -=========== - -#. `@trexfeathers`_ set the linkcheck to ignore - http://www.nationalarchives.gov.uk/doc/open-government-licence since this - always works locally, but never within CI. (:pull:`4307`) - -#. `@wjbenfold`_ netCDF integration tests now skip ``TestConstrainedLoad`` if - test data is missing (:pull:`4319`) - -#. `@wjbenfold`_ excluded ``Good First Issue`` labelled issues from being - marked stale. (:pull:`4317`) - -#. `@tkknight`_ added additional make targets for reducing the time of the - documentation build including ``html-noapi`` and ``html-quick``. - Useful for development purposes only. For more information see - :ref:`contributing.documentation.building` the documentation. (:pull:`4333`) - -#. `@rcomer`_ modified the ``animation`` test to prevent it throwing a warning - that sometimes interferes with unrelated tests. (:pull:`4330`) - -#. `@rcomer`_ removed a now redundant workaround in :func:`~iris.plot.contourf`. - (:pull:`4349`) - -#. `@trexfeathers`_ refactored :mod:`iris.experimental.ugrid` into sub-modules. - (:pull:`4347`). - -#. `@bjlittle`_ enabled the `sort-all`_ `pre-commit`_ hook to automatically - sort ``__all__`` entries into alphabetical order. (:pull:`4353`) - -#. `@rcomer`_ modified a NetCDF saver test to prevent it triggering a numpy - deprecation warning. (:issue:`4374`, :pull:`4376`) - -#. `@akuhnregnier`_ removed addition of period from - :func:`~iris.analysis.cartography.wrap_lons` and updated affected tests - using ``assertArrayAllClose`` following :issue:`3993`. - (:pull:`4421`) - -#. `@rcomer`_ updated some tests to work with Matplotlib v3.5. (:pull:`4428`) - -#. `@rcomer`_ applied minor fixes to some regridding tests. (:pull:`4432`) - -#. `@lbdreyer`_ corrected the license PyPI classifier. (:pull:`4435`) - -#. `@aaronspring `_ exchanged ``dask`` with - ``dask-core`` in testing environments reducing the number of dependencies - installed for testing. (:pull:`4434`) - -#. `@wjbenfold`_ prevented github action runs in forks (:issue:`4441`, - :pull:`4444`) - -#. `@wjbenfold`_ fixed tests for hybrid formulae that weren't being found by - nose (:issue:`4431`, :pull:`4450`) - -.. comment - Whatsnew author names (@github name) in alphabetical order. Note that, - core dev names are automatically included by the common_links.inc: - -.. _@aaronspring: https://github.com/aaronspring -.. _@akuhnregnier: https://github.com/akuhnregnier -.. _@bsherratt: https://github.com/bsherratt -.. _@larsbarring: https://github.com/larsbarring -.. _@pdearnshaw: https://github.com/pdearnshaw -.. _@SimonPeatman: https://github.com/SimonPeatman -.. _@tinyendian: https://github.com/tinyendian - -.. comment - Whatsnew resources in alphabetical order: - -.. _NEP-29: https://numpy.org/neps/nep-0029-deprecation_policy.html -.. _Metarelate: http://www.metarelate.net/ -.. _UGRID: http://ugrid-conventions.github.io/ugrid-conventions/ -.. _iris-emsf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid -.. _faster documentation building: https://docs.readthedocs.io/en/stable/guides/conda.html#making-builds-faster-with-mamba -.. _sort-all: https://github.com/aio-libs/sort-all diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst new file mode 120000 index 0000000000..56aebe92dd --- /dev/null +++ b/docs/src/whatsnew/latest.rst @@ -0,0 +1 @@ +dev.rst \ No newline at end of file From 9d598f199806ad2f16cd826b4108cbba5f687952 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Mon, 31 Jan 2022 17:23:53 +0000 Subject: [PATCH 002/319] 3.2 version and whats new (#4559) --- docs/src/whatsnew/{dev.rst => 3.2.rst} | 9 +- docs/src/whatsnew/dev.rst.template | 112 ------------------------- docs/src/whatsnew/index.rst | 2 +- docs/src/whatsnew/latest.rst | 2 +- lib/iris/__init__.py | 2 +- 5 files changed, 7 insertions(+), 120 deletions(-) rename docs/src/whatsnew/{dev.rst => 3.2.rst} (98%) delete mode 100644 docs/src/whatsnew/dev.rst.template diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/3.2.rst similarity index 98% rename from docs/src/whatsnew/dev.rst rename to docs/src/whatsnew/3.2.rst index e2d4c2bc0b..c78e1283d6 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/3.2.rst @@ -1,13 +1,13 @@ .. include:: ../common_links.inc -|iris_version| |build_date| [unreleased] -**************************************** +v3.2 (31 Jan 2022) [unreleased] +******************************* This document explains the changes made to Iris for this release (:doc:`View all changes `.) -.. dropdown:: :opticon:`report` |iris_version| Release Highlights +.. dropdown:: :opticon:`report` v3.2.0 Release Highlights :container: + shadow :title: text-primary text-center font-weight-bold :body: bg-light @@ -18,8 +18,7 @@ This document explains the changes made to Iris for this release * We've added experimental support for :ref:`Meshes `, which can now be loaded and - attached to a cube. Mesh support is based on the based on `CF-UGRID`_ - model. + attached to a cube. Mesh support is based on the `CF-UGRID`_ model. * We've also dropped support for ``Python 3.7``. And finally, get in touch with us on :issue:`GitHub` if you have diff --git a/docs/src/whatsnew/dev.rst.template b/docs/src/whatsnew/dev.rst.template deleted file mode 100644 index 79c578ca65..0000000000 --- a/docs/src/whatsnew/dev.rst.template +++ /dev/null @@ -1,112 +0,0 @@ -.. include:: ../common_links.inc - -|iris_version| |build_date| [unreleased] -**************************************** - -This document explains the changes made to Iris for this release -(:doc:`View all changes `.) - - -.. dropdown:: :opticon:`report` |iris_version| Release Highlights - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - :open: - - The highlights for this major/minor release of Iris include: - - * N/A - - And finally, get in touch with us on :issue:`GitHub` if you have - any issues or feature requests for improving Iris. Enjoy! - - -NOTE: section below is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'latest.rst') - -v3.X.X (DD MMM YYYY) -==================== - -.. dropdown:: :opticon:`alert` v3.X.X Patches - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - - The patches in this release of Iris include: - - #. N/A - -NOTE: section above is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'latest.rst') - - - -📢 Announcements -================ - -#. N/A - - -✨ Features -=========== - -#. N/A - - -🐛 Bugs Fixed -============= - -#. N/A - - -💣 Incompatible Changes -======================= - -#. N/A - - -🚀 Performance Enhancements -=========================== - -#. N/A - - -🔥 Deprecations -=============== - -#. N/A - - -🔗 Dependencies -=============== - -#. N/A - - -📚 Documentation -================ - -#. N/A - - -💼 Internal -=========== - -#. N/A - - -.. comment - Whatsnew author names (@github name) in alphabetical order. Note that, - core dev names are automatically included by the common_links.inc: - - - - -.. comment - Whatsnew resources in alphabetical order: - - diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index 51f03e8d8f..f425e649b9 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -10,7 +10,7 @@ Iris versions. .. toctree:: :maxdepth: 1 - dev.rst + 3.2.rst 3.1.rst 3.0.rst 2.4.rst diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 56aebe92dd..2bdbea5d85 120000 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -1 +1 @@ -dev.rst \ No newline at end of file +3.2.rst \ No newline at end of file diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 26f03c0566..aca4e77e88 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -104,7 +104,7 @@ def callback(cube, field, filename): # Iris revision. -__version__ = "3.2.dev0" +__version__ = "3.2.0rc0" # Restrict the names imported when using "from iris import *" __all__ = [ From 678f4b523ea41c1f77581b7910c6b3d11d598f72 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 4 Feb 2022 09:27:16 +0000 Subject: [PATCH 003/319] Reset whats new (#4563) * reset whats new * Specify minor in whats new --- docs/src/whatsnew/dev.rst | 317 ++--------------------------- docs/src/whatsnew/dev.rst.template | 4 +- 2 files changed, 14 insertions(+), 307 deletions(-) diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index e2d4c2bc0b..27ed876a20 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -16,11 +16,7 @@ This document explains the changes made to Iris for this release The highlights for this minor release of Iris include: - * We've added experimental support for - :ref:`Meshes `, which can now be loaded and - attached to a cube. Mesh support is based on the based on `CF-UGRID`_ - model. - * We've also dropped support for ``Python 3.7``. + * N/A And finally, get in touch with us on :issue:`GitHub` if you have any issues or feature requests for improving Iris. Enjoy! @@ -29,154 +25,19 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. Welcome to `@wjbenfold`_, `@tinyendian`_, `@larsbarring`_, `@bsherratt`_ and - `@aaronspring`_ who made their first contributions to Iris. The first of - many we hope! -#. Congratulations to `@wjbenfold`_ who has become a core developer for Iris! 🎉 +#. N/A ✨ Features =========== -#. `@bjlittle`_, `@pp-mo`_, `@trexfeathers`_ and `@stephenworsley`_ added - support for :ref:`unstructured meshes `. This involved - adding a data model (:pull:`3968`, :pull:`4014`, :pull:`4027`, :pull:`4036`, - :pull:`4053`, :pull:`4439`) and API (:pull:`4063`, :pull:`4064`), and - supporting representation (:pull:`4033`, :pull:`4054`) of data on meshes. - Most of this new API can be found in :mod:`iris.experimental.ugrid`. The key - objects introduced are :class:`iris.experimental.ugrid.mesh.Mesh`, - :class:`iris.experimental.ugrid.mesh.MeshCoord` and - :obj:`iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD`. - A :class:`~iris.experimental.ugrid.mesh.Mesh` contains a full description of a UGRID - type mesh. :class:`~iris.experimental.ugrid.mesh.MeshCoord`\ s are coordinates that - reference and represent a :class:`~iris.experimental.ugrid.mesh.Mesh` for use - on a :class:`~iris.cube.Cube`. :class:`~iris.cube.Cube`\ s are also given the - property :attr:`~iris.cube.Cube.mesh` which returns a - :class:`~iris.experimental.ugrid.mesh.Mesh` if one is attached to the - :class:`~iris.cube.Cube` via a :class:`~iris.experimental.ugrid.mesh.MeshCoord`. - -#. `@trexfeathers`_ added support for loading unstructured mesh data from netcdf data, - for files using the `CF-UGRID`_ conventions. - The context manager :obj:`~iris.experimental.ugrid.load.PARSE_UGRID_ON_LOAD` - provides a way to load UGRID files so that :class:`~iris.cube.Cube`\ s can be - returned with a :class:`~iris.experimental.ugrid.mesh.Mesh` attached. - (:pull:`4058`). - -#. `@pp-mo`_ added support to save cubes with :ref:`meshes ` to netcdf - files, using the `CF-UGRID`_ conventions. - The existing :meth:`iris.save` function now does this, when saving cubes with meshes. - A routine :meth:`iris.experimental.ugrid.save.save_mesh` allows saving - :class:`~iris.experimental.ugrid.mesh.Mesh` objects to netcdf *without* any associated data - (i.e. not attached to cubes). - (:pull:`4318` and :pull:`4339`). - -#. `@trexfeathers`_ added :meth:`iris.experimental.ugrid.mesh.Mesh.from_coords` - for inferring a :class:`~iris.experimental.ugrid.mesh.Mesh` from an - appropriate collection of :class:`iris.coords.Coord`\ s. - -#. `@larsbarring`_ updated :func:`~iris.util.equalise_attributes` to return a list of dictionaries - containing the attributes removed from each :class:`~iris.cube.Cube`. (:pull:`4357`) - -#. `@trexfeathers`_ enabled streaming of **all** lazy arrays when saving to - NetCDF files (was previously just :class:`~iris.cube.Cube` - :attr:`~iris.cube.Cube.data`). This is - important given the much greater size of - :class:`~iris.coords.AuxCoord` :attr:`~iris.coords.AuxCoord.points` and - :class:`~iris.experimental.ugrid.mesh.Connectivity` - :attr:`~iris.experimental.ugrid.mesh.Connectivity.indices` under the - :ref:`mesh model `. (:pull:`4375`) - -#. `@bsherratt`_ added a ``threshold`` parameter to - :meth:`~iris.cube.Cube.intersection` (:pull:`4363`) - -#. `@wjbenfold`_ added test data to ci benchmarks so that it is accessible to - benchmark scripts. Also added a regridding benchmark that uses this data - (:pull:`4402`) - -#. `@pp-mo`_ updated to the latest CF Standard Names Table ``v78`` (21 Sept 2021). - (:issue:`4479`, :pull:`4483`) - -#. `@SimonPeatman`_ added support for filenames in the form of a :class:`~pathlib.PurePath` - in :func:`~iris.load`, :func:`~iris.load_cube`, :func:`~iris.load_cubes`, - :func:`~iris.load_raw` and :func:`~iris.save` (:issue:`3411`, :pull:`3917`). - Support for :class:`~pathlib.PurePath` is yet to be implemented across the rest - of Iris (:issue:`4523`). - -#. `@pp-mo`_ removed broken tooling for deriving Iris metadata translations - from `Metarelate`_. From now we intend to manage phenonemon translation - in Iris itself. (:pull:`4484`) - -#. `@pp-mo`_ improved printout of various cube data component objects : - :class:`~iris.coords.Coord`, :class:`~iris.coords.CellMeasure`, - :class:`~iris.coords.AncillaryVariable`, - :class:`~iris.experimental.ugrid.mesh.MeshCoord` and - :class:`~iris.experimental.ugrid.mesh.Mesh`. - These now all provide a more controllable ``summary()`` method, and - more convenient and readable ``str()`` and ``repr()`` output in the style of - the :class:`iris.cube.Cube`. - They also no longer realise lazy data. (:pull:`4499`). +#. N/A 🐛 Bugs Fixed ============= -#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.intersection` for special cases where - one cell's bounds align with the requested maximum and negative minimum, fixing - :issue:`4221`. (:pull:`4278`) - -#. `@bsherratt`_ fixed further edge cases in - :meth:`~iris.cube.Cube.intersection`, including :issue:`3698` (:pull:`4363`) - -#. `@tinyendian`_ fixed the error message produced by :meth:`~iris.cube.CubeList.concatenate_cube` - when a cube list contains cubes with different names, which will no longer report - "Cube names differ: var1 != var1" if var1 appears multiple times in the list - (:issue:`4342`, :pull:`4345`) - -#. `@larsbarring`_ fixed :class:`~iris.coord_systems.GeoCS` to handle spherical ellipsoid - parameter inverse_flattening = 0 (:issue:`4146`, :pull:`4348`) - -#. `@pdearnshaw`_ fixed an error in the call to :class:`cftime.datetime` in - :mod:`~iris.fileformats.pp_save_rules` that prevented the saving to PP of climate - means for DJF (:pull:`4391`) - -#. `@wjbenfold`_ improved the error message for failure of :meth:`~iris.cube.CubeList.concatenate` - to indicate that the value of a scalar coordinate may be mismatched, rather than the metadata - (:issue:`4096`, :pull:`4387`) - -#. `@bsherratt`_ fixed a regression to the NAME file loader introduced in 3.0.4, - as well as some long-standing bugs with vertical coordinates and number - formats. (:pull:`4411`) - -#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.subset` to alway return ``None`` if - no value match is found. (:pull:`4417`) - -#. `@wjbenfold`_ changed :meth:`iris.util.points_step` to stop it from warning - when applied to a single point (:issue:`4250`, :pull:`4367`) - -#. `@trexfeathers`_ changed :class:`~iris.coords._DimensionalMetadata` and - :class:`~iris.experimental.ugrid.Connectivity` equality methods to preserve - array laziness, allowing efficient comparisons even with larger-than-memory - objects. (:pull:`4439`) - -#. `@rcomer`_ modified :meth:`~iris.cube.Cube.aggregated_by` to calculate new - coordinate bounds using minimum and maximum for unordered coordinates, - fixing :issue:`1528`. (:pull:`4315`) - -#. `@wjbenfold`_ changed how a delayed unit conversion is performed on a cube - so that a cube with lazy data awaiting a unit conversion can be pickled. - (:issue:`4354`, :pull:`4377`) - -#. `@pp-mo`_ fixed a bug in netcdf loading, whereby *any* rotated latlon coordinate - was mistakenly interpreted as a latitude, usually resulting in two 'latitude's - instead of one latitude and one longitude. - (:issue:`4460`, :pull:`4470`) - -#. `@wjbenfold`_ stopped :meth:`iris.coord_systems.GeogCS.as_cartopy_projection` - from assuming the globe to be the Earth (:issue:`4408`, :pull:`4497`) - -#. `@rcomer`_ corrected the ``long_name`` mapping from UM stash code ``m01s09i215`` - to indicate cloud fraction greater than 7.9 oktas, rather than 7.5 - (:issue:`3305`, :pull:`4535`) +#. N/A 💣 Incompatible Changes @@ -188,195 +49,41 @@ This document explains the changes made to Iris for this release 🚀 Performance Enhancements =========================== -#. `@wjbenfold`_ resolved an issue that previously caused regridding with lazy - data to take significantly longer than with real data. Benchmark - :class:`benchmarks.HorizontalChunkedRegridding` shows a time decrease - from >10s to 625ms. (:issue:`4280`, :pull:`4400`) - -#. `@bjlittle`_ included an optimisation to :class:`~iris.cube.Cube.coord_dims` - to avoid unnecessary processing whenever a coordinate instance that already - exists within the cube is provided. (:pull:`4549`) +#. N/A 🔥 Deprecations =============== -#. `@wjbenfold`_ removed :mod:`iris.experimental.equalise_cubes`. In ``v3.0`` - the experimental ``equalise_attributes`` functionality was moved to the - :mod:`iris.util.equalise_attributes` function. Since then, calling the - :func:`iris.experimental.equalise_cubes.equalise_attributes` function raised - an exception. (:issue:`3528`, :pull:`4496`) - -#. `@wjbenfold`_ deprecated :func:`iris.util.approx_equal` in preference for - :func:`math.isclose`. The :func:`~iris.util.approx_equal` function will be - removed in a future release of Iris. (:pull:`4514`) - -#. `@wjbenfold`_ deprecated :mod:`iris.experimental.raster` as it is not - believed to still be in use. The deprecation warnings invite users to contact - the Iris Developers if this isn't the case. (:pull:`4525`) - -#. `@wjbenfold`_ deprecated :mod:`iris.fileformats.abf` and - :mod:`iris.fileformats.dot` as they are not believed to still be in use. The - deprecation warnings invite users to contact the Iris Developers if this - isn't the case. (:pull:`4515`) - -#. `@wjbenfold`_ removed the :func:`iris.util.as_compatible_shape` function, - which was deprecated in ``v3.0``. Instead use - :class:`iris.common.resolve.Resolve`. For example, rather than calling - ``as_compatible_shape(src_cube, target_cube)`` replace with - ``Resolve(src_cube, target_cube)(target_cube.core_data())``. (:pull:`4513`) - -#. `@wjbenfold`_ deprecated :func:`iris.analysis.maths.intersection_of_cubes` in - preference for :meth:`iris.cube.CubeList.extract_overlapping`. The - :func:`~iris.analysis.maths.intersection_of_cubes` function will be removed in - a future release of Iris. (:pull:`4541`) - -#. `@pp-mo`_ deprecated :mod:`iris.experimental.regrid_conservative`. This is - now replaced by `iris-emsf-regrid`_. (:pull:`4551`) - -#. `@pp-mo`_ deprecated everything in :mod:`iris.experimental.regrid`. - Most features have a preferred exact alternative, as suggested, *except* - :class:`iris.experimental.regrid.ProjectedUnstructuredLinear` : that has no - identical equivalent, but :class:`iris.analysis.UnstructuredNearest` is - suggested as being quite close (though possibly slower). (:pull:`4548`) +#. N/A 🔗 Dependencies =============== -#. `@bjlittle`_ introduced the ``cartopy >=0.20`` minimum pin. - (:pull:`4331`) - -#. `@trexfeathers`_ introduced the ``cf-units >=3`` and ``nc-time-axis >=1.3`` - minimum pins. (:pull:`4356`) - -#. `@bjlittle`_ introduced the ``numpy >=1.19`` minimum pin, in - accordance with `NEP-29`_ deprecation policy. (:pull:`4386`) - -#. `@bjlittle`_ dropped support for ``Python 3.7``, as per the `NEP-29`_ - backwards compatibility and deprecation policy schedule. (:pull:`4481`) +#. N/A 📚 Documentation ================ -#. `@rcomer`_ updated the "Plotting Wind Direction Using Quiver" Gallery - example. (:pull:`4120`) - -#. `@trexfeathers`_ included `Iris GitHub Discussions`_ in - :ref:`get involved `. (:pull:`4307`) - -#. `@wjbenfold`_ improved readability in :ref:`userguide interpolation - section `. (:pull:`4314`) - -#. `@wjbenfold`_ added explanation about the absence of | operator for - :class:`iris.Constraint` to :ref:`userguide loading section - ` and to api reference documentation. (:pull:`4321`) - -#. `@trexfeathers`_ added more detail on making `iris-test-data`_ available - during :ref:`developer_running_tests`. (:pull:`4359`) - -#. `@lbdreyer`_ added a section to the release documentation outlining the role - of the :ref:`release_manager`. (:pull:`4413`) - -#. `@trexfeathers`_ encouraged contributors to include type hinting in code - they are working on - :ref:`code_formatting`. (:pull:`4390`) - -#. `@wjbenfold`_ updated Cartopy documentation links to point to the renamed - :class:`cartopy.mpl.geoaxes.GeoAxes`. (:pull:`4464`) - -#. `@wjbenfold`_ clarified behaviour of :func:`iris.load` in :ref:`userguide - loading section `. (:pull:`4462`) - -#. `@bjlittle`_ migrated readthedocs to use mambaforge for `faster documentation building`_. - (:pull:`4476`) - -#. `@wjbenfold`_ contributed `@alastair-gemmell`_'s :ref:`step-by-step guide to - contributing to the docs ` to the docs. - (:pull:`4461`) - -#. `@pp-mo`_ improved and corrected docstrings of - :class:`iris.analysis.PointInCell`, making it clear what is the actual - calculation performed. (:pull:`4548`) - -#. `@pp-mo`_ removed reference in docstring of - :class:`iris.analysis.UnstructuredNearest` to the obsolete (deprecated) - :class:`iris.experimental.regrid.ProjectedUnstructuredNearest`. - (:pull:`4548`) +#. N/A 💼 Internal =========== -#. `@trexfeathers`_ set the linkcheck to ignore - http://www.nationalarchives.gov.uk/doc/open-government-licence since this - always works locally, but never within CI. (:pull:`4307`) - -#. `@wjbenfold`_ netCDF integration tests now skip ``TestConstrainedLoad`` if - test data is missing (:pull:`4319`) - -#. `@wjbenfold`_ excluded ``Good First Issue`` labelled issues from being - marked stale. (:pull:`4317`) - -#. `@tkknight`_ added additional make targets for reducing the time of the - documentation build including ``html-noapi`` and ``html-quick``. - Useful for development purposes only. For more information see - :ref:`contributing.documentation.building` the documentation. (:pull:`4333`) - -#. `@rcomer`_ modified the ``animation`` test to prevent it throwing a warning - that sometimes interferes with unrelated tests. (:pull:`4330`) - -#. `@rcomer`_ removed a now redundant workaround in :func:`~iris.plot.contourf`. - (:pull:`4349`) - -#. `@trexfeathers`_ refactored :mod:`iris.experimental.ugrid` into sub-modules. - (:pull:`4347`). - -#. `@bjlittle`_ enabled the `sort-all`_ `pre-commit`_ hook to automatically - sort ``__all__`` entries into alphabetical order. (:pull:`4353`) - -#. `@rcomer`_ modified a NetCDF saver test to prevent it triggering a numpy - deprecation warning. (:issue:`4374`, :pull:`4376`) - -#. `@akuhnregnier`_ removed addition of period from - :func:`~iris.analysis.cartography.wrap_lons` and updated affected tests - using ``assertArrayAllClose`` following :issue:`3993`. - (:pull:`4421`) - -#. `@rcomer`_ updated some tests to work with Matplotlib v3.5. (:pull:`4428`) - -#. `@rcomer`_ applied minor fixes to some regridding tests. (:pull:`4432`) - -#. `@lbdreyer`_ corrected the license PyPI classifier. (:pull:`4435`) - -#. `@aaronspring `_ exchanged ``dask`` with - ``dask-core`` in testing environments reducing the number of dependencies - installed for testing. (:pull:`4434`) - -#. `@wjbenfold`_ prevented github action runs in forks (:issue:`4441`, - :pull:`4444`) +#. N/A -#. `@wjbenfold`_ fixed tests for hybrid formulae that weren't being found by - nose (:issue:`4431`, :pull:`4450`) .. comment Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: -.. _@aaronspring: https://github.com/aaronspring -.. _@akuhnregnier: https://github.com/akuhnregnier -.. _@bsherratt: https://github.com/bsherratt -.. _@larsbarring: https://github.com/larsbarring -.. _@pdearnshaw: https://github.com/pdearnshaw -.. _@SimonPeatman: https://github.com/SimonPeatman -.. _@tinyendian: https://github.com/tinyendian + + .. comment Whatsnew resources in alphabetical order: -.. _NEP-29: https://numpy.org/neps/nep-0029-deprecation_policy.html -.. _Metarelate: http://www.metarelate.net/ -.. _UGRID: http://ugrid-conventions.github.io/ugrid-conventions/ -.. _iris-emsf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid -.. _faster documentation building: https://docs.readthedocs.io/en/stable/guides/conda.html#making-builds-faster-with-mamba -.. _sort-all: https://github.com/aio-libs/sort-all + diff --git a/docs/src/whatsnew/dev.rst.template b/docs/src/whatsnew/dev.rst.template index 79c578ca65..1b36d3f0b0 100644 --- a/docs/src/whatsnew/dev.rst.template +++ b/docs/src/whatsnew/dev.rst.template @@ -24,7 +24,7 @@ This document explains the changes made to Iris for this release NOTE: section below is a template for bugfix patches ==================================================== - (Please remove this section when creating an initial 'latest.rst') + (Please remove this section when creating an initial 'dev.rst') v3.X.X (DD MMM YYYY) ==================== @@ -41,7 +41,7 @@ v3.X.X (DD MMM YYYY) NOTE: section above is a template for bugfix patches ==================================================== - (Please remove this section when creating an initial 'latest.rst') + (Please remove this section when creating an initial 'dev.rst') From 8838e23f7461c575adc841fc5c3304c975805d6a Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 4 Feb 2022 10:24:11 +0000 Subject: [PATCH 004/319] update trove classifiers (#4564) --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 1d3fb8b7c9..c2d31a5ddb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,6 @@ classifiers = Operating System :: Unix Programming Language :: Python Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: Implementation :: CPython Topic :: Scientific/Engineering From 48d354f29f6f21700dbf3b9fdbbe5716150b1525 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 4 Feb 2022 23:22:56 +0000 Subject: [PATCH 005/319] Update version to 3.3.dev0 (#4565) --- lib/iris/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 26f03c0566..713c163deb 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -104,7 +104,7 @@ def callback(cube, field, filename): # Iris revision. -__version__ = "3.2.dev0" +__version__ = "3.3.dev0" # Restrict the names imported when using "from iris import *" __all__ = [ From 1d5eb3eaf9d183af4e1849e82798bedf876ab53e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 5 Feb 2022 23:25:38 +0000 Subject: [PATCH 006/319] Updated environment lockfiles (#4567) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 368554bb25..caf6a739b3 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -22,7 +22,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_12.tar https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.3-h9c3ff4c_0.tar.bz2#bd783d12b65023e333bb7016de41570b +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.4-h9c3ff4c_0.tar.bz2#3cedab1fd76644efd516e1b271f2da95 https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#fe9a66a351bfa7a84c3108304c7bcba5 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -74,7 +74,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 -https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.27.27-hc3e0081_3.tar.bz2#a47110f41fcbf88fcdf8549d7f69a6d8 +https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h36c2ea0_1013.tar.bz2#cf7190238072a41e9579e4476a6a60b8 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 @@ -104,12 +104,12 @@ https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.2-hcc1bbae_3.tar.bz2#e https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/nss-3.74-hb5efdd6_0.tar.bz2#136876ca50177058594f6c2944e95c40 -https://conda.anaconda.org/conda-forge/linux-64/python-3.8.12-hb7a2778_2_cpython.tar.bz2#148ea076514259c7f562fbfba956a693 +https://conda.anaconda.org/conda-forge/linux-64/python-3.8.12-ha38a3c6_3_cpython.tar.bz2#bed445cebcd8f97dce76dc06201928ee https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.10-pyhd8ed1ab_0.tar.bz2#ea77236c8031cfa821720b21b4cb0ceb +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.11-pyhd8ed1ab_0.tar.bz2#e51530e33440ea8044edb0076cb40a0f https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb @@ -143,7 +143,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.ta https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.7.0-pyhd8ed1ab_0.tar.bz2#947f7f41958eabc0f6e886557512bb76 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.7.0-pyhd8ed1ab_1.tar.bz2#b689b2cbc8481b224777415e1a193170 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha00ac49_1009.tar.bz2#d1dff57b8731c245d3247b46d002e1c9 @@ -157,7 +157,7 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.3.2-py38h1fd1430_1. https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h3cfcdeb_1.tar.bz2#37d7568c595f0cfcd0c493f5ca0344ab https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.0.1-py38h497a2fe_1.tar.bz2#1ef7b5f4826ca48a15e2cd98a5c3436d https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38he865349_0.tar.bz2#b1b3d6847a68251a1465206ab466b475 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.1-py38h6ae9a64_0.tar.bz2#9ec24c7acb2252816f1f6b6687317432 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.2-py38h6ae9a64_0.tar.bz2#065a900932f904e0182acfcfadc467e3 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 @@ -169,7 +169,7 @@ https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-2.0.2-py38h497a2fe_1.tar.bz2#977d03222271270ea8fe35388bf13752 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h497a2fe_3.tar.bz2#131de7d638aa59fb8afbce59f1a8aa98 https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-ha98a1a1_5.tar.bz2#9b27fa0b1044a2119fb1b290617fe06f -https://conda.anaconda.org/conda-forge/linux-64/setuptools-60.5.0-py38h578d9bd_0.tar.bz2#9807c89f3ce846015dbad3c1d04348a5 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-60.7.1-py38h578d9bd_0.tar.bz2#8bf9c51a7e371df1673de909c1f46e6c https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h497a2fe_2.tar.bz2#63b3b55c98b4239134e0be080f448944 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.13.0-py38h578d9bd_0.tar.bz2#561081f4a30990533541979c9ee84732 @@ -177,14 +177,14 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h497a2fe_1003 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.5.2-py38h6c62de6_0.tar.bz2#73892e60ccea826c7f7a2215e48d22cf https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.1-py38h3e25421_0.tar.bz2#acc14d0d71dbf74f6a15f2456951b6cf https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.1.1-pyhd8ed1ab_0.tar.bz2#7968db84df10b74d9792d66d7da216df -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.29.0-py38h497a2fe_0.tar.bz2#3d96473ac57b7260a3fc3bdb13d2db79 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-3.2.0-hb4a5f5f_0.tar.bz2#d03d53e6bcb97e6a97a1659fb38aa76e +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.29.1-py38h497a2fe_0.tar.bz2#121e02be214af4980911bb2cbd5b2742 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-3.3.1-hb4a5f5f_0.tar.bz2#abe529a4b140720078f0febe1b6014a4 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.0.3-pyhd8ed1ab_0.tar.bz2#036d872c653780cb26e797e2e2f61b4c https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.0-py38h43a58ef_0.tar.bz2#23427f52c81076594a95c006ebf7552e -https://conda.anaconda.org/conda-forge/noarch/pip-21.3.1-pyhd8ed1ab_0.tar.bz2#e4fe2a9af78ff11f1aced7e62128c6a8 +https://conda.anaconda.org/conda-forge/noarch/pip-22.0.3-pyhd8ed1ab_0.tar.bz2#45dedae69a0ea21cb8566d04b2ca5536 https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38h5383654_1.tar.bz2#5b600e019fa7c33be73bdb626236936b https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba @@ -195,13 +195,13 @@ https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h596eeab_5.tar https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h1fd1430_1.tar.bz2#c494f75082f9c052944fda1b22c83336 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 -https://conda.anaconda.org/conda-forge/noarch/identify-2.4.6-pyhd8ed1ab_0.tar.bz2#d4030c75256440b8375b2f32c4ed35cd +https://conda.anaconda.org/conda-forge/noarch/identify-2.4.8-pyhd8ed1ab_0.tar.bz2#d4d25c0b7c1a7a1b0442e061fdd49260 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.3-h9967ed3_0.tar.bz2#37f1c68380bc5dfe0f5bb2655e207a73 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-21.0.0-pyhd8ed1ab_0.tar.bz2#8c49efecb7dca466e18b06015e8c88ce +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38ha217159_3.tar.bz2#d7461e191f7a0522e4709612786bdf4e From 8404ff6402488d720daff0062ab81060382bf087 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 10 Feb 2022 11:44:56 +0000 Subject: [PATCH 007/319] New tool-agnostic ASV environment management (#4571) * New tool-agnostic ASV env management. * Benchmarking only build the latest Python. * Increase benchmark accuracy by increasing rounds. * Fix ASV rounds mistake. * ASV clearer use of _interpolate_commands. --- benchmarks/asv.conf.json | 23 ++- benchmarks/asv_delegated_conda.py | 208 +++++++++++++++++++++++++ benchmarks/nox_asv_plugin.py | 249 ------------------------------ noxfile.py | 9 +- 4 files changed, 231 insertions(+), 258 deletions(-) create mode 100644 benchmarks/asv_delegated_conda.py delete mode 100644 benchmarks/nox_asv_plugin.py diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json index 9ea1cdb101..3468b2fca9 100644 --- a/benchmarks/asv.conf.json +++ b/benchmarks/asv.conf.json @@ -3,18 +3,25 @@ "project": "scitools-iris", "project_url": "https://github.com/SciTools/iris", "repo": "..", - "environment_type": "nox-conda", + "environment_type": "conda-delegated", "show_commit_url": "http://github.com/scitools/iris/commit/", "benchmark_dir": "./benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results", "html_dir": ".asv/html", - "plugins": [".nox_asv_plugin"], - // The commit to checkout to first run Nox to set up the environment. - "nox_setup_commit": "HEAD", - // The path of the noxfile's location relative to the project root. - "noxfile_rel_path": "noxfile.py", - // The ``--session`` arg to be used with ``--install-only`` to prep an environment. - "nox_session_name": "tests" + "plugins": [".asv_delegated_conda"], + + // The command(s) that create/update an environment correctly for the + // checked-out commit. + // Interpreted the same as build_command, with following exceptions: + // * No build-time environment variables. + // * Is run in the same environment as the ASV install itself. + "delegated_env_commands": [ + "sed -i 's/_PY_VERSIONS_ALL/_PY_VERSION_LATEST/g' noxfile.py", + "nox --envdir={conf_dir}/.asv/env/nox01 --session=tests --install-only --no-error-on-external-run --verbose" + ], + // The parent directory of the above environment. + // The most recently modified environment in the directory will be used. + "delegated_env_parent": "{conf_dir}/.asv/env/nox01" } diff --git a/benchmarks/asv_delegated_conda.py b/benchmarks/asv_delegated_conda.py new file mode 100644 index 0000000000..250a4e032d --- /dev/null +++ b/benchmarks/asv_delegated_conda.py @@ -0,0 +1,208 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +ASV plug-in providing an alternative :class:`asv.plugins.conda.Conda` +subclass that manages the Conda environment via custom user scripts. + +""" + +from os import environ +from os.path import getmtime +from pathlib import Path +from shutil import copy2, copytree, rmtree +from tempfile import TemporaryDirectory + +from asv import util as asv_util +from asv.config import Config +from asv.console import log +from asv.plugins.conda import Conda +from asv.repo import Repo + + +class CondaDelegated(Conda): + """ + Manage a Conda environment using custom user scripts, run at each commit. + + Ignores user input variations - ``matrix`` / ``pythons`` / + ``conda_environment_file``, since environment is being managed outside ASV. + + Original environment creation behaviour is inherited, but upon checking out + a commit the custom script(s) are run and the original environment is + replaced with a symlink to the custom environment. This arrangement is then + re-used in subsequent runs. + + """ + + tool_name = "conda-delegated" + + def __init__( + self, + conf: Config, + python: str, + requirements: dict, + tagged_env_vars: dict, + ) -> None: + """ + Parameters + ---------- + conf : Config instance + + python : str + Version of Python. Must be of the form "MAJOR.MINOR". + + requirements : dict + Dictionary mapping a PyPI package name to a version + identifier string. + + tagged_env_vars : dict + Environment variables, tagged for build vs. non-build + + """ + ignored = ["`python`"] + if requirements: + ignored.append("`requirements`") + if tagged_env_vars: + ignored.append("`tagged_env_vars`") + if conf.conda_environment_file: + ignored.append("`conda_environment_file`") + message = ( + f"Ignoring ASV setting(s): {', '.join(ignored)}. Benchmark " + "environment management is delegated to third party script(s)." + ) + log.warning(message) + requirements = {} + tagged_env_vars = {} + conf.conda_environment_file = None + + super().__init__(conf, python, requirements, tagged_env_vars) + self._update_info() + + self._env_commands = self._interpolate_commands( + conf.delegated_env_commands + ) + # Again using _interpolate_commands to get env parent path - allows use + # of the same ASV env variables. + env_parent_interpolated = self._interpolate_commands( + conf.delegated_env_parent + ) + # Returns list of tuples, we just want the first. + env_parent_first = env_parent_interpolated[0] + # The 'command' is the first item in the returned tuple. + env_parent_string = " ".join(env_parent_first[0]) + self._delegated_env_parent = Path(env_parent_string).resolve() + + @property + def name(self): + """Get a name to uniquely identify this environment.""" + return asv_util.sanitize_filename(self.tool_name) + + def _update_info(self) -> None: + """Make sure class properties reflect the actual environment being used.""" + # Follow symlink if it has been created. + actual_path = Path(self._path).resolve() + self._path = str(actual_path) + + # Get custom environment's Python version if it exists yet. + try: + get_version = ( + "from sys import version_info; " + "print(f'{version_info.major}.{version_info.minor}')" + ) + actual_python = self.run(["-c", get_version]) + self._python = actual_python + except OSError: + pass + + def _prep_env(self) -> None: + """Run the custom environment script(s) and switch to using that environment.""" + message = f"Running delegated environment management for: {self.name}" + log.info(message) + env_path = Path(self._path) + + def copy_asv_files(src_parent: Path, dst_parent: Path) -> None: + """For copying between self._path and a temporary cache.""" + asv_files = list(src_parent.glob("asv*")) + # build_root_path.name usually == "project" . + asv_files += [src_parent / Path(self._build_root).name] + for src_path in asv_files: + dst_path = dst_parent / src_path.name + if not dst_path.exists(): + # Only caching in case the environment has been rebuilt. + # If the dst_path already exists: rebuilding hasn't + # happened. Also a non-issue when copying in the reverse + # direction because the cache dir is temporary. + if src_path.is_dir(): + func = copytree + else: + func = copy2 + func(src_path, dst_path) + + with TemporaryDirectory(prefix="delegated_asv_cache_") as asv_cache: + asv_cache_path = Path(asv_cache) + # Cache all of ASV's files as delegated command may remove and + # re-build the environment. + copy_asv_files(env_path.resolve(), asv_cache_path) + + # Adapt the build_dir to the cache location. + build_root_path = Path(self._build_root) + build_dir_original = build_root_path / self._repo_subdir + build_dir_subpath = build_dir_original.relative_to( + build_root_path.parent + ) + build_dir = asv_cache_path / build_dir_subpath + + # Run the script(s) for delegated environment creation/updating. + # (An adaptation of self._interpolate_and_run_commands). + for command, env, return_codes, cwd in self._env_commands: + local_envs = dict(environ) + local_envs.update(env) + if cwd is None: + cwd = str(build_dir) + _ = asv_util.check_output( + command, + timeout=self._install_timeout, + cwd=cwd, + env=local_envs, + valid_return_codes=return_codes, + ) + + # Replace the env that ASV created with a symlink to the env + # created/updated by the custom script. + delegated_env_path = sorted( + self._delegated_env_parent.glob("*"), + key=getmtime, + reverse=True, + )[0] + if env_path.resolve() != delegated_env_path: + try: + env_path.unlink(missing_ok=True) + except IsADirectoryError: + rmtree(env_path) + env_path.symlink_to( + delegated_env_path, target_is_directory=True + ) + + # Check that environment exists. + try: + env_path.resolve(strict=True) + except FileNotFoundError: + message = f"Path does not resolve to environment: {env_path}" + log.error(message) + raise RuntimeError(message) + + # Restore ASV's files from the cache (if necessary). + copy_asv_files(asv_cache_path, env_path.resolve()) + + # Record new environment information in properties. + self._update_info() + + def checkout_project(self, repo: Repo, commit_hash: str) -> None: + """Check out the working tree of the project at given commit hash.""" + super().checkout_project(repo, commit_hash) + self._prep_env() + log.info( + f"Environment {self.name} updated to spec at {commit_hash[:8]}" + ) diff --git a/benchmarks/nox_asv_plugin.py b/benchmarks/nox_asv_plugin.py deleted file mode 100644 index 6c9ce14272..0000000000 --- a/benchmarks/nox_asv_plugin.py +++ /dev/null @@ -1,249 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -ASV plug-in providing an alternative ``Environment`` subclass, which uses Nox -for environment management. - -""" -from importlib.util import find_spec -from pathlib import Path -from shutil import copy2, copytree -from tempfile import TemporaryDirectory - -from asv import util as asv_util -from asv.config import Config -from asv.console import log -from asv.environment import get_env_name -from asv.plugins.conda import Conda, _find_conda -from asv.repo import Repo, get_repo - - -class NoxConda(Conda): - """ - Manage a Conda environment using Nox, updating environment at each commit. - - Defers environment management to the project's noxfile, which must be able - to create/update the benchmarking environment using ``nox --install-only``, - with the ``--session`` specified in ``asv.conf.json.nox_session_name``. - - Notes - ----- - If not all benchmarked commits support this use of Nox: the plugin will - need to be modified to prep the environment in other ways. - - """ - - tool_name = "nox-conda" - - @classmethod - def matches(cls, python: str) -> bool: - """Used by ASV to work out if this type of environment can be used.""" - result = find_spec("nox") is not None - if result: - result = super().matches(python) - - if result: - message = ( - f"NOTE: ASV env match check incomplete. Not possible to know " - f"if selected Nox session (asv.conf.json.nox_session_name) is " - f"compatible with ``--python={python}`` until project is " - f"checked out." - ) - log.warning(message) - - return result - - def __init__(self, conf: Config, python: str, requirements: dict) -> None: - """ - Parameters - ---------- - conf: Config instance - - python : str - Version of Python. Must be of the form "MAJOR.MINOR". - - requirements : dict - Dictionary mapping a PyPI package name to a version - identifier string. - - """ - from nox.sessions import _normalize_path - - # Need to checkout the project BEFORE the benchmark run - to access a noxfile. - self.project_temp_checkout = TemporaryDirectory( - prefix="nox_asv_checkout_" - ) - repo = get_repo(conf) - repo.checkout(self.project_temp_checkout.name, conf.nox_setup_commit) - self.noxfile_rel_path = conf.noxfile_rel_path - self.setup_noxfile = ( - Path(self.project_temp_checkout.name) / self.noxfile_rel_path - ) - self.nox_session_name = conf.nox_session_name - - # Some duplication of parent code - need these attributes BEFORE - # running inherited code. - self._python = python - self._requirements = requirements - self._env_dir = conf.env_dir - - # Prepare the actual environment path, to override self._path. - nox_envdir = str(Path(self._env_dir).absolute() / self.hashname) - nox_friendly_name = self._get_nox_session_name(python) - self._nox_path = Path(_normalize_path(nox_envdir, nox_friendly_name)) - - # For storing any extra conda requirements from asv.conf.json. - self._extra_reqs_path = self._nox_path / "asv-extra-reqs.yaml" - - super().__init__(conf, python, requirements) - - @property - def _path(self) -> str: - """ - Using a property to override getting and setting in parent classes - - unable to modify parent classes as this is a plugin. - - """ - return str(self._nox_path) - - @_path.setter - def _path(self, value) -> None: - """Enforce overriding of this variable by disabling modification.""" - pass - - @property - def name(self) -> str: - """Overridden to prevent inclusion of user input requirements.""" - return get_env_name(self.tool_name, self._python, {}) - - def _get_nox_session_name(self, python: str) -> str: - nox_cmd_substring = ( - f"--noxfile={self.setup_noxfile} " - f"--session={self.nox_session_name} " - f"--python={python}" - ) - - list_output = asv_util.check_output( - ["nox", "--list", *nox_cmd_substring.split(" ")], - display_error=False, - dots=False, - ) - list_output = list_output.split("\n") - list_matches = list(filter(lambda s: s.startswith("*"), list_output)) - matches_count = len(list_matches) - - if matches_count == 0: - message = f"No Nox sessions found for: {nox_cmd_substring} ." - log.error(message) - raise RuntimeError(message) - elif matches_count > 1: - message = ( - f"Ambiguous - >1 Nox session found for: {nox_cmd_substring} ." - ) - log.error(message) - raise RuntimeError(message) - else: - line = list_matches[0] - session_name = line.split(" ")[1] - assert isinstance(session_name, str) - return session_name - - def _nox_prep_env(self, setup: bool = False) -> None: - message = f"Running Nox environment update for: {self.name}" - log.info(message) - - build_root_path = Path(self._build_root) - env_path = Path(self._path) - - def copy_asv_files(src_parent: Path, dst_parent: Path) -> None: - """For copying between self._path and a temporary cache.""" - asv_files = list(src_parent.glob("asv*")) - # build_root_path.name usually == "project" . - asv_files += [src_parent / build_root_path.name] - for src_path in asv_files: - dst_path = dst_parent / src_path.name - if not dst_path.exists(): - # Only cache-ing in case Nox has rebuilt the env @ - # self._path. If the dst_path already exists: rebuilding - # hasn't happened. Also a non-issue when copying in the - # reverse direction because the cache dir is temporary. - if src_path.is_dir(): - func = copytree - else: - func = copy2 - func(src_path, dst_path) - - with TemporaryDirectory(prefix="nox_asv_cache_") as asv_cache: - asv_cache_path = Path(asv_cache) - if setup: - noxfile = self.setup_noxfile - else: - # Cache all of ASV's files as Nox may remove and re-build the environment. - copy_asv_files(env_path, asv_cache_path) - # Get location of noxfile in cache. - noxfile_original = ( - build_root_path / self._repo_subdir / self.noxfile_rel_path - ) - noxfile_subpath = noxfile_original.relative_to( - build_root_path.parent - ) - noxfile = asv_cache_path / noxfile_subpath - - nox_cmd = [ - "nox", - f"--noxfile={noxfile}", - # Place the env in the ASV env directory, instead of the default. - f"--envdir={env_path.parent}", - f"--session={self.nox_session_name}", - f"--python={self._python}", - "--install-only", - "--no-error-on-external-run", - "--verbose", - ] - - _ = asv_util.check_output(nox_cmd) - if not env_path.is_dir(): - message = f"Expected Nox environment not found: {env_path}" - log.error(message) - raise RuntimeError(message) - - if not setup: - # Restore ASV's files from the cache (if necessary). - copy_asv_files(asv_cache_path, env_path) - - def _setup(self) -> None: - """Used for initial environment creation - mimics parent method where possible.""" - try: - self.conda = _find_conda() - except IOError as e: - raise asv_util.UserError(str(e)) - if find_spec("nox") is None: - raise asv_util.UserError("Module not found: nox") - - message = f"Creating Nox-Conda environment for {self.name} ." - log.info(message) - - try: - self._nox_prep_env(setup=True) - finally: - # No longer need the setup checkout now that the environment has been built. - self.project_temp_checkout.cleanup() - - conda_args, pip_args = self._get_requirements(self.conda) - if conda_args or pip_args: - message = ( - "Ignoring user input package requirements. Benchmark " - "environment management is exclusively performed by Nox." - ) - log.warning(message) - - def checkout_project(self, repo: Repo, commit_hash: str) -> None: - """Check out the working tree of the project at given commit hash.""" - super().checkout_project(repo, commit_hash) - self._nox_prep_env() - log.info( - f"Environment {self.name} updated to spec at {commit_hash[:8]}" - ) diff --git a/noxfile.py b/noxfile.py index 8b23948677..6367b74aef 100755 --- a/noxfile.py +++ b/noxfile.py @@ -328,7 +328,14 @@ def asv_exec(*sub_args: str) -> None: # Else: compare to previous commit. previous_commit = os.environ.get("PR_BASE_SHA", "HEAD^1") try: - asv_exec("continuous", "--factor=1.2", previous_commit, "HEAD") + asv_exec( + "continuous", + "--factor=1.2", + previous_commit, + "HEAD", + "--attribute", + "rounds=4", + ) finally: asv_exec("compare", previous_commit, "HEAD") else: From e37d30460c4f5d22ce9a167e9f145f21f8ae065d Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 10 Feb 2022 12:18:39 +0000 Subject: [PATCH 008/319] adopt dependabot GHA (#4568) --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..e9b45d116a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# Reference: +# - https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/keeping-your-actions-up-to-date-with-dependabot +# - https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates + +version: 2 +updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "daily" + labels: + - "New: Pull Request" + - "Bot" From 6182d14e448aba8e2e82956a0d4747b0ca8296d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 12:56:08 +0000 Subject: [PATCH 009/319] Bump peter-evans/create-pull-request from 3.8.2 to 3.12.1 (#4577) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3.8.2 to 3.12.1. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/052fc72b4198ba9fbc81b818c6e1859f747d49a8...f22a7da129c901513876a2380e2dae9f8e145330) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100755 => 100644 .github/workflows/refresh-lockfiles.yml diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml old mode 100755 new mode 100644 index 3106d94a67..082a06fb5f --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -108,7 +108,7 @@ jobs: rm -r artifacts - name: Create Pull Request - uses: peter-evans/create-pull-request@052fc72b4198ba9fbc81b818c6e1859f747d49a8 + uses: peter-evans/create-pull-request@f22a7da129c901513876a2380e2dae9f8e145330 with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From 163b49abfe34563f80c92834a4916f10bb99548e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 12:57:03 +0000 Subject: [PATCH 010/319] Bump actions/stale from 4.0.0 to 4.1.0 (#4575) Bumps [actions/stale](https://github.com/actions/stale) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a38a03637e..f9bb09ce46 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,7 +10,7 @@ jobs: if: "github.repository == 'SciTools/iris'" runs-on: ubuntu-latest steps: - - uses: actions/stale@v4.0.0 + - uses: actions/stale@v4.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} From 7c1529cb4112a1f5ec1ce12603f67079487aa6dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Feb 2022 13:19:31 +0000 Subject: [PATCH 011/319] Bump actions/script from 4 to 5.1.0 (#4576) --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 082a06fb5f..a48b2a2629 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -35,7 +35,7 @@ jobs: # the lockfile bot has made the head commit, abort the workflow. # This job can be manually overridden by running directly from the github actions panel # (known as a "workflow_dispatch") and setting the `clobber` input to "yes". - - uses: actions/script@v4 + - uses: actions/script@v5.1.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From e3fbd843d0c64a027d5ff460a5564fdd3a87439d Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 10 Feb 2022 13:47:47 +0000 Subject: [PATCH 012/319] gha: lockfiles labels and auto-pr details (#4578) --- .github/workflows/refresh-lockfiles.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index a48b2a2629..f7fa10069f 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -108,6 +108,7 @@ jobs: rm -r artifacts - name: Create Pull Request + id: cpr uses: peter-evans/create-pull-request@f22a7da129c901513876a2380e2dae9f8e145330 with: commit-message: Updated environment lockfiles @@ -115,6 +116,17 @@ jobs: author: "Lockfile bot " delete-branch: true branch: auto-update-lockfiles - title: Update CI environment lockfiles + title: [iris.ci] environment lockfiles auto-update body: | Lockfiles updated to the latest resolvable environment. + labels: | + New: Pull Request + Bot + + - name: Check Pull Request + if: steps.cpr.outputs.pull-request-number != '' + run: | + echo "pull-request #${{ steps.cpr.outputs.pull-request-number }}" + echo "pull-request URL ${{ steps.cpr.outputs.pull-request-url }}" + echo "pull-request operation [${{ steps.cpr.outputs.pull-request-operation }}]" + echo "pull-request head SHA ${{ steps.cpr.outputs.pull-request-head-sha }}" From d1d1e005de0854ddecad457e31b285b37cb2245b Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 11 Feb 2022 09:37:12 +0000 Subject: [PATCH 013/319] Fix refresh lockfile worrkflow pull request title (#4579) --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index f7fa10069f..b40c3ca446 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -116,7 +116,7 @@ jobs: author: "Lockfile bot " delete-branch: true branch: auto-update-lockfiles - title: [iris.ci] environment lockfiles auto-update + title: "[iris.ci] environment lockfiles auto-update" body: | Lockfiles updated to the latest resolvable environment. labels: | From 611416730970d7603b6e1e4e48dc20c7fb55e2e6 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 10 Feb 2022 11:44:56 +0000 Subject: [PATCH 014/319] New tool-agnostic ASV environment management (#4571) * New tool-agnostic ASV env management. * Benchmarking only build the latest Python. * Increase benchmark accuracy by increasing rounds. * Fix ASV rounds mistake. * ASV clearer use of _interpolate_commands. --- benchmarks/asv.conf.json | 23 ++- benchmarks/asv_delegated_conda.py | 208 +++++++++++++++++++++++++ benchmarks/nox_asv_plugin.py | 249 ------------------------------ noxfile.py | 9 +- 4 files changed, 231 insertions(+), 258 deletions(-) create mode 100644 benchmarks/asv_delegated_conda.py delete mode 100644 benchmarks/nox_asv_plugin.py diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json index 9ea1cdb101..3468b2fca9 100644 --- a/benchmarks/asv.conf.json +++ b/benchmarks/asv.conf.json @@ -3,18 +3,25 @@ "project": "scitools-iris", "project_url": "https://github.com/SciTools/iris", "repo": "..", - "environment_type": "nox-conda", + "environment_type": "conda-delegated", "show_commit_url": "http://github.com/scitools/iris/commit/", "benchmark_dir": "./benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results", "html_dir": ".asv/html", - "plugins": [".nox_asv_plugin"], - // The commit to checkout to first run Nox to set up the environment. - "nox_setup_commit": "HEAD", - // The path of the noxfile's location relative to the project root. - "noxfile_rel_path": "noxfile.py", - // The ``--session`` arg to be used with ``--install-only`` to prep an environment. - "nox_session_name": "tests" + "plugins": [".asv_delegated_conda"], + + // The command(s) that create/update an environment correctly for the + // checked-out commit. + // Interpreted the same as build_command, with following exceptions: + // * No build-time environment variables. + // * Is run in the same environment as the ASV install itself. + "delegated_env_commands": [ + "sed -i 's/_PY_VERSIONS_ALL/_PY_VERSION_LATEST/g' noxfile.py", + "nox --envdir={conf_dir}/.asv/env/nox01 --session=tests --install-only --no-error-on-external-run --verbose" + ], + // The parent directory of the above environment. + // The most recently modified environment in the directory will be used. + "delegated_env_parent": "{conf_dir}/.asv/env/nox01" } diff --git a/benchmarks/asv_delegated_conda.py b/benchmarks/asv_delegated_conda.py new file mode 100644 index 0000000000..250a4e032d --- /dev/null +++ b/benchmarks/asv_delegated_conda.py @@ -0,0 +1,208 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +ASV plug-in providing an alternative :class:`asv.plugins.conda.Conda` +subclass that manages the Conda environment via custom user scripts. + +""" + +from os import environ +from os.path import getmtime +from pathlib import Path +from shutil import copy2, copytree, rmtree +from tempfile import TemporaryDirectory + +from asv import util as asv_util +from asv.config import Config +from asv.console import log +from asv.plugins.conda import Conda +from asv.repo import Repo + + +class CondaDelegated(Conda): + """ + Manage a Conda environment using custom user scripts, run at each commit. + + Ignores user input variations - ``matrix`` / ``pythons`` / + ``conda_environment_file``, since environment is being managed outside ASV. + + Original environment creation behaviour is inherited, but upon checking out + a commit the custom script(s) are run and the original environment is + replaced with a symlink to the custom environment. This arrangement is then + re-used in subsequent runs. + + """ + + tool_name = "conda-delegated" + + def __init__( + self, + conf: Config, + python: str, + requirements: dict, + tagged_env_vars: dict, + ) -> None: + """ + Parameters + ---------- + conf : Config instance + + python : str + Version of Python. Must be of the form "MAJOR.MINOR". + + requirements : dict + Dictionary mapping a PyPI package name to a version + identifier string. + + tagged_env_vars : dict + Environment variables, tagged for build vs. non-build + + """ + ignored = ["`python`"] + if requirements: + ignored.append("`requirements`") + if tagged_env_vars: + ignored.append("`tagged_env_vars`") + if conf.conda_environment_file: + ignored.append("`conda_environment_file`") + message = ( + f"Ignoring ASV setting(s): {', '.join(ignored)}. Benchmark " + "environment management is delegated to third party script(s)." + ) + log.warning(message) + requirements = {} + tagged_env_vars = {} + conf.conda_environment_file = None + + super().__init__(conf, python, requirements, tagged_env_vars) + self._update_info() + + self._env_commands = self._interpolate_commands( + conf.delegated_env_commands + ) + # Again using _interpolate_commands to get env parent path - allows use + # of the same ASV env variables. + env_parent_interpolated = self._interpolate_commands( + conf.delegated_env_parent + ) + # Returns list of tuples, we just want the first. + env_parent_first = env_parent_interpolated[0] + # The 'command' is the first item in the returned tuple. + env_parent_string = " ".join(env_parent_first[0]) + self._delegated_env_parent = Path(env_parent_string).resolve() + + @property + def name(self): + """Get a name to uniquely identify this environment.""" + return asv_util.sanitize_filename(self.tool_name) + + def _update_info(self) -> None: + """Make sure class properties reflect the actual environment being used.""" + # Follow symlink if it has been created. + actual_path = Path(self._path).resolve() + self._path = str(actual_path) + + # Get custom environment's Python version if it exists yet. + try: + get_version = ( + "from sys import version_info; " + "print(f'{version_info.major}.{version_info.minor}')" + ) + actual_python = self.run(["-c", get_version]) + self._python = actual_python + except OSError: + pass + + def _prep_env(self) -> None: + """Run the custom environment script(s) and switch to using that environment.""" + message = f"Running delegated environment management for: {self.name}" + log.info(message) + env_path = Path(self._path) + + def copy_asv_files(src_parent: Path, dst_parent: Path) -> None: + """For copying between self._path and a temporary cache.""" + asv_files = list(src_parent.glob("asv*")) + # build_root_path.name usually == "project" . + asv_files += [src_parent / Path(self._build_root).name] + for src_path in asv_files: + dst_path = dst_parent / src_path.name + if not dst_path.exists(): + # Only caching in case the environment has been rebuilt. + # If the dst_path already exists: rebuilding hasn't + # happened. Also a non-issue when copying in the reverse + # direction because the cache dir is temporary. + if src_path.is_dir(): + func = copytree + else: + func = copy2 + func(src_path, dst_path) + + with TemporaryDirectory(prefix="delegated_asv_cache_") as asv_cache: + asv_cache_path = Path(asv_cache) + # Cache all of ASV's files as delegated command may remove and + # re-build the environment. + copy_asv_files(env_path.resolve(), asv_cache_path) + + # Adapt the build_dir to the cache location. + build_root_path = Path(self._build_root) + build_dir_original = build_root_path / self._repo_subdir + build_dir_subpath = build_dir_original.relative_to( + build_root_path.parent + ) + build_dir = asv_cache_path / build_dir_subpath + + # Run the script(s) for delegated environment creation/updating. + # (An adaptation of self._interpolate_and_run_commands). + for command, env, return_codes, cwd in self._env_commands: + local_envs = dict(environ) + local_envs.update(env) + if cwd is None: + cwd = str(build_dir) + _ = asv_util.check_output( + command, + timeout=self._install_timeout, + cwd=cwd, + env=local_envs, + valid_return_codes=return_codes, + ) + + # Replace the env that ASV created with a symlink to the env + # created/updated by the custom script. + delegated_env_path = sorted( + self._delegated_env_parent.glob("*"), + key=getmtime, + reverse=True, + )[0] + if env_path.resolve() != delegated_env_path: + try: + env_path.unlink(missing_ok=True) + except IsADirectoryError: + rmtree(env_path) + env_path.symlink_to( + delegated_env_path, target_is_directory=True + ) + + # Check that environment exists. + try: + env_path.resolve(strict=True) + except FileNotFoundError: + message = f"Path does not resolve to environment: {env_path}" + log.error(message) + raise RuntimeError(message) + + # Restore ASV's files from the cache (if necessary). + copy_asv_files(asv_cache_path, env_path.resolve()) + + # Record new environment information in properties. + self._update_info() + + def checkout_project(self, repo: Repo, commit_hash: str) -> None: + """Check out the working tree of the project at given commit hash.""" + super().checkout_project(repo, commit_hash) + self._prep_env() + log.info( + f"Environment {self.name} updated to spec at {commit_hash[:8]}" + ) diff --git a/benchmarks/nox_asv_plugin.py b/benchmarks/nox_asv_plugin.py deleted file mode 100644 index 6c9ce14272..0000000000 --- a/benchmarks/nox_asv_plugin.py +++ /dev/null @@ -1,249 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -ASV plug-in providing an alternative ``Environment`` subclass, which uses Nox -for environment management. - -""" -from importlib.util import find_spec -from pathlib import Path -from shutil import copy2, copytree -from tempfile import TemporaryDirectory - -from asv import util as asv_util -from asv.config import Config -from asv.console import log -from asv.environment import get_env_name -from asv.plugins.conda import Conda, _find_conda -from asv.repo import Repo, get_repo - - -class NoxConda(Conda): - """ - Manage a Conda environment using Nox, updating environment at each commit. - - Defers environment management to the project's noxfile, which must be able - to create/update the benchmarking environment using ``nox --install-only``, - with the ``--session`` specified in ``asv.conf.json.nox_session_name``. - - Notes - ----- - If not all benchmarked commits support this use of Nox: the plugin will - need to be modified to prep the environment in other ways. - - """ - - tool_name = "nox-conda" - - @classmethod - def matches(cls, python: str) -> bool: - """Used by ASV to work out if this type of environment can be used.""" - result = find_spec("nox") is not None - if result: - result = super().matches(python) - - if result: - message = ( - f"NOTE: ASV env match check incomplete. Not possible to know " - f"if selected Nox session (asv.conf.json.nox_session_name) is " - f"compatible with ``--python={python}`` until project is " - f"checked out." - ) - log.warning(message) - - return result - - def __init__(self, conf: Config, python: str, requirements: dict) -> None: - """ - Parameters - ---------- - conf: Config instance - - python : str - Version of Python. Must be of the form "MAJOR.MINOR". - - requirements : dict - Dictionary mapping a PyPI package name to a version - identifier string. - - """ - from nox.sessions import _normalize_path - - # Need to checkout the project BEFORE the benchmark run - to access a noxfile. - self.project_temp_checkout = TemporaryDirectory( - prefix="nox_asv_checkout_" - ) - repo = get_repo(conf) - repo.checkout(self.project_temp_checkout.name, conf.nox_setup_commit) - self.noxfile_rel_path = conf.noxfile_rel_path - self.setup_noxfile = ( - Path(self.project_temp_checkout.name) / self.noxfile_rel_path - ) - self.nox_session_name = conf.nox_session_name - - # Some duplication of parent code - need these attributes BEFORE - # running inherited code. - self._python = python - self._requirements = requirements - self._env_dir = conf.env_dir - - # Prepare the actual environment path, to override self._path. - nox_envdir = str(Path(self._env_dir).absolute() / self.hashname) - nox_friendly_name = self._get_nox_session_name(python) - self._nox_path = Path(_normalize_path(nox_envdir, nox_friendly_name)) - - # For storing any extra conda requirements from asv.conf.json. - self._extra_reqs_path = self._nox_path / "asv-extra-reqs.yaml" - - super().__init__(conf, python, requirements) - - @property - def _path(self) -> str: - """ - Using a property to override getting and setting in parent classes - - unable to modify parent classes as this is a plugin. - - """ - return str(self._nox_path) - - @_path.setter - def _path(self, value) -> None: - """Enforce overriding of this variable by disabling modification.""" - pass - - @property - def name(self) -> str: - """Overridden to prevent inclusion of user input requirements.""" - return get_env_name(self.tool_name, self._python, {}) - - def _get_nox_session_name(self, python: str) -> str: - nox_cmd_substring = ( - f"--noxfile={self.setup_noxfile} " - f"--session={self.nox_session_name} " - f"--python={python}" - ) - - list_output = asv_util.check_output( - ["nox", "--list", *nox_cmd_substring.split(" ")], - display_error=False, - dots=False, - ) - list_output = list_output.split("\n") - list_matches = list(filter(lambda s: s.startswith("*"), list_output)) - matches_count = len(list_matches) - - if matches_count == 0: - message = f"No Nox sessions found for: {nox_cmd_substring} ." - log.error(message) - raise RuntimeError(message) - elif matches_count > 1: - message = ( - f"Ambiguous - >1 Nox session found for: {nox_cmd_substring} ." - ) - log.error(message) - raise RuntimeError(message) - else: - line = list_matches[0] - session_name = line.split(" ")[1] - assert isinstance(session_name, str) - return session_name - - def _nox_prep_env(self, setup: bool = False) -> None: - message = f"Running Nox environment update for: {self.name}" - log.info(message) - - build_root_path = Path(self._build_root) - env_path = Path(self._path) - - def copy_asv_files(src_parent: Path, dst_parent: Path) -> None: - """For copying between self._path and a temporary cache.""" - asv_files = list(src_parent.glob("asv*")) - # build_root_path.name usually == "project" . - asv_files += [src_parent / build_root_path.name] - for src_path in asv_files: - dst_path = dst_parent / src_path.name - if not dst_path.exists(): - # Only cache-ing in case Nox has rebuilt the env @ - # self._path. If the dst_path already exists: rebuilding - # hasn't happened. Also a non-issue when copying in the - # reverse direction because the cache dir is temporary. - if src_path.is_dir(): - func = copytree - else: - func = copy2 - func(src_path, dst_path) - - with TemporaryDirectory(prefix="nox_asv_cache_") as asv_cache: - asv_cache_path = Path(asv_cache) - if setup: - noxfile = self.setup_noxfile - else: - # Cache all of ASV's files as Nox may remove and re-build the environment. - copy_asv_files(env_path, asv_cache_path) - # Get location of noxfile in cache. - noxfile_original = ( - build_root_path / self._repo_subdir / self.noxfile_rel_path - ) - noxfile_subpath = noxfile_original.relative_to( - build_root_path.parent - ) - noxfile = asv_cache_path / noxfile_subpath - - nox_cmd = [ - "nox", - f"--noxfile={noxfile}", - # Place the env in the ASV env directory, instead of the default. - f"--envdir={env_path.parent}", - f"--session={self.nox_session_name}", - f"--python={self._python}", - "--install-only", - "--no-error-on-external-run", - "--verbose", - ] - - _ = asv_util.check_output(nox_cmd) - if not env_path.is_dir(): - message = f"Expected Nox environment not found: {env_path}" - log.error(message) - raise RuntimeError(message) - - if not setup: - # Restore ASV's files from the cache (if necessary). - copy_asv_files(asv_cache_path, env_path) - - def _setup(self) -> None: - """Used for initial environment creation - mimics parent method where possible.""" - try: - self.conda = _find_conda() - except IOError as e: - raise asv_util.UserError(str(e)) - if find_spec("nox") is None: - raise asv_util.UserError("Module not found: nox") - - message = f"Creating Nox-Conda environment for {self.name} ." - log.info(message) - - try: - self._nox_prep_env(setup=True) - finally: - # No longer need the setup checkout now that the environment has been built. - self.project_temp_checkout.cleanup() - - conda_args, pip_args = self._get_requirements(self.conda) - if conda_args or pip_args: - message = ( - "Ignoring user input package requirements. Benchmark " - "environment management is exclusively performed by Nox." - ) - log.warning(message) - - def checkout_project(self, repo: Repo, commit_hash: str) -> None: - """Check out the working tree of the project at given commit hash.""" - super().checkout_project(repo, commit_hash) - self._nox_prep_env() - log.info( - f"Environment {self.name} updated to spec at {commit_hash[:8]}" - ) diff --git a/noxfile.py b/noxfile.py index 8b23948677..6367b74aef 100755 --- a/noxfile.py +++ b/noxfile.py @@ -328,7 +328,14 @@ def asv_exec(*sub_args: str) -> None: # Else: compare to previous commit. previous_commit = os.environ.get("PR_BASE_SHA", "HEAD^1") try: - asv_exec("continuous", "--factor=1.2", previous_commit, "HEAD") + asv_exec( + "continuous", + "--factor=1.2", + previous_commit, + "HEAD", + "--attribute", + "rounds=4", + ) finally: asv_exec("compare", previous_commit, "HEAD") else: From ee9cadc989931dbd93d59495c92ddec88dbf4e68 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 11 Feb 2022 11:06:20 +0000 Subject: [PATCH 015/319] Fix load_http bug, extend testing, and note to docs (#4580) * Fix opendap bug, add docs and extra testing * Add whats new entry * Update docs/src/whatsnew/3.2.rst Co-authored-by: Bill Little * Add warning box Co-authored-by: Bill Little --- docs/src/whatsnew/3.2.rst | 3 +++ lib/iris/__init__.py | 8 ++++++++ lib/iris/fileformats/netcdf.py | 4 ++-- lib/iris/io/__init__.py | 8 ++++---- lib/iris/tests/test_load.py | 35 +++++++++++++++++++++++++++------- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index c78e1283d6..9aa6a78846 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -177,6 +177,9 @@ This document explains the changes made to Iris for this release to indicate cloud fraction greater than 7.9 oktas, rather than 7.5 (:issue:`3305`, :pull:`4535`) +#. `@lbdreyer`_ fixed a bug in :class:`iris.io.load_http` which was missing an import + (:pull:`4580`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index aca4e77e88..95722c69cf 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -44,6 +44,10 @@ standard library function :func:`os.path.expanduser` and module :mod:`fnmatch` for more details. + .. warning:: + + If supplying a URL, only OPeNDAP Data Sources are supported. + * constraints: Either a single constraint, or an iterable of constraints. Each constraint can be either a string, an instance of @@ -287,6 +291,7 @@ def load(uris, constraints=None, callback=None): * uris: One or more filenames/URIs, as a string or :class:`pathlib.PurePath`. + If supplying a URL, only OPeNDAP Data Sources are supported. Kwargs: @@ -315,6 +320,7 @@ def load_cube(uris, constraint=None, callback=None): * uris: One or more filenames/URIs, as a string or :class:`pathlib.PurePath`. + If supplying a URL, only OPeNDAP Data Sources are supported. Kwargs: @@ -354,6 +360,7 @@ def load_cubes(uris, constraints=None, callback=None): * uris: One or more filenames/URIs, as a string or :class:`pathlib.PurePath`. + If supplying a URL, only OPeNDAP Data Sources are supported. Kwargs: @@ -399,6 +406,7 @@ def load_raw(uris, constraints=None, callback=None): * uris: One or more filenames/URIs, as a string or :class:`pathlib.PurePath`. + If supplying a URL, only OPeNDAP Data Sources are supported. Kwargs: diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 100ab29daa..dd819fb63f 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -825,12 +825,12 @@ def inner(cf_datavar): def load_cubes(filenames, callback=None, constraints=None): """ - Loads cubes from a list of NetCDF filenames/URLs. + Loads cubes from a list of NetCDF filenames/OPeNDAP URLs. Args: * filenames (string/list): - One or more NetCDF filenames/DAP URLs to load from. + One or more NetCDF filenames/OPeNDAP URLs to load from. Kwargs: diff --git a/lib/iris/io/__init__.py b/lib/iris/io/__init__.py index 034fa4baab..8d5a2e05d2 100644 --- a/lib/iris/io/__init__.py +++ b/lib/iris/io/__init__.py @@ -216,7 +216,7 @@ def load_files(filenames, callback, constraints=None): def load_http(urls, callback): """ - Takes a list of urls and a callback function, and returns a generator + Takes a list of OPeNDAP URLs and a callback function, and returns a generator of Cubes from the given URLs. .. note:: @@ -226,11 +226,11 @@ def load_http(urls, callback): """ # Create default dict mapping iris format handler to its associated filenames + from iris.fileformats import FORMAT_AGENT + handler_map = collections.defaultdict(list) for url in urls: - handling_format_spec = iris.fileformats.FORMAT_AGENT.get_spec( - url, None - ) + handling_format_spec = FORMAT_AGENT.get_spec(url, None) handler_map[handling_format_spec].append(url) # Call each iris format handler with the appropriate filenames diff --git a/lib/iris/tests/test_load.py b/lib/iris/tests/test_load.py index 86ff2f1ece..d21b40ee26 100644 --- a/lib/iris/tests/test_load.py +++ b/lib/iris/tests/test_load.py @@ -12,6 +12,9 @@ import iris.tests as tests # isort:skip import pathlib +from unittest import mock + +import netCDF4 import iris import iris.io @@ -148,19 +151,20 @@ def test_path_object(self): self.assertEqual(len(cubes), 1) -class TestOpenDAP(tests.IrisTest): - def test_load(self): - # Check that calling iris.load_* with a http URI triggers a call to - # ``iris.io.load_http`` +class TestOPeNDAP(tests.IrisTest): + def setUp(self): + self.url = "http://geoport.whoi.edu:80/thredds/dodsC/bathy/gom15" - url = "http://geoport.whoi.edu:80/thredds/dodsC/bathy/gom15" + def test_load_http_called(self): + # Check that calling iris.load_* with an http URI triggers a call to + # ``iris.io.load_http`` class LoadHTTPCalled(Exception): pass def new_load_http(passed_urls, *args, **kwargs): self.assertEqual(len(passed_urls), 1) - self.assertEqual(url, passed_urls[0]) + self.assertEqual(self.url, passed_urls[0]) raise LoadHTTPCalled() try: @@ -174,11 +178,28 @@ def new_load_http(passed_urls, *args, **kwargs): iris.load_cubes, ]: with self.assertRaises(LoadHTTPCalled): - fn(url) + fn(self.url) finally: iris.io.load_http = orig + def test_netCDF_Dataset_call(self): + # Check that load_http calls netCDF4.Dataset and supplies the expected URL. + + # To avoid making a request to an OPeNDAP server in a test, instead + # mock the call to netCDF.Dataset so that it returns a dataset for a + # local file. + filename = tests.get_data_path( + ("NetCDF", "global", "xyt", "SMALL_total_column_co2.nc") + ) + fake_dataset = netCDF4.Dataset(filename) + + with mock.patch( + "netCDF4.Dataset", return_value=fake_dataset + ) as dataset_loader: + next(iris.io.load_http([self.url], callback=None)) + dataset_loader.assert_called_with(self.url, mode="r") + if __name__ == "__main__": tests.main() From 15bd351f7d68fa0470376d52e52a621ef3566457 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:11:19 +0000 Subject: [PATCH 016/319] Loading Benchmarks (#4477) * Synthetic FF PP NetCDF and loading benchmarks. --- .github/workflows/benchmark.yml | 14 +- benchmarks/benchmarks/__init__.py | 41 ---- .../benchmarks/generate_data/__init__.py | 94 ++++++++ .../benchmarks/generate_data/um_files.py | 215 ++++++++++++++++++ benchmarks/benchmarks/loading.py | 185 +++++++++++++++ noxfile.py | 45 +++- 6 files changed, 543 insertions(+), 51 deletions(-) create mode 100644 benchmarks/benchmarks/generate_data/__init__.py create mode 100644 benchmarks/benchmarks/generate_data/um_files.py create mode 100644 benchmarks/benchmarks/loading.py diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b489eba036..a8247a247b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -16,7 +16,9 @@ jobs: IRIS_TEST_DATA_PATH: benchmarks/iris-test-data IRIS_TEST_DATA_VERSION: "2.5" # Lets us manually bump the cache to rebuild + ENV_CACHE_BUILD: "0" TEST_DATA_CACHE_BUILD: "2" + PY_VER: 3.8 steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it @@ -32,19 +34,15 @@ jobs: run: | pip install nox - - name: Cache .nox and .asv/env directories + - name: Cache environment directories id: cache-env-dir uses: actions/cache@v2 with: path: | .nox benchmarks/.asv/env - # Make sure GHA never gets an exact cache match by using the unique - # github.sha. This means it will always store this run as a new - # cache (Nox may have made relevant changes during run). Cache - # restoration still succeeds via the partial restore-key match. - key: ${{ runner.os }}-${{ github.sha }} - restore-keys: ${{ runner.os }} + $CONDA/pkgs + key: ${{ runner.os }}-${{ hashFiles('requirements/') }}-${{ env.ENV_CACHE_BUILD }} - name: Cache test data directory id: cache-test-data @@ -62,7 +60,7 @@ jobs: unzip -q iris-test-data.zip mkdir --parents ${GITHUB_WORKSPACE}/${IRIS_TEST_DATA_LOC_PATH} mv iris-test-data-${IRIS_TEST_DATA_VERSION} ${GITHUB_WORKSPACE}/${IRIS_TEST_DATA_PATH} - + - name: Set test data var run: | echo "OVERRIDE_TEST_DATA_REPOSITORY=${GITHUB_WORKSPACE}/${IRIS_TEST_DATA_PATH}/test_data" >> $GITHUB_ENV diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py index 2e741c3da0..4a964a648d 100644 --- a/benchmarks/benchmarks/__init__.py +++ b/benchmarks/benchmarks/__init__.py @@ -5,45 +5,4 @@ # licensing details. """Common code for benchmarks.""" -import os -from pathlib import Path - -# Environment variable names -_ASVDIR_VARNAME = "ASV_DIR" # As set in nightly script "asv_nightly/asv.sh" -_DATADIR_VARNAME = "BENCHMARK_DATA" # For local runs - ARTIFICIAL_DIM_SIZE = int(10e3) # For all artificial cubes, coords etc. - -# Work out where the benchmark data dir is. -asv_dir = os.environ.get("ASV_DIR", None) -if asv_dir: - # For an overnight run, this comes from the 'ASV_DIR' setting. - benchmark_data_dir = Path(asv_dir) / "data" -else: - # For a local run, you set 'BENCHMARK_DATA'. - benchmark_data_dir = os.environ.get(_DATADIR_VARNAME, None) - if benchmark_data_dir is not None: - benchmark_data_dir = Path(benchmark_data_dir) - - -def testdata_path(*path_names): - """ - Return the path of a benchmark test data file. - - These are based from a test-data location dir, which is either - ${}/data (for overnight tests), or ${} for local testing. - - If neither of these were set, an error is raised. - - """.format( - _ASVDIR_VARNAME, _DATADIR_VARNAME - ) - if benchmark_data_dir is None: - msg = ( - "Benchmark data dir is not defined : " - 'Either "${}" or "${}" must be set.' - ) - raise (ValueError(msg.format(_ASVDIR_VARNAME, _DATADIR_VARNAME))) - path = benchmark_data_dir.joinpath(*path_names) - path = str(path) # Because Iris doesn't understand Path objects yet. - return path diff --git a/benchmarks/benchmarks/generate_data/__init__.py b/benchmarks/benchmarks/generate_data/__init__.py new file mode 100644 index 0000000000..a56f2e4623 --- /dev/null +++ b/benchmarks/benchmarks/generate_data/__init__.py @@ -0,0 +1,94 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Scripts for generating supporting data for benchmarking. + +Data generated using Iris should use :func:`run_function_elsewhere`, which +means that data is generated using a fixed version of Iris and a fixed +environment, rather than those that get changed when the benchmarking run +checks out a new commit. + +Downstream use of data generated 'elsewhere' requires saving; usually in a +NetCDF file. Could also use pickling but there is a potential risk if the +benchmark sequence runs over two different Python versions. + +""" +from inspect import getsource +from os import environ +from pathlib import Path +from subprocess import CalledProcessError, check_output, run +from textwrap import dedent + +#: Python executable used by :func:`run_function_elsewhere`, set via env +#: variable of same name. Must be path of Python within an environment that +#: includes Iris (including dependencies and test modules) and Mule. +try: + DATA_GEN_PYTHON = environ["DATA_GEN_PYTHON"] + _ = check_output([DATA_GEN_PYTHON, "-c", "a = True"]) +except KeyError: + error = "Env variable DATA_GEN_PYTHON not defined." + raise KeyError(error) +except (CalledProcessError, FileNotFoundError, PermissionError): + error = ( + "Env variable DATA_GEN_PYTHON not a runnable python executable path." + ) + raise ValueError(error) + +# The default location of data files used in benchmarks. Used by CI. +default_data_dir = (Path(__file__).parents[2] / ".data").resolve() +# Optionally override the default data location with environment variable. +BENCHMARK_DATA = Path(environ.get("BENCHMARK_DATA", default_data_dir)) +if BENCHMARK_DATA == default_data_dir: + BENCHMARK_DATA.mkdir(exist_ok=True) +elif not BENCHMARK_DATA.is_dir(): + message = f"Not a directory: {BENCHMARK_DATA} ." + raise ValueError(message) + +# Manual flag to allow the rebuilding of synthetic data. +# False forces a benchmark run to re-make all the data files. +REUSE_DATA = True + + +def run_function_elsewhere(func_to_run, *args, **kwargs): + """ + Run a given function using the :const:`DATA_GEN_PYTHON` executable. + + This structure allows the function to be written natively. + + Parameters + ---------- + func_to_run : FunctionType + The function object to be run. + NOTE: the function must be completely self-contained, i.e. perform all + its own imports (within the target :const:`DATA_GEN_PYTHON` + environment). + *args : tuple, optional + Function call arguments. Must all be expressible as simple literals, + i.e. the ``repr`` must be a valid literal expression. + **kwargs: dict, optional + Function call keyword arguments. All values must be expressible as + simple literals (see ``*args``). + + Returns + ------- + str + The ``stdout`` from the run. + + """ + func_string = dedent(getsource(func_to_run)) + func_string = func_string.replace("@staticmethod\n", "") + func_call_term_strings = [repr(arg) for arg in args] + func_call_term_strings += [ + f"{name}={repr(val)}" for name, val in kwargs.items() + ] + func_call_string = ( + f"{func_to_run.__name__}(" + ",".join(func_call_term_strings) + ")" + ) + python_string = "\n".join([func_string, func_call_string]) + result = run( + [DATA_GEN_PYTHON, "-c", python_string], capture_output=True, check=True + ) + return result.stdout diff --git a/benchmarks/benchmarks/generate_data/um_files.py b/benchmarks/benchmarks/generate_data/um_files.py new file mode 100644 index 0000000000..8792fcc48b --- /dev/null +++ b/benchmarks/benchmarks/generate_data/um_files.py @@ -0,0 +1,215 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Generate FF, PP and NetCDF files based on a minimal synthetic FF file. + +NOTE: uses the Mule package, so depends on an environment with Mule installed. +""" + + +def _create_um_files( + len_x: int, len_y: int, len_z: int, len_t: int, compress, save_paths: dict +) -> None: + """ + Generate an FF object of given shape and compression, save to FF/PP/NetCDF. + + This is run externally + (:func:`benchmarks.generate_data.run_function_elsewhere`), so all imports + are self-contained and input parameters are simple types. + """ + from copy import deepcopy + from datetime import datetime + from tempfile import NamedTemporaryFile + + from mo_pack import compress_wgdos as mo_pack_compress + from mule import ArrayDataProvider, Field3, FieldsFile + from mule.pp import fields_to_pp_file + import numpy as np + + from iris import load_cube + from iris import save as save_cube + + def packing_patch(*compress_args, **compress_kwargs) -> bytes: + """ + Force conversion from returned :class:`memoryview` to :class:`bytes`. + + Downstream uses of :func:`mo_pack.compress_wgdos` were written + for the ``Python2`` behaviour, where the returned buffer had a + different ``__len__`` value to the current :class:`memoryview`. + Unable to fix directly in Mule, so monkey patching for now. + """ + return mo_pack_compress(*compress_args, **compress_kwargs).tobytes() + + import mo_pack + + mo_pack.compress_wgdos = packing_patch + + ######## + + template = { + "fixed_length_header": {"dataset_type": 3, "grid_staggering": 3}, + "integer_constants": { + "num_p_levels": len_z, + "num_cols": len_x, + "num_rows": len_y, + }, + "real_constants": {}, + "level_dependent_constants": {"dims": (len_z + 1, None)}, + } + new_ff = FieldsFile.from_template(deepcopy(template)) + + data_array = np.arange(len_x * len_y).reshape(len_x, len_y) + array_provider = ArrayDataProvider(data_array) + + def add_field(level_: int, time_step_: int) -> None: + """ + Add a minimal field to the new :class:`~mule.FieldsFile`. + + Includes the minimum information to allow Mule saving and Iris + loading, as well as incrementation for vertical levels and time + steps to allow generation of z and t dimensions. + """ + new_field = Field3.empty() + # To correspond to the header-release 3 class used. + new_field.lbrel = 3 + # Mule uses the first element of the lookup to test for + # unpopulated fields (and skips them), so the first element should + # be set to something. The year will do. + new_field.raw[1] = datetime.now().year + + # Horizontal. + new_field.lbcode = 1 + new_field.lbnpt = len_x + new_field.lbrow = len_y + new_field.bdx = new_ff.real_constants.col_spacing + new_field.bdy = new_ff.real_constants.row_spacing + new_field.bzx = new_ff.real_constants.start_lon - 0.5 * new_field.bdx + new_field.bzy = new_ff.real_constants.start_lat - 0.5 * new_field.bdy + + # Hemisphere. + new_field.lbhem = 32 + # Processing. + new_field.lbproc = 0 + + # Vertical. + # Hybrid height values by simulating sequences similar to those in a + # theta file. + new_field.lbvc = 65 + if level_ == 0: + new_field.lblev = 9999 + else: + new_field.lblev = level_ + + level_1 = level_ + 1 + six_rec = 20 / 3 + three_rec = six_rec / 2 + + new_field.blev = level_1 ** 2 * six_rec - six_rec + new_field.brsvd1 = ( + level_1 ** 2 * six_rec + (six_rec * level_1) - three_rec + ) + + brsvd2_simulated = np.linspace(0.995, 0, len_z) + shift = min(len_z, 2) + bhrlev_simulated = np.concatenate( + [np.ones(shift), brsvd2_simulated[:-shift]] + ) + new_field.brsvd2 = brsvd2_simulated[level_] + new_field.bhrlev = bhrlev_simulated[level_] + + # Time. + new_field.lbtim = 11 + + new_field.lbyr = time_step_ + for attr_name in ["lbmon", "lbdat", "lbhr", "lbmin", "lbsec"]: + setattr(new_field, attr_name, 0) + + new_field.lbyrd = time_step_ + 1 + for attr_name in ["lbmond", "lbdatd", "lbhrd", "lbmind", "lbsecd"]: + setattr(new_field, attr_name, 0) + + # Data and packing. + new_field.lbuser1 = 1 + new_field.lbpack = int(compress) + new_field.bacc = 0 + new_field.bmdi = -1 + new_field.lbext = 0 + new_field.set_data_provider(array_provider) + + new_ff.fields.append(new_field) + + for time_step in range(len_t): + for level in range(len_z): + add_field(level, time_step + 1) + + ff_path = save_paths.get("FF", None) + pp_path = save_paths.get("PP", None) + nc_path = save_paths.get("NetCDF", None) + + if ff_path: + new_ff.to_file(ff_path) + if pp_path: + fields_to_pp_file(str(pp_path), new_ff.fields) + if nc_path: + temp_ff_path = None + # Need an Iris Cube from the FF content. + if ff_path: + # Use the existing file. + ff_cube = load_cube(ff_path) + else: + # Make a temporary file. + temp_ff_path = NamedTemporaryFile() + new_ff.to_file(temp_ff_path.name) + ff_cube = load_cube(temp_ff_path.name) + + save_cube(ff_cube, nc_path, zlib=compress) + if temp_ff_path: + temp_ff_path.close() + + +FILE_EXTENSIONS = {"FF": "", "PP": ".pp", "NetCDF": ".nc"} + + +def create_um_files( + len_x: int, + len_y: int, + len_z: int, + len_t: int, + compress: bool, + file_types: list, +) -> dict: + """ + Generate FF-based FF / PP / NetCDF files with specified shape and compression. + + All files representing a given shape are saved in a dedicated directory. A + dictionary of the saved paths is returned. + + If the required files exist, they are re-used, unless + :const:`benchmarks.REUSE_DATA` is ``False``. + """ + # Self contained imports to avoid linting confusion with _create_um_files(). + from . import BENCHMARK_DATA, REUSE_DATA, run_function_elsewhere + + save_name_sections = ["UM", len_x, len_y, len_z, len_t] + save_name = "_".join(str(section) for section in save_name_sections) + save_dir = BENCHMARK_DATA / save_name + if not save_dir.is_dir(): + save_dir.mkdir(parents=True) + + save_paths = {} + files_exist = True + for file_type in file_types: + file_ext = FILE_EXTENSIONS[file_type] + save_path = (save_dir / f"{compress}").with_suffix(file_ext) + files_exist = files_exist and save_path.is_file() + save_paths[file_type] = str(save_path) + + if not REUSE_DATA or not files_exist: + _ = run_function_elsewhere( + _create_um_files, len_x, len_y, len_z, len_t, compress, save_paths + ) + + return save_paths diff --git a/benchmarks/benchmarks/loading.py b/benchmarks/benchmarks/loading.py new file mode 100644 index 0000000000..4558c3b5cb --- /dev/null +++ b/benchmarks/benchmarks/loading.py @@ -0,0 +1,185 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +File loading benchmark tests. + +Where applicable benchmarks should be parameterised for two sizes of input data: + * minimal: enables detection of regressions in parts of the run-time that do + NOT scale with data size. + * large: large enough to exclusively detect regressions in parts of the + run-time that scale with data size. Size should be _just_ large + enough - don't want to bloat benchmark runtime. + +""" + +from iris import AttributeConstraint, Constraint, load, load_cube +from iris.cube import Cube +from iris.fileformats.um import structured_um_loading + +from .generate_data import BENCHMARK_DATA, REUSE_DATA, run_function_elsewhere +from .generate_data.um_files import create_um_files + + +class LoadAndRealise: + params = [ + [(2, 2, 2), (1280, 960, 5)], + [False, True], + ["FF", "PP", "NetCDF"], + ] + param_names = ["xyz", "compressed", "file_format"] + + def setup_cache(self) -> dict: + file_type_args = self.params[2] + file_path_dict = {} + for xyz in self.params[0]: + file_path_dict[xyz] = {} + x, y, z = xyz + for compress in self.params[1]: + file_path_dict[xyz][compress] = create_um_files( + x, y, z, 1, compress, file_type_args + ) + return file_path_dict + + def setup( + self, + file_path_dict: dict, + xyz: tuple, + compress: bool, + file_format: str, + ) -> None: + self.file_path = file_path_dict[xyz][compress][file_format] + self.cube = self.load() + + def load(self) -> Cube: + return load_cube(self.file_path) + + def time_load(self, _, __, ___, ____) -> None: + _ = self.load() + + def time_realise(self, _, __, ___, ____) -> None: + # Don't touch cube.data - permanent realisation plays badly with ASV's + # re-run strategy. + assert self.cube.has_lazy_data() + self.cube.core_data().compute() + + +class STASHConstraint: + # xyz sizes mimic LoadAndRealise to maximise file re-use. + params = [[(2, 2, 2), (1280, 960, 5)], ["FF", "PP"]] + param_names = ["xyz", "file_format"] + + def setup_cache(self) -> dict: + file_type_args = self.params[1] + file_path_dict = {} + for xyz in self.params[0]: + x, y, z = xyz + file_path_dict[xyz] = create_um_files( + x, y, z, 1, False, file_type_args + ) + return file_path_dict + + def setup( + self, file_path_dict: dict, xyz: tuple, file_format: str + ) -> None: + self.file_path = file_path_dict[xyz][file_format] + + def time_stash_constraint(self, _, __, ___) -> None: + _ = load_cube(self.file_path, AttributeConstraint(STASH="m??s??i901")) + + +class TimeConstraint: + params = [[3, 20], ["FF", "PP", "NetCDF"]] + param_names = ["time_dim_len", "file_format"] + + def setup_cache(self) -> dict: + file_type_args = self.params[1] + file_path_dict = {} + for time_dim_len in self.params[0]: + file_path_dict[time_dim_len] = create_um_files( + 20, 20, 5, time_dim_len, False, file_type_args + ) + return file_path_dict + + def setup( + self, file_path_dict: dict, time_dim_len: int, file_format: str + ) -> None: + self.file_path = file_path_dict[time_dim_len][file_format] + self.time_constr = Constraint(time=lambda cell: cell.point.year < 3) + + def time_time_constraint(self, _, __, ___) -> None: + _ = load_cube(self.file_path, self.time_constr) + + +class ManyVars: + FILE_PATH = BENCHMARK_DATA / "many_var_file.nc" + + @staticmethod + def _create_file(save_path: str) -> None: + """Is run externally - everything must be self-contained.""" + import numpy as np + + from iris import save + from iris.coords import AuxCoord + from iris.cube import Cube + + data_len = 8 + data = np.arange(data_len) + cube = Cube(data, units="unknown") + extra_vars = 80 + names = ["coord_" + str(i) for i in range(extra_vars)] + for name in names: + coord = AuxCoord(data, long_name=name, units="unknown") + cube.add_aux_coord(coord, 0) + save(cube, save_path) + + def setup_cache(self) -> None: + if not REUSE_DATA or not self.FILE_PATH.is_file(): + # See :mod:`benchmarks.generate_data` docstring for full explanation. + _ = run_function_elsewhere( + self._create_file, + str(self.FILE_PATH), + ) + + def time_many_var_load(self) -> None: + _ = load(str(self.FILE_PATH)) + + +class StructuredFF: + """ + Test structured loading of a large-ish fieldsfile. + + Structured load of the larger size should show benefit over standard load, + avoiding the cost of merging. + """ + + params = [[(2, 2, 2), (1280, 960, 5)], [False, True]] + param_names = ["xyz", "structured_loading"] + + def setup_cache(self) -> dict: + file_path_dict = {} + for xyz in self.params[0]: + x, y, z = xyz + file_path_dict[xyz] = create_um_files(x, y, z, 1, False, ["FF"]) + return file_path_dict + + def setup(self, file_path_dict, xyz, structured_load): + self.file_path = file_path_dict[xyz]["FF"] + self.structured_load = structured_load + + def load(self): + """Load the whole file (in fact there is only 1 cube).""" + + def _load(): + _ = load(self.file_path) + + if self.structured_load: + with structured_um_loading(): + _load() + else: + _load() + + def time_structured_load(self, _, __, ___): + self.load() diff --git a/noxfile.py b/noxfile.py index 6367b74aef..0600540c5b 100755 --- a/noxfile.py +++ b/noxfile.py @@ -289,7 +289,7 @@ def linkcheck(session: nox.sessions.Session): ) -@nox.session(python=PY_VER[-1], venv_backend="conda") +@nox.session(python=PY_VER, venv_backend="conda") @nox.parametrize( ["ci_mode"], [True, False], @@ -297,7 +297,7 @@ def linkcheck(session: nox.sessions.Session): ) def benchmarks(session: nox.sessions.Session, ci_mode: bool): """ - Perform esmf-regrid performance benchmarks (using Airspeed Velocity). + Perform Iris performance benchmarks (using Airspeed Velocity). Parameters ---------- @@ -315,6 +315,47 @@ def benchmarks(session: nox.sessions.Session, ci_mode: bool): """ session.install("asv", "nox") + + data_gen_var = "DATA_GEN_PYTHON" + if data_gen_var in os.environ: + print("Using existing data generation environment.") + else: + print("Setting up the data generation environment...") + # Get Nox to build an environment for the `tests` session, but don't + # run the session. Will re-use a cached environment if appropriate. + session.run_always( + "nox", + "--session=tests", + "--install-only", + f"--python={session.python}", + ) + # Find the environment built above, set it to be the data generation + # environment. + data_gen_python = next( + Path(".nox").rglob(f"tests*/bin/python{session.python}") + ).resolve() + session.env[data_gen_var] = data_gen_python + + mule_dir = data_gen_python.parents[1] / "resources" / "mule" + if not mule_dir.is_dir(): + print("Installing Mule into data generation environment...") + session.run_always( + "git", + "clone", + "https://github.com/metomi/mule.git", + str(mule_dir), + external=True, + ) + session.run_always( + str(data_gen_python), + "-m", + "pip", + "install", + str(mule_dir / "mule"), + external=True, + ) + + print("Running ASV...") session.cd("benchmarks") # Skip over setup questions for a new machine. session.run("asv", "machine", "--yes") From cbc31c7f010c1538b8260e431eb7265683cf11ad Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 14 Feb 2022 15:43:52 +0000 Subject: [PATCH 017/319] fix test (#4585) --- .../pp_load_rules/test__convert_time_coords.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py index d975884cb0..2aae32b1ae 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py @@ -13,8 +13,6 @@ # importing anything else. import iris.tests as tests # isort:skip -import unittest - from cf_units import CALENDAR_360_DAY, CALENDAR_GREGORIAN, Unit from cftime import datetime as nc_datetime import numpy as np @@ -733,7 +731,6 @@ def test_t1_list_t2_scalar(self): class TestArrayInputWithLBTIM_0_3_1(TestField): - @unittest.skip("#3508 investigate unit test failure") def test_t1_scalar_t2_list(self): lbtim = _lbtim(ib=3, ic=1) lbcode = _lbcode(1) @@ -756,9 +753,13 @@ def test_t1_scalar_t2_list(self): ) # Expected coords. + leap_year_adjust = np.array([0, 24, 24]) points = np.ones_like(years) * lbft bounds = np.array( - [lbft - ((years - 1970) * 365 * 24 + 2 * 24), points] + [ + lbft - ((years - 1970) * 365 * 24 + 2 * 24 + leap_year_adjust), + points, + ] ).transpose() fp_coord = AuxCoord( points, @@ -766,7 +767,7 @@ def test_t1_scalar_t2_list(self): units="hours", bounds=bounds, ) - points = (years - 1970) * 365 * 24 + 10 * 24 + 9 + points = (years - 1970) * 365 * 24 + 10 * 24 + 9 + leap_year_adjust bounds = np.array( [np.ones_like(points) * (8 * 24 + 9), points] ).transpose() From cf5413c1549b1614b345949e1e87ccda1b1846a6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 13:45:28 +0000 Subject: [PATCH 018/319] [pre-commit.ci] pre-commit autoupdate (#4587) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 21.12b0 → 22.1.0](https://github.com/psf/black/compare/21.12b0...22.1.0) - [github.com/asottile/blacken-docs: v1.12.0 → v1.12.1](https://github.com/asottile/blacken-docs/compare/v1.12.0...v1.12.1) - [github.com/aio-libs/sort-all: v1.1.0 → v1.2.0](https://github.com/aio-libs/sort-all/compare/v1.1.0...v1.2.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +-- .../benchmarks/generate_data/um_files.py | 4 +- benchmarks/benchmarks/plot.py | 2 +- .../meteorology/plot_wind_barbs.py | 4 +- .../meteorology/plot_wind_speed.py | 2 +- lib/iris/analysis/__init__.py | 2 +- lib/iris/analysis/_grid_angles.py | 2 +- lib/iris/analysis/_scipy_interpolate.py | 2 +- lib/iris/analysis/calculus.py | 12 ++---- lib/iris/analysis/cartography.py | 6 +-- lib/iris/analysis/maths.py | 2 +- lib/iris/analysis/stats.py | 4 +- lib/iris/fileformats/netcdf.py | 4 +- lib/iris/fileformats/pp.py | 4 +- lib/iris/tests/integration/test_netcdf.py | 6 +-- lib/iris/tests/test_basic_maths.py | 38 +++++++++---------- .../analysis/cartography/test_rotate_winds.py | 8 ++-- .../regrid/test_RectilinearRegridder.py | 2 +- .../test_add_categorised_coord.py | 2 +- ...__collapse_degenerate_points_and_bounds.py | 2 +- 20 files changed, 55 insertions(+), 59 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97dff666cf..ee036038e4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: no-commit-to-branch - repo: https://github.com/psf/black - rev: 21.12b0 + rev: 22.1.0 hooks: - id: black pass_filenames: false @@ -50,14 +50,14 @@ repos: args: [--filter-files] - repo: https://github.com/asottile/blacken-docs - rev: v1.12.0 + rev: v1.12.1 hooks: - id: blacken-docs types: [file, rst] additional_dependencies: [black==21.6b0] - repo: https://github.com/aio-libs/sort-all - rev: v1.1.0 + rev: v1.2.0 hooks: - id: sort-all types: [file, python] diff --git a/benchmarks/benchmarks/generate_data/um_files.py b/benchmarks/benchmarks/generate_data/um_files.py index 8792fcc48b..1037954f08 100644 --- a/benchmarks/benchmarks/generate_data/um_files.py +++ b/benchmarks/benchmarks/generate_data/um_files.py @@ -107,9 +107,9 @@ def add_field(level_: int, time_step_: int) -> None: six_rec = 20 / 3 three_rec = six_rec / 2 - new_field.blev = level_1 ** 2 * six_rec - six_rec + new_field.blev = level_1**2 * six_rec - six_rec new_field.brsvd1 = ( - level_1 ** 2 * six_rec + (six_rec * level_1) - three_rec + level_1**2 * six_rec + (six_rec * level_1) - three_rec ) brsvd2_simulated = np.linspace(0.995, 0, len_z) diff --git a/benchmarks/benchmarks/plot.py b/benchmarks/benchmarks/plot.py index 45905abd2f..24899776dc 100644 --- a/benchmarks/benchmarks/plot.py +++ b/benchmarks/benchmarks/plot.py @@ -22,7 +22,7 @@ def setup(self): # Should generate 10 distinct contours, regardless of dim size. dim_size = int(ARTIFICIAL_DIM_SIZE / 5) repeat_number = int(dim_size / 10) - repeat_range = range(int((dim_size ** 2) / repeat_number)) + repeat_range = range(int((dim_size**2) / repeat_number)) data = np.repeat(repeat_range, repeat_number) data = data.reshape((dim_size,) * 2) diff --git a/docs/gallery_code/meteorology/plot_wind_barbs.py b/docs/gallery_code/meteorology/plot_wind_barbs.py index c3c056eb4a..b09040c64e 100644 --- a/docs/gallery_code/meteorology/plot_wind_barbs.py +++ b/docs/gallery_code/meteorology/plot_wind_barbs.py @@ -30,7 +30,7 @@ def main(): # To illustrate the full range of barbs, scale the wind speed up to pretend # that a storm is passing over - magnitude = (uwind ** 2 + vwind ** 2) ** 0.5 + magnitude = (uwind**2 + vwind**2) ** 0.5 magnitude.convert_units("knot") max_speed = magnitude.collapsed( ("latitude", "longitude"), iris.analysis.MAX @@ -41,7 +41,7 @@ def main(): vwind = vwind / max_speed * max_desired # Create a cube containing the wind speed - windspeed = (uwind ** 2 + vwind ** 2) ** 0.5 + windspeed = (uwind**2 + vwind**2) ** 0.5 windspeed.rename("windspeed") windspeed.convert_units("knot") diff --git a/docs/gallery_code/meteorology/plot_wind_speed.py b/docs/gallery_code/meteorology/plot_wind_speed.py index fd03f54205..40d9d0da00 100644 --- a/docs/gallery_code/meteorology/plot_wind_speed.py +++ b/docs/gallery_code/meteorology/plot_wind_speed.py @@ -27,7 +27,7 @@ def main(): vwind = iris.load_cube(infile, "y_wind") # Create a cube containing the wind speed. - windspeed = (uwind ** 2 + vwind ** 2) ** 0.5 + windspeed = (uwind**2 + vwind**2) ** 0.5 windspeed.rename("windspeed") # Plot the wind speed as a contour plot. diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 465a521065..b1a9e1d259 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -1394,7 +1394,7 @@ def _lazy_rms(array, axis, **kwargs): # all. Thus trying to use this aggregator with weights will currently # raise an error in dask due to the unexpected keyword `weights`, # rather than silently returning the wrong answer. - return da.sqrt(da.mean(array ** 2, axis=axis, **kwargs)) + return da.sqrt(da.mean(array**2, axis=axis, **kwargs)) @_build_dask_mdtol_function diff --git a/lib/iris/analysis/_grid_angles.py b/lib/iris/analysis/_grid_angles.py index 127aec7c1e..0b52f54568 100644 --- a/lib/iris/analysis/_grid_angles.py +++ b/lib/iris/analysis/_grid_angles.py @@ -120,7 +120,7 @@ def _angle(p, q, r): mid_lons = np.deg2rad(q[0]) pr = _3d_xyz_from_latlon(r[0], r[1]) - _3d_xyz_from_latlon(p[0], p[1]) - pr_norm = np.sqrt(np.sum(pr ** 2, axis=0)) + pr_norm = np.sqrt(np.sum(pr**2, axis=0)) pr_top = pr[1] * np.cos(mid_lons) - pr[0] * np.sin(mid_lons) index = pr_norm == 0 diff --git a/lib/iris/analysis/_scipy_interpolate.py b/lib/iris/analysis/_scipy_interpolate.py index c6b33c56a4..fc64249729 100644 --- a/lib/iris/analysis/_scipy_interpolate.py +++ b/lib/iris/analysis/_scipy_interpolate.py @@ -229,7 +229,7 @@ def compute_interp_weights(self, xi, method=None): xi_shape, method, indices, norm_distances, out_of_bounds = prepared # Allocate arrays for describing the sparse matrix. - n_src_values_per_result_value = 2 ** ndim + n_src_values_per_result_value = 2**ndim n_result_values = len(indices[0]) n_non_zero = n_result_values * n_src_values_per_result_value weights = np.ones(n_non_zero, dtype=norm_distances[0].dtype) diff --git a/lib/iris/analysis/calculus.py b/lib/iris/analysis/calculus.py index 409782f256..4630f47967 100644 --- a/lib/iris/analysis/calculus.py +++ b/lib/iris/analysis/calculus.py @@ -629,14 +629,10 @@ def curl(i_cube, j_cube, k_cube=None): # (d/dtheta (i_cube * sin(lat)) - d_j_cube_dphi) # phi_cmpt = 1/r * ( d/dr (r * j_cube) - d_k_cube_dtheta) # theta_cmpt = 1/r * ( 1/cos(lat) * d_k_cube_dphi - d/dr (r * i_cube) - if ( - y_coord.name() - not in [ - "latitude", - "grid_latitude", - ] - or x_coord.name() not in ["longitude", "grid_longitude"] - ): + if y_coord.name() not in [ + "latitude", + "grid_latitude", + ] or x_coord.name() not in ["longitude", "grid_longitude"]: raise ValueError( "Expecting latitude as the y coord and " "longitude as the x coord for spherical curl." diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index 373487af53..f704468e33 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -335,7 +335,7 @@ def _quadrant_area(radian_lat_bounds, radian_lon_bounds, radius_of_earth): raise ValueError("Bounds must be [n,2] array") # fill in a new array of areas - radius_sqr = radius_of_earth ** 2 + radius_sqr = radius_of_earth**2 radian_lat_64 = radian_lat_bounds.astype(np.float64) radian_lon_64 = radian_lon_bounds.astype(np.float64) @@ -1010,8 +1010,8 @@ def _transform_distance_vectors_tolerance_mask( # Squared magnitudes should be equal to one within acceptable tolerance. # A value of atol=2e-3 is used, which corresponds to a change in magnitude # of approximately 0.1%. - sqmag_1_0 = u_one_t ** 2 + v_zero_t ** 2 - sqmag_0_1 = u_zero_t ** 2 + v_one_t ** 2 + sqmag_1_0 = u_one_t**2 + v_zero_t**2 + sqmag_0_1 = u_zero_t**2 + v_one_t**2 mask = np.logical_not( np.logical_and( np.isclose(sqmag_1_0, ones, atol=2e-3), diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index 107d964ed4..1cbc90cc60 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -540,7 +540,7 @@ def power(data, out=None): return _math_op_common( cube, power, - cube.units ** exponent, + cube.units**exponent, new_dtype=new_dtype, in_place=in_place, ) diff --git a/lib/iris/analysis/stats.py b/lib/iris/analysis/stats.py index 89dde1818b..711e3c5bfb 100644 --- a/lib/iris/analysis/stats.py +++ b/lib/iris/analysis/stats.py @@ -168,10 +168,10 @@ def _ones_like(cube): covar = (s1 * s2).collapsed( corr_coords, iris.analysis.SUM, weights=weights_1, mdtol=mdtol ) - var_1 = (s1 ** 2).collapsed( + var_1 = (s1**2).collapsed( corr_coords, iris.analysis.SUM, weights=weights_1 ) - var_2 = (s2 ** 2).collapsed( + var_2 = (s2**2).collapsed( corr_coords, iris.analysis.SUM, weights=weights_2 ) diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 100ab29daa..73a137b4af 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -2738,9 +2738,9 @@ def _create_cf_data_variable( cmin, cmax = _co_realise_lazy_arrays([cmin, cmax]) n = dtype.itemsize * 8 if masked: - scale_factor = (cmax - cmin) / (2 ** n - 2) + scale_factor = (cmax - cmin) / (2**n - 2) else: - scale_factor = (cmax - cmin) / (2 ** n - 1) + scale_factor = (cmax - cmin) / (2**n - 1) if dtype.kind == "u": add_offset = cmin elif dtype.kind == "i": diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 9f213ec4db..9bda98bf61 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -403,7 +403,7 @@ def _calculate_str_value_from_value(self): def _calculate_value_from_str_value(self): self._value = np.sum( - [10 ** i * val for i, val in enumerate(self._strvalue)] + [10**i * val for i, val in enumerate(self._strvalue)] ) def __len__(self): @@ -418,7 +418,7 @@ def __getitem__(self, key): # if the key returns a list of values, then combine them together # to an integer if isinstance(val, list): - val = sum([10 ** i * val for i, val in enumerate(val)]) + val = sum([10**i * val for i, val in enumerate(val)]) return val diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index f7aaa1d05c..2a45561e17 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -416,7 +416,7 @@ def setUp(self): levels.units = "centimeters" levels.positive = "down" levels.axis = "Z" - levels[:] = np.linspace(0, 10 ** 5, 3) + levels[:] = np.linspace(0, 10**5, 3) volcello.id = "volcello" volcello.out_name = "volcello" @@ -507,9 +507,9 @@ def _get_scale_factor_add_offset(cube, datatype): else: masked = False if masked: - scale_factor = (cmax - cmin) / (2 ** n - 2) + scale_factor = (cmax - cmin) / (2**n - 2) else: - scale_factor = (cmax - cmin) / (2 ** n - 1) + scale_factor = (cmax - cmin) / (2**n - 1) if dt.kind == "u": add_offset = cmin elif dt.kind == "i": diff --git a/lib/iris/tests/test_basic_maths.py b/lib/iris/tests/test_basic_maths.py index e753adbae8..24f2b89442 100644 --- a/lib/iris/tests/test_basic_maths.py +++ b/lib/iris/tests/test_basic_maths.py @@ -249,7 +249,7 @@ def test_apply_ufunc(self): np.square, a, new_name="squared temperature", - new_unit=a.units ** 2, + new_unit=a.units**2, in_place=False, ) self.assertCMLApproxData(a, ("analysis", "apply_ufunc_original.cml")) @@ -259,14 +259,14 @@ def test_apply_ufunc(self): np.square, a, new_name="squared temperature", - new_unit=a.units ** 2, + new_unit=a.units**2, in_place=True, ) self.assertCMLApproxData(b, ("analysis", "apply_ufunc.cml")) self.assertCMLApproxData(a, ("analysis", "apply_ufunc.cml")) def vec_mag(u, v): - return math.sqrt(u ** 2 + v ** 2) + return math.sqrt(u**2 + v**2) c = a.copy() + 2 @@ -295,7 +295,7 @@ def test_apply_ufunc_fail(self): def test_ifunc(self): a = self.cube - my_ifunc = iris.analysis.maths.IFunc(np.square, lambda a: a.units ** 2) + my_ifunc = iris.analysis.maths.IFunc(np.square, lambda a: a.units**2) b = my_ifunc(a, new_name="squared temperature", in_place=False) self.assertCMLApproxData(a, ("analysis", "apply_ifunc_original.cml")) @@ -307,7 +307,7 @@ def test_ifunc(self): self.assertCMLApproxData(a, ("analysis", "apply_ifunc.cml")) def vec_mag(u, v): - return math.sqrt(u ** 2 + v ** 2) + return math.sqrt(u**2 + v**2) c = a.copy() + 2 @@ -347,7 +347,7 @@ def test_ifunc_init_fail(self): def test_ifunc_call_fail(self): a = self.cube - my_ifunc = iris.analysis.maths.IFunc(np.square, lambda a: a.units ** 2) + my_ifunc = iris.analysis.maths.IFunc(np.square, lambda a: a.units**2) # should now NOT fail because giving 2 arguments to an ifunc that # expects only one will now ignore the surplus argument and raise @@ -367,7 +367,7 @@ def test_ifunc_call_fail(self): my_ifunc(a) my_ifunc = iris.analysis.maths.IFunc( - lambda a: (a, a ** 2.0), lambda cube: cf_units.Unit("1") + lambda a: (a, a**2.0), lambda cube: cf_units.Unit("1") ) # should fail because data function returns a tuple @@ -553,9 +553,9 @@ def test_square_root(self): a.data = abs(a.data) a.units **= 2 - e = a ** 0.5 + e = a**0.5 - self.assertArrayAllClose(e.data, a.data ** 0.5) + self.assertArrayAllClose(e.data, a.data**0.5) self.assertCML(e, ("analysis", "sqrt.cml"), checksum=False) self.assertRaises(ValueError, iris.analysis.maths.exponentiate, a, 0.3) @@ -585,26 +585,26 @@ def test_apply_ufunc(self): np.square, a, new_name="more_thingness", - new_unit=a.units ** 2, + new_unit=a.units**2, in_place=False, ) - ans = a.data ** 2 + ans = a.data**2 self.assertArrayEqual(b.data, ans) self.assertEqual(b.name(), "more_thingness") self.assertEqual(b.units, cf_units.Unit("m^2")) def vec_mag(u, v): - return math.sqrt(u ** 2 + v ** 2) + return math.sqrt(u**2 + v**2) c = a.copy() + 2 vec_mag_ufunc = np.frompyfunc(vec_mag, 2, 1) b = iris.analysis.maths.apply_ufunc(vec_mag_ufunc, a, c) - ans = a.data ** 2 + c.data ** 2 - b2 = b ** 2 + ans = a.data**2 + c.data**2 + b2 = b**2 self.assertArrayAlmostEqual(b2.data, ans) @@ -617,17 +617,17 @@ def test_ifunc(self): a = self.cube a.units = cf_units.Unit("meters") - my_ifunc = iris.analysis.maths.IFunc(np.square, lambda x: x.units ** 2) + my_ifunc = iris.analysis.maths.IFunc(np.square, lambda x: x.units**2) b = my_ifunc(a, new_name="more_thingness", in_place=False) - ans = a.data ** 2 + ans = a.data**2 self.assertArrayEqual(b.data, ans) self.assertEqual(b.name(), "more_thingness") self.assertEqual(b.units, cf_units.Unit("m^2")) def vec_mag(u, v): - return math.sqrt(u ** 2 + v ** 2) + return math.sqrt(u**2 + v**2) c = a.copy() + 2 @@ -637,12 +637,12 @@ def vec_mag(u, v): ) b = my_ifunc(a, c) - ans = (a.data ** 2 + c.data ** 2) ** 0.5 + ans = (a.data**2 + c.data**2) ** 0.5 self.assertArrayAlmostEqual(b.data, ans) def vec_mag_data_func(u_data, v_data): - return np.sqrt(u_data ** 2 + v_data ** 2) + return np.sqrt(u_data**2 + v_data**2) vec_mag_ifunc = iris.analysis.maths.IFunc( vec_mag_data_func, lambda a, b: (a + b).units diff --git a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py index 9e3af90603..eafaa20ec8 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py +++ b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py @@ -343,8 +343,8 @@ def test_orig_coords(self): def test_magnitude_preservation(self): u, v = self._uv_cubes_limited_extent() ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - orig_sq_mag = u.data ** 2 + v.data ** 2 - res_sq_mag = ut.data ** 2 + vt.data ** 2 + orig_sq_mag = u.data**2 + v.data**2 + res_sq_mag = ut.data**2 + vt.data**2 self.assertArrayAllClose(orig_sq_mag, res_sq_mag, rtol=5e-4) def test_data_values(self): @@ -437,9 +437,9 @@ def test_rotated_to_osgb(self): self.assertArrayEqual(expected_mask, vt.data.mask) # Check unmasked values have sufficiently small error in mag. - expected_mag = np.sqrt(u.data ** 2 + v.data ** 2) + expected_mag = np.sqrt(u.data**2 + v.data**2) # Use underlying data to ignore mask in calculation. - res_mag = np.sqrt(ut.data.data ** 2 + vt.data.data ** 2) + res_mag = np.sqrt(ut.data.data**2 + vt.data.data**2) # Calculate percentage error (note there are no zero magnitudes # so we can divide safely). anom = 100.0 * np.abs(res_mag - expected_mag) / expected_mag diff --git a/lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py b/lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py index f0dba83748..a018507fb3 100644 --- a/lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py +++ b/lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py @@ -33,7 +33,7 @@ def setUp(self): self.xs, self.ys = np.meshgrid(self.x.points, self.y.points) def transformation(x, y): - return x + y ** 2 + return x + y**2 # Construct a function which adds dimensions to the 2D data array # so that we can test higher dimensional functionality. diff --git a/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py b/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py index b7c59ff566..0c20f16f5a 100644 --- a/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py +++ b/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py @@ -36,7 +36,7 @@ def test_vectorise_call(self): # The reason we use numpy.vectorize is to support multi-dimensional # coordinate points. def fn(coord, v): - return v ** 2 + return v**2 with mock.patch( "numpy.vectorize", return_value=self.vectorised diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py index 0f2a8a2d4b..c9c4821e0a 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py @@ -65,7 +65,7 @@ def test_3d(self): def test_multiple_odd_dims(self): # Test to ensure multiple collapsed dimensions don't interfere. # make a 5-D array where dimensions 0, 2 and 3 are degenerate. - array = np.arange(3 ** 5).reshape([3] * 5) + array = np.arange(3**5).reshape([3] * 5) array[1:] = array[0:1] array[:, :, 1:] = array[:, :, 0:1] array[:, :, :, 1:] = array[:, :, :, 0:1] From 683ed1879b4d43219875cca67467c17fad82b3f4 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Tue, 15 Feb 2022 13:57:37 +0000 Subject: [PATCH 019/319] Finalise whatsnew and version string update (#4588) --- docs/src/whatsnew/3.2.rst | 6 +++--- lib/iris/__init__.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index 9aa6a78846..ef3764daa5 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -1,7 +1,7 @@ .. include:: ../common_links.inc -v3.2 (31 Jan 2022) [unreleased] -******************************* +v3.2 (15 Feb 2022) +****************** This document explains the changes made to Iris for this release (:doc:`View all changes `.) @@ -351,7 +351,7 @@ This document explains the changes made to Iris for this release #. `@lbdreyer`_ corrected the license PyPI classifier. (:pull:`4435`) -#. `@aaronspring `_ exchanged ``dask`` with +#. `@aaronspring`_ exchanged ``dask`` with ``dask-core`` in testing environments reducing the number of dependencies installed for testing. (:pull:`4434`) diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 95722c69cf..009a83aed5 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -108,7 +108,7 @@ def callback(cube, field, filename): # Iris revision. -__version__ = "3.2.0rc0" +__version__ = "3.2.0" # Restrict the names imported when using "from iris import *" __all__ = [ From 0365407396f7606b40487c84afe4ff3cc0b9cc45 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 15 Feb 2022 15:31:22 +0000 Subject: [PATCH 020/319] docs linkcheck skip (#4590) --- docs/src/conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/src/conf.py b/docs/src/conf.py index 19f22e808f..db2cdc3633 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -316,6 +316,7 @@ def _dotv(version): "https://software.ac.uk/how-cite-software", "http://www.esrl.noaa.gov/psd/data/gridded/conventions/cdc_netcdf_standard.shtml", "http://www.nationalarchives.gov.uk/doc/open-government-licence", + "https://www.metoffice.gov.uk/", ] # list of sources to exclude from the build. From f66353f611a1bfc7ee708da32ea330a100bc577f Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 16 Feb 2022 11:36:58 +0000 Subject: [PATCH 021/319] Add missing commit to v3.2.x and update version number (#4593) * fix trove classifier (#4324) * update version to 3.2. post release Co-authored-by: Bill Little --- lib/iris/__init__.py | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 009a83aed5..a28a7cd479 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -108,7 +108,7 @@ def callback(cube, field, filename): # Iris revision. -__version__ = "3.2.0" +__version__ = "3.2.0.post0" # Restrict the names imported when using "from iris import *" __all__ = [ diff --git a/setup.cfg b/setup.cfg index c2d31a5ddb..1aabe33d83 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ author = SciTools Developers author_email = scitools-iris-dev@googlegroups.com classifiers = - Development Status :: 5 Production/Stable + Development Status :: 5 - Production/Stable Intended Audience :: Science/Research License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Operating System :: MacOS From 2c29705d9e6286f75c802afa4a23f1c610189ca8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Feb 2022 13:50:30 +0000 Subject: [PATCH 022/319] Bump actions/script from 5.1.0 to 6 (#4586) Bumps [actions/script](https://github.com/actions/script) from 5.1.0 to 6. - [Release notes](https://github.com/actions/script/releases) - [Commits](https://github.com/actions/script/compare/v5.1.0...v6) --- updated-dependencies: - dependency-name: actions/script dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index b40c3ca446..28e01e4511 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -35,7 +35,7 @@ jobs: # the lockfile bot has made the head commit, abort the workflow. # This job can be manually overridden by running directly from the github actions panel # (known as a "workflow_dispatch") and setting the `clobber` input to "yes". - - uses: actions/script@v5.1.0 + - uses: actions/script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 36935a4775a4586cba65cddc43bc9c7fec69bfc4 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 16 Feb 2022 16:38:06 +0000 Subject: [PATCH 023/319] Yaml fixes + clarifications. (#4594) * Yaml fixes + clarifications. * Update .github/workflows/stale.yml Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> Co-authored-by: Bill Little Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 4 +++- .github/workflows/stale.yml | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 28e01e4511..ff2f6c4d75 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -22,7 +22,9 @@ on: default: "no" schedule: # Run once a week on a Saturday night - - cron: 1 0 * * 6 + # N.B. "should" be quoted, according to + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule + - cron: "1 0 * * 6" jobs: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index f9bb09ce46..a1bb0fca6c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,9 +1,13 @@ # See https://github.com/actions/stale name: Stale issues and pull-requests + on: schedule: - - cron: 0 0 * * * + # Run once a day + # N.B. "should" be quoted, according to + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule + - cron: "0 0 * * *" jobs: stale: @@ -59,11 +63,11 @@ jobs: stale-pr-label: Stale # Labels on issues exempted from stale. - exempt-issue-labels: | + exempt-issue-labels: "Status: Blocked,Status: Decision Required,Peloton 🚴‍♂️,Good First Issue" # Labels on prs exempted from stale. - exempt-pr-labels: | + exempt-pr-labels: "Status: Blocked,Status: Decision Required,Peloton 🚴‍♂️,Good First Issue" # Max number of operations per run. From 70583f96849789ad4af35caface1a4474466d983 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 16 Feb 2022 23:14:27 +0000 Subject: [PATCH 024/319] Overnight benchmarks (#4583) --- .github/workflows/benchmark.yml | 59 +++++++++--- benchmarks/README.md | 80 ++++++++++++++++ noxfile.py | 165 +++++++++++++++++++++++++------- 3 files changed, 253 insertions(+), 51 deletions(-) create mode 100644 benchmarks/README.md diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a8247a247b..d4c01af48a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -1,10 +1,11 @@ -# This is a basic workflow to help you get started with Actions +# Use ASV to check for performance regressions in the last 24 hours' commits. name: benchmark-check on: - # Triggers the workflow on push or pull request events but only for the master branch - pull_request: + schedule: + # Runs every day at 23:00. + - cron: "0 23 * * *" jobs: benchmark: @@ -23,12 +24,8 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v2 - - - name: Fetch the PR base branch too - run: | - git fetch --depth=1 origin ${{ github.event.pull_request.base.ref }} - git branch _base FETCH_HEAD - echo PR_BASE_SHA=$(git rev-parse _base) >> $GITHUB_ENV + with: + fetch-depth: 0 - name: Install Nox run: | @@ -65,11 +62,46 @@ jobs: run: | echo "OVERRIDE_TEST_DATA_REPOSITORY=${GITHUB_WORKSPACE}/${IRIS_TEST_DATA_PATH}/test_data" >> $GITHUB_ENV - - name: Run CI benchmarks + - name: Run overnight benchmarks + run: | + first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1) + if [ "$first_commit" != "" ] + then + nox --session="benchmarks(overnight)" -- $first_commit + fi + + - name: Create issues for performance shifts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - mkdir --parents benchmarks/.asv - set -o pipefail - nox --session="benchmarks(ci compare)" | tee benchmarks/.asv/ci_compare.txt + if [ -d benchmarks/.asv/performance-shifts ] + then + cd benchmarks/.asv/performance-shifts + for commit_file in * + do + pr_number=$(git log "$commit_file"^! --oneline | grep -o "#[0-9]*" | tail -1 | cut -c 2-) + assignee=$(gh pr view $pr_number --json author -q '.["author"]["login"]' --repo $GITHUB_REPOSITORY) + title="Performance Shift(s): \`$commit_file\`" + body=" + Benchmark comparison has identified performance shifts at commit \ + $commit_file (#$pr_number). Please review the report below and \ + take corrective/congratulatory action as appropriate \ + :slightly_smiling_face: + +
+ Performance shift report + + \`\`\` + $(cat $commit_file) + \`\`\` + +
+ + Generated by GHA run [\`${{github.run_id}}\`](https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}) + " + gh issue create --title "$title" --body "$body" --assignee $assignee --label "Bot" --label "Type: Performance" --repo $GITHUB_REPOSITORY + done + fi - name: Archive asv results if: ${{ always() }} @@ -78,4 +110,3 @@ jobs: name: asv-report path: | benchmarks/.asv/results - benchmarks/.asv/ci_compare.txt diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000000..baa1afe700 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,80 @@ +# Iris Performance Benchmarking + +Iris uses an [Airspeed Velocity](https://github.com/airspeed-velocity/asv) +(ASV) setup to benchmark performance. This is primarily designed to check for +performance shifts between commits using statistical analysis, but can also +be easily repurposed for manual comparative and scalability analyses. + +The benchmarks are automatically run overnight +[by a GitHub Action](../.github/workflows/benchmark.yml), with any notable +shifts in performance being flagged in a new GitHub issue. + +## Running benchmarks + +`asv ...` commands must be run from this directory. You will need to have ASV +installed, as well as Nox (see +[Benchmark environments](#benchmark-environments)). + +[Iris' noxfile](../noxfile.py) includes a `benchmarks` session that provides +conveniences for setting up before benchmarking, and can also replicate the +automated overnight run locally. See the session docstring for detail. + +### Environment variables + +* ``DATA_GEN_PYTHON`` - required - path to a Python executable that can be +used to generate benchmark test objects/files; see +[Data generation](#data-generation). The Nox session sets this automatically, +but will defer to any value already set in the shell. +* ``BENCHMARK_DATA`` - optional - path to a directory for benchmark synthetic +test data, which the benchmark scripts will create if it doesn't already +exist. Defaults to ``/benchmarks/.data/`` if not set. + +## Writing benchmarks + +[See the ASV docs](https://asv.readthedocs.io/) for full detail. + +### Data generation +**Important:** be sure not to use the benchmarking environment to generate any +test objects/files, as this environment changes with each commit being +benchmarked, creating inconsistent benchmark 'conditions'. The +[generate_data](./benchmarks/generate_data/__init__.py) module offers a +solution; read more detail there. + +### ASV re-run behaviour + +Note that ASV re-runs a benchmark multiple times between its `setup()` routine. +This is a problem for benchmarking certain Iris operations such as data +realisation, since the data will no longer be lazy after the first run. +Consider writing extra steps to restore objects' original state _within_ the +benchmark itself. + +If adding steps to the benchmark will skew the result too much then re-running +can be disabled by setting an attribute on the benchmark: `number = 1`. To +maintain result accuracy this should be accompanied by increasing the number of +repeats _between_ `setup()` calls using the `repeat` attribute. +`warmup_time = 0` is also advisable since ASV performs independent re-runs to +estimate run-time, and these will still be subject to the original problem. + +### Scaling / non-Scaling Performance Differences + +When comparing performance between commits/file-type/whatever it can be helpful +to know if the differences exist in scaling or non-scaling parts of the Iris +functionality in question. This can be done using a size parameter, setting +one value to be as small as possible (e.g. a scalar `Cube`), and the other to +be significantly larger (e.g. a 1000x1000 `Cube`). Performance differences +might only be seen for the larger value, or the smaller, or both, getting you +closer to the root cause. + +## Benchmark environments + +We have disabled ASV's standard environment management, instead using an +environment built using the same Nox scripts as Iris' test environments. This +is done using ASV's plugin architecture - see +[asv_delegated_conda.py](asv_delegated_conda.py) and the extra config items in +[asv.conf.json](asv.conf.json). + +(ASV is written to control the environment(s) that benchmarks are run in - +minimising external factors and also allowing it to compare between a matrix +of dependencies (each in a separate environment). We have chosen to sacrifice +these features in favour of testing each commit with its intended dependencies, +controlled by Nox + lock-files). diff --git a/noxfile.py b/noxfile.py index 0600540c5b..e4d91c6bab 100755 --- a/noxfile.py +++ b/noxfile.py @@ -8,6 +8,8 @@ import hashlib import os from pathlib import Path +from tempfile import NamedTemporaryFile +from typing import Literal import nox from nox.logger import logger @@ -289,31 +291,60 @@ def linkcheck(session: nox.sessions.Session): ) -@nox.session(python=PY_VER, venv_backend="conda") +@nox.session @nox.parametrize( - ["ci_mode"], - [True, False], - ids=["ci compare", "full"], + "run_type", + ["overnight", "branch", "custom"], + ids=["overnight", "branch", "custom"], ) -def benchmarks(session: nox.sessions.Session, ci_mode: bool): +def benchmarks( + session: nox.sessions.Session, + run_type: Literal["overnight", "branch", "custom"], +): """ Perform Iris performance benchmarks (using Airspeed Velocity). + All run types require a single Nox positional argument (e.g. + ``nox --session="foo" -- my_pos_arg``) - detailed in the parameters + section - and can optionally accept a series of further arguments that will + be added to session's ASV command. + Parameters ---------- session: object A `nox.sessions.Session` object. - ci_mode: bool - Run a cut-down selection of benchmarks, comparing the current commit to - the last commit for performance regressions. - - Notes - ----- - ASV is set up to use ``nox --session=tests --install-only`` to prepare - the benchmarking environment. This session environment must use a Python - version that is also available for ``--session=tests``. + run_type: {"overnight", "branch", "custom"} + * ``overnight``: benchmarks all commits between the input **first + commit** to ``HEAD``, comparing each to its parent for performance + shifts. If a commit causes shifts, the output is saved to a file: + ``.asv/performance-shifts/``. Designed for checking the + previous 24 hours' commits, typically in a scheduled script. + * ``branch``: Performs the same operations as ``overnight``, but always + on two commits only - ``HEAD``, and ``HEAD``'s merge-base with the + input **base branch**. Output from this run is never saved to a file. + Designed for testing if the active branch's changes cause performance + shifts - anticipating what would be caught by ``overnight`` once + merged. + **For maximum accuracy, avoid using the machine that is running this + session. Run time could be >1 hour for the full benchmark suite.** + * ``custom``: run ASV with the input **ASV sub-command**, without any + preset arguments - must all be supplied by the user. So just like + running ASV manually, with the convenience of re-using the session's + scripted setup steps. + + Examples + -------- + * ``nox --session="benchmarks(overnight)" -- a1b23d4`` + * ``nox --session="benchmarks(branch)" -- upstream/main`` + * ``nox --session="benchmarks(branch)" -- upstream/mesh-data-model`` + * ``nox --session="benchmarks(branch)" -- upstream/main --bench=regridding`` + * ``nox --session="benchmarks(custom)" -- continuous a1b23d4 HEAD --quick`` """ + # The threshold beyond which shifts are 'notable'. See `asv compare`` docs + # for more. + COMPARE_FACTOR = 1.2 + session.install("asv", "nox") data_gen_var = "DATA_GEN_PYTHON" @@ -327,12 +358,12 @@ def benchmarks(session: nox.sessions.Session, ci_mode: bool): "nox", "--session=tests", "--install-only", - f"--python={session.python}", + f"--python={_PY_VERSION_LATEST}", ) # Find the environment built above, set it to be the data generation # environment. data_gen_python = next( - Path(".nox").rglob(f"tests*/bin/python{session.python}") + Path(".nox").rglob(f"tests*/bin/python{_PY_VERSION_LATEST}") ).resolve() session.env[data_gen_var] = data_gen_python @@ -360,25 +391,85 @@ def benchmarks(session: nox.sessions.Session, ci_mode: bool): # Skip over setup questions for a new machine. session.run("asv", "machine", "--yes") - def asv_exec(*sub_args: str) -> None: - run_args = ["asv", *sub_args] - session.run(*run_args) - - if ci_mode: - # If on a PR: compare to the base (target) branch. - # Else: compare to previous commit. - previous_commit = os.environ.get("PR_BASE_SHA", "HEAD^1") - try: - asv_exec( - "continuous", - "--factor=1.2", - previous_commit, - "HEAD", - "--attribute", - "rounds=4", - ) - finally: - asv_exec("compare", previous_commit, "HEAD") + # All run types require one Nox posarg. + run_type_arg = { + "overnight": "first commit", + "branch": "base branch", + "custom": "ASV sub-command", + } + if run_type not in run_type_arg.keys(): + message = f"Unsupported run-type: {run_type}" + raise NotImplementedError(message) + if not session.posargs: + message = ( + f"Missing mandatory first Nox session posarg: " + f"{run_type_arg[run_type]}" + ) + raise ValueError(message) + first_arg = session.posargs[0] + # Optional extra arguments to be passed down to ASV. + asv_args = session.posargs[1:] + + def asv_compare(*commits): + """Run through a list of commits comparing each one to the next.""" + commits = [commit[:8] for commit in commits] + shifts_dir = Path(".asv") / "performance-shifts" + for i in range(len(commits) - 1): + before = commits[i] + after = commits[i + 1] + asv_command_ = f"asv compare {before} {after} --factor={COMPARE_FACTOR} --split" + session.run(*asv_command_.split(" ")) + + if run_type == "overnight": + # Record performance shifts. + # Run the command again but limited to only showing performance + # shifts. + shifts = session.run( + *asv_command_.split(" "), "--only-changed", silent=True + ) + if shifts: + # Write the shifts report to a file. + # Dir is used by .github/workflows/benchmarks.yml, + # but not cached - intended to be discarded after run. + shifts_dir.mkdir(exist_ok=True, parents=True) + shifts_path = shifts_dir / after + with shifts_path.open("w") as shifts_file: + shifts_file.write(shifts) + + # Common ASV arguments used for both `overnight` and `bench` run_types. + asv_harness = "asv run {posargs} --attribute rounds=4 --interleave-rounds --strict --show-stderr" + + if run_type == "overnight": + first_commit = first_arg + commit_range = f"{first_commit}^^.." + asv_command = asv_harness.format(posargs=commit_range) + session.run(*asv_command.split(" "), *asv_args) + + # git rev-list --first-parent is the command ASV uses. + git_command = f"git rev-list --first-parent {commit_range}" + commit_string = session.run( + *git_command.split(" "), silent=True, external=True + ) + commit_list = commit_string.rstrip().split("\n") + asv_compare(*reversed(commit_list)) + + elif run_type == "branch": + base_branch = first_arg + git_command = f"git merge-base HEAD {base_branch}" + merge_base = session.run( + *git_command.split(" "), silent=True, external=True + )[:8] + + with NamedTemporaryFile("w") as hashfile: + hashfile.writelines([merge_base, "\n", "HEAD"]) + hashfile.flush() + commit_range = f"HASHFILE:{hashfile.name}" + asv_command = asv_harness.format(posargs=commit_range) + session.run(*asv_command.split(" "), *asv_args) + + asv_compare(merge_base, "HEAD") + else: - # f5ceb808 = first commit supporting nox --install-only . - asv_exec("run", "f5ceb808..HEAD") + asv_subcommand = first_arg + assert run_type == "custom" + session.run("asv", asv_subcommand, *asv_args) From 8f3e3b90dfb68945c1e32e8c3c4d3c6d11c6a7a0 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 17 Feb 2022 07:29:35 +0000 Subject: [PATCH 025/319] Utility class in netcdf loader should not be public. (#4592) * Utility class in netcdf loader should not be public. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Rename container for better clarity. Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../fileformats/_nc_load_rules/actions.py | 4 +-- lib/iris/fileformats/netcdf.py | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/iris/fileformats/_nc_load_rules/actions.py b/lib/iris/fileformats/_nc_load_rules/actions.py index d286abbf3d..4c5184deb1 100644 --- a/lib/iris/fileformats/_nc_load_rules/actions.py +++ b/lib/iris/fileformats/_nc_load_rules/actions.py @@ -18,7 +18,7 @@ 3) Iris-specific info is (still) stored in additional properties created on the engine object : - engine.cf_var, .cube, .cube_parts, .requires, .rule_triggered, .filename + engine.cf_var, .cube, .cube_parts, .requires, .rules_triggered, .filename Our "rules" are just action routines. The top-level 'run_actions' routine decides which actions to call, based on the @@ -78,7 +78,7 @@ def inner(engine, *args, **kwargs): # but also may vary depending on whether it successfully # triggered, and if so what it matched. rule_name = _default_rulenamesfunc(func.__name__) - engine.rule_triggered.add(rule_name) + engine.rules_triggered.add(rule_name) func._rulenames_func = _default_rulenamesfunc return inner diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 8eb2b7d830..4526963972 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -498,7 +498,7 @@ def _actions_activation_stats(engine, cf_name): print("Rules Triggered:") - for rule in sorted(list(engine.rule_triggered)): + for rule in sorted(list(engine.rules_triggered)): print("\t%s" % rule) print("Case Specific Facts:") @@ -570,13 +570,21 @@ def _get_cf_var_data(cf_var, filename): return as_lazy_data(proxy, chunks=chunks) -class OrderedAddableList(list): - # Used purely in actions debugging, to accumulate a record of which actions - # were activated. - # It replaces a set, so as to record the ordering of operations, with - # possible repeats, and it also numbers the entries. - # Actions routines invoke the 'add' method, which thus effectively converts - # a set.add into a list.append. +class _OrderedAddableList(list): + """ + A custom container object for actions recording. + + Used purely in actions debugging, to accumulate a record of which actions + were activated. + + It replaces a set, so as to preserve the ordering of operations, with + possible repeats, and it also numbers the entries. + + The actions routines invoke an 'add' method, so this effectively replaces + a set.add with a list.append. + + """ + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._n_add = 0 @@ -602,7 +610,7 @@ def _load_cube(engine, cf, cf_var, filename): engine.cube = cube engine.cube_parts = {} engine.requires = {} - engine.rule_triggered = OrderedAddableList() + engine.rules_triggered = _OrderedAddableList() engine.filename = filename # Assert all the case-specific facts. From ebc2039c8d1990d5230e2b105b15526ca08383fa Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 24 Feb 2022 11:58:11 +0000 Subject: [PATCH 026/319] Stop using nc_time_axis.CalendarDateTime (#4584) * stop using CalendarDateTime * update dependencies * stronger tests for _fixup_dates * add whatsnew --- docs/src/whatsnew/dev.rst | 4 +++- lib/iris/plot.py | 8 +++----- lib/iris/tests/integration/plot/test_netcdftime.py | 9 ++------- lib/iris/tests/unit/plot/test__fixup_dates.py | 10 ++++------ requirements/ci/py38.yml | 2 +- setup.cfg | 2 +- 6 files changed, 14 insertions(+), 21 deletions(-) diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 27ed876a20..857264f43f 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -61,7 +61,9 @@ This document explains the changes made to Iris for this release 🔗 Dependencies =============== -#. N/A +#. `@rcomer`_ introduced the ``nc-time-axis >=1.4`` minimum pin, reflecting that + we no longer use the deprecated :class:`nc_time_axis.CalendarDateTime` + when plotting against time coordinates. (:pull:`4584`) 📚 Documentation diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 0e9645c783..3cd54ef08f 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -591,7 +591,7 @@ def _fixup_dates(coord, values): r = [datetime.datetime(*date) for date in dates] else: try: - import nc_time_axis + import nc_time_axis # noqa: F401 except ImportError: msg = ( "Cannot plot against time in a non-gregorian " @@ -603,12 +603,10 @@ def _fixup_dates(coord, values): raise IrisError(msg) r = [ - nc_time_axis.CalendarDateTime( - cftime.datetime(*date, calendar=coord.units.calendar), - coord.units.calendar, - ) + cftime.datetime(*date, calendar=coord.units.calendar) for date in dates ] + values = np.empty(len(r), dtype=object) values[:] = r return values diff --git a/lib/iris/tests/integration/plot/test_netcdftime.py b/lib/iris/tests/integration/plot/test_netcdftime.py index 340f37dda7..9f0baeda35 100644 --- a/lib/iris/tests/integration/plot/test_netcdftime.py +++ b/lib/iris/tests/integration/plot/test_netcdftime.py @@ -18,10 +18,6 @@ from iris.coords import AuxCoord -if tests.NC_TIME_AXIS_AVAILABLE: - from nc_time_axis import CalendarDateTime - - # Run tests in no graphics mode if matplotlib is not available. if tests.MPL_AVAILABLE: import iris.plot as iplt @@ -48,9 +44,8 @@ def test_360_day_calendar(self): ) for atime in times ] - expected_ydata = np.array( - [CalendarDateTime(time, calendar) for time in times] - ) + + expected_ydata = times (line1,) = iplt.plot(time_coord) result_ydata = line1.get_ydata() self.assertArrayEqual(expected_ydata, result_ydata) diff --git a/lib/iris/tests/unit/plot/test__fixup_dates.py b/lib/iris/tests/unit/plot/test__fixup_dates.py index 157780dcae..1ad5c87691 100644 --- a/lib/iris/tests/unit/plot/test__fixup_dates.py +++ b/lib/iris/tests/unit/plot/test__fixup_dates.py @@ -23,6 +23,7 @@ def test_gregorian_calendar(self): unit = Unit("hours since 2000-04-13 00:00:00", calendar="gregorian") coord = AuxCoord([1, 3, 6], "time", units=unit) result = _fixup_dates(coord, coord.points) + self.assertIsInstance(result[0], datetime.datetime) expected = [ datetime.datetime(2000, 4, 13, 1), datetime.datetime(2000, 4, 13, 3), @@ -34,6 +35,7 @@ def test_gregorian_calendar_sub_second(self): unit = Unit("seconds since 2000-04-13 00:00:00", calendar="gregorian") coord = AuxCoord([1, 1.25, 1.5], "time", units=unit) result = _fixup_dates(coord, coord.points) + self.assertIsInstance(result[0], datetime.datetime) expected = [ datetime.datetime(2000, 4, 13, 0, 0, 1), datetime.datetime(2000, 4, 13, 0, 0, 1), @@ -52,9 +54,7 @@ def test_360_day_calendar(self): cftime.datetime(2000, 2, 29, calendar=calendar), cftime.datetime(2000, 2, 30, calendar=calendar), ] - self.assertArrayEqual( - [cdt.datetime for cdt in result], expected_datetimes - ) + self.assertArrayEqual(result, expected_datetimes) @tests.skip_nc_time_axis def test_365_day_calendar(self): @@ -67,9 +67,7 @@ def test_365_day_calendar(self): cftime.datetime(2000, 2, 25, 1, 0, calendar=calendar), cftime.datetime(2000, 2, 25, 2, 30, calendar=calendar), ] - self.assertArrayEqual( - [cdt.datetime for cdt in result], expected_datetimes - ) + self.assertArrayEqual(result, expected_datetimes) @tests.skip_nc_time_axis def test_360_day_calendar_attribute(self): diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index d3d7f9d0c2..ef095815c9 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -25,7 +25,7 @@ dependencies: - graphviz - iris-sample-data >=2.4.0 - mo_pack - - nc-time-axis >=1.3 + - nc-time-axis >=1.4 - pandas - pip - python-stratify diff --git a/setup.cfg b/setup.cfg index 1aabe33d83..ecdcad85b2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -81,7 +81,7 @@ test = requests all = mo_pack - nc-time-axis>=1.3 + nc-time-axis>=1.4 pandas stratify %(docs)s From 68ce060c2538a7041ab7c932aaf8a6ce2793168f Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 25 Feb 2022 16:52:31 +0000 Subject: [PATCH 027/319] Update 3.2.rst whats new with section for bug fixes (#4604) --- docs/src/whatsnew/3.2.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index ef3764daa5..fac755ad72 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -25,6 +25,26 @@ This document explains the changes made to Iris for this release any issues or feature requests for improving Iris. Enjoy! +v3.2.1 |build_date| [unreleased] +******************************** + +.. dropdown:: :opticon:`alert` v3.2.1 Patches + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + + The patches in this release of Iris include: + + 🐛 **Bugs Fixed** + + #. N/A + + 💼 **Internal** + + #. N/A + + 📢 Announcements ================ From 2314e6b565c9fe147113fc50a0c44a1492bf95fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 10:55:00 +0000 Subject: [PATCH 028/319] Bump peter-evans/create-pull-request from 3.12.1 to 3.13.0 (#4607) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3.12.1 to 3.13.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/f22a7da129c901513876a2380e2dae9f8e145330...89265e8d24a5dea438a2577fdc409a11e9f855ca) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index ff2f6c4d75..5a06c97189 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -111,7 +111,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@f22a7da129c901513876a2380e2dae9f8e145330 + uses: peter-evans/create-pull-request@89265e8d24a5dea438a2577fdc409a11e9f855ca with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From c2e75572facc50833eb69e0f6559aaeea1fad5c1 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Tue, 1 Mar 2022 09:41:33 +0000 Subject: [PATCH 029/319] Support false-easting and false-northing when loading Mercator-projected data (#4524) * Add extra pieces to get false easting and northing * tests * Add extra test file ref and cml * lib/iris/tests/results/netcdf/netcdf_merc_false.cml * Update cml for new test data * Bump test data version for cirrus * What's new --- .cirrus.yml | 2 +- docs/src/whatsnew/dev.rst | 3 +- lib/iris/coord_systems.py | 20 +++++- .../fileformats/_nc_load_rules/helpers.py | 38 +++------- lib/iris/fileformats/netcdf.py | 6 +- .../tests/results/coord_systems/Mercator.xml | 2 +- lib/iris/tests/results/netcdf/netcdf_merc.cml | 8 +-- .../results/netcdf/netcdf_merc_false.cml | 33 +++++++++ lib/iris/tests/test_netcdf.py | 10 +++ .../tests/unit/coord_systems/test_Mercator.py | 29 +++++++- .../test_has_supported_mercator_parameters.py | 71 +++++-------------- 11 files changed, 127 insertions(+), 95 deletions(-) create mode 100644 lib/iris/tests/results/netcdf/netcdf_merc_false.cml diff --git a/.cirrus.yml b/.cirrus.yml index 92b8d788e6..c9c1d71859 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -38,7 +38,7 @@ env: # Conda packages to be installed. CONDA_CACHE_PACKAGES: "nox pip" # Git commit hash for iris test data. - IRIS_TEST_DATA_VERSION: "2.5" + IRIS_TEST_DATA_VERSION: "2.7" # Base directory for the iris-test-data. IRIS_TEST_DATA_DIR: ${HOME}/iris-test-data diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 857264f43f..5952dc45b0 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -31,7 +31,8 @@ This document explains the changes made to Iris for this release ✨ Features =========== -#. N/A +#. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to + :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`) 🐛 Bugs Fixed diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 2f875bb159..311ed35f44 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -1083,6 +1083,8 @@ def __init__( longitude_of_projection_origin=None, ellipsoid=None, standard_parallel=None, + false_easting=None, + false_northing=None, ): """ Constructs a Mercator coord system. @@ -1098,6 +1100,12 @@ def __init__( * standard_parallel: The latitude where the scale is 1. Defaults to 0.0 . + * false_easting: + X offset from the planar origin in metres. Defaults to 0.0. + + * false_northing: + Y offset from the planar origin in metres. Defaults to 0.0. + """ #: True longitude of planar origin in degrees. self.longitude_of_projection_origin = _arg_default( @@ -1110,12 +1118,20 @@ def __init__( #: The latitude where the scale is 1. self.standard_parallel = _arg_default(standard_parallel, 0) + #: X offset from the planar origin in metres. + self.false_easting = _arg_default(false_easting, 0) + + #: Y offset from the planar origin in metres. + self.false_northing = _arg_default(false_northing, 0) + def __repr__(self): res = ( "Mercator(longitude_of_projection_origin=" "{self.longitude_of_projection_origin!r}, " "ellipsoid={self.ellipsoid!r}, " - "standard_parallel={self.standard_parallel!r})" + "standard_parallel={self.standard_parallel!r}, " + "false_easting={self.false_easting!r}, " + "false_northing={self.false_northing!r})" ) return res.format(self=self) @@ -1126,6 +1142,8 @@ def as_cartopy_crs(self): central_longitude=self.longitude_of_projection_origin, globe=globe, latitude_true_scale=self.standard_parallel, + false_easting=self.false_easting, + false_northing=self.false_northing, ) def as_cartopy_projection(self): diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index a5b507d583..198daeceea 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -440,10 +440,13 @@ def build_mercator_coordinate_system(engine, cf_grid_var): longitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LON_OF_PROJ_ORIGIN, None ) + standard_parallel = getattr( + cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None + ) + false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None) + false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None) # Iris currently only supports Mercator projections with specific - # values for false_easting, false_northing, - # scale_factor_at_projection_origin and standard_parallel. These are - # checked elsewhere. + # scale_factor_at_projection_origin. This is checked elsewhere. ellipsoid = None if ( @@ -454,7 +457,11 @@ def build_mercator_coordinate_system(engine, cf_grid_var): ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) cs = iris.coord_systems.Mercator( - longitude_of_projection_origin, ellipsoid=ellipsoid + longitude_of_projection_origin, + ellipsoid=ellipsoid, + standard_parallel=standard_parallel, + false_easting=false_easting, + false_northing=false_northing, ) return cs @@ -1244,27 +1251,10 @@ def has_supported_mercator_parameters(engine, cf_name): is_valid = True cf_grid_var = engine.cf_var.cf_group[cf_name] - false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None) - false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None) scale_factor_at_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None ) - standard_parallel = getattr( - cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None - ) - if false_easting is not None and false_easting != 0: - warnings.warn( - "False eastings other than 0.0 not yet supported " - "for Mercator projections" - ) - is_valid = False - if false_northing is not None and false_northing != 0: - warnings.warn( - "False northings other than 0.0 not yet supported " - "for Mercator projections" - ) - is_valid = False if ( scale_factor_at_projection_origin is not None and scale_factor_at_projection_origin != 1 @@ -1274,12 +1264,6 @@ def has_supported_mercator_parameters(engine, cf_name): "Mercator projections" ) is_valid = False - if standard_parallel is not None and standard_parallel != 0: - warnings.warn( - "Standard parallels other than 0.0 not yet " - "supported for Mercator projections" - ) - is_valid = False return is_valid diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 4526963972..80f213dbc2 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -2561,10 +2561,8 @@ def add_ellipsoid(ellipsoid): cf_var_grid.longitude_of_projection_origin = ( cs.longitude_of_projection_origin ) - # The Mercator class has implicit defaults for certain - # parameters - cf_var_grid.false_easting = 0.0 - cf_var_grid.false_northing = 0.0 + cf_var_grid.false_easting = cs.false_easting + cf_var_grid.false_northing = cs.false_northing cf_var_grid.scale_factor_at_projection_origin = 1.0 # lcc diff --git a/lib/iris/tests/results/coord_systems/Mercator.xml b/lib/iris/tests/results/coord_systems/Mercator.xml index e8036ef824..db3ccffec7 100644 --- a/lib/iris/tests/results/coord_systems/Mercator.xml +++ b/lib/iris/tests/results/coord_systems/Mercator.xml @@ -1,2 +1,2 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_merc.cml b/lib/iris/tests/results/netcdf/netcdf_merc.cml index 02fc4e7c34..5e17400158 100644 --- a/lib/iris/tests/results/netcdf/netcdf_merc.cml +++ b/lib/iris/tests/results/netcdf/netcdf_merc.cml @@ -53,15 +53,15 @@ 45.5158, 45.9993]]" shape="(192, 192)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="lon"/> - - + - - + diff --git a/lib/iris/tests/results/netcdf/netcdf_merc_false.cml b/lib/iris/tests/results/netcdf/netcdf_merc_false.cml new file mode 100644 index 0000000000..d916f5f753 --- /dev/null +++ b/lib/iris/tests/results/netcdf/netcdf_merc_false.cml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py index 2c22c6d088..8cdbe27257 100644 --- a/lib/iris/tests/test_netcdf.py +++ b/lib/iris/tests/test_netcdf.py @@ -218,6 +218,16 @@ def test_load_merc_grid(self): ) self.assertCML(cube, ("netcdf", "netcdf_merc.cml")) + def test_load_merc_false_en_grid(self): + # Test loading a single CF-netCDF file with a Mercator grid_mapping that + # includes false easting and northing + cube = iris.load_cube( + tests.get_data_path( + ("NetCDF", "mercator", "false_east_north_merc.nc") + ) + ) + self.assertCML(cube, ("netcdf", "netcdf_merc_false.cml")) + def test_load_stereographic_grid(self): # Test loading a single CF-netCDF file with a stereographic # grid_mapping. diff --git a/lib/iris/tests/unit/coord_systems/test_Mercator.py b/lib/iris/tests/unit/coord_systems/test_Mercator.py index 33efaef9da..8a37a8fcc5 100644 --- a/lib/iris/tests/unit/coord_systems/test_Mercator.py +++ b/lib/iris/tests/unit/coord_systems/test_Mercator.py @@ -29,7 +29,8 @@ def test_repr(self): "Mercator(longitude_of_projection_origin=90.0, " "ellipsoid=GeogCS(semi_major_axis=6377563.396, " "semi_minor_axis=6356256.909), " - "standard_parallel=0.0)" + "standard_parallel=0.0, " + "false_easting=0.0, false_northing=0.0)" ) self.assertEqual(expected, repr(self.tm)) @@ -38,16 +39,23 @@ class Test_init_defaults(tests.IrisTest): def test_set_optional_args(self): # Check that setting the optional (non-ellipse) args works. crs = Mercator( - longitude_of_projection_origin=27, standard_parallel=157.4 + longitude_of_projection_origin=27, + standard_parallel=157.4, + false_easting=13, + false_northing=12, ) self.assertEqualAndKind(crs.longitude_of_projection_origin, 27.0) self.assertEqualAndKind(crs.standard_parallel, 157.4) + self.assertEqualAndKind(crs.false_easting, 13.0) + self.assertEqualAndKind(crs.false_northing, 12.0) def _check_crs_defaults(self, crs): # Check for property defaults when no kwargs options were set. # NOTE: except ellipsoid, which is done elsewhere. self.assertEqualAndKind(crs.longitude_of_projection_origin, 0.0) self.assertEqualAndKind(crs.standard_parallel, 0.0) + self.assertEqualAndKind(crs.false_easting, 0.0) + self.assertEqualAndKind(crs.false_northing, 0.0) def test_no_optional_args(self): # Check expected defaults with no optional args. @@ -57,7 +65,10 @@ def test_no_optional_args(self): def test_optional_args_None(self): # Check expected defaults with optional args=None. crs = Mercator( - longitude_of_projection_origin=None, standard_parallel=None + longitude_of_projection_origin=None, + standard_parallel=None, + false_easting=None, + false_northing=None, ) self._check_crs_defaults(crs) @@ -77,6 +88,8 @@ def test_extra_kwargs(self): # converted to a cartopy CRS. longitude_of_projection_origin = 90.0 true_scale_lat = 14.0 + false_easting = 13 + false_northing = 12 ellipsoid = GeogCS( semi_major_axis=6377563.396, semi_minor_axis=6356256.909 ) @@ -85,6 +98,8 @@ def test_extra_kwargs(self): longitude_of_projection_origin, ellipsoid=ellipsoid, standard_parallel=true_scale_lat, + false_easting=false_easting, + false_northing=false_northing, ) expected = ccrs.Mercator( @@ -95,6 +110,8 @@ def test_extra_kwargs(self): ellipse=None, ), latitude_true_scale=true_scale_lat, + false_easting=false_easting, + false_northing=false_northing, ) res = merc_cs.as_cartopy_crs() @@ -113,6 +130,8 @@ def test_simple(self): def test_extra_kwargs(self): longitude_of_projection_origin = 90.0 true_scale_lat = 14.0 + false_easting = 13 + false_northing = 12 ellipsoid = GeogCS( semi_major_axis=6377563.396, semi_minor_axis=6356256.909 ) @@ -121,6 +140,8 @@ def test_extra_kwargs(self): longitude_of_projection_origin, ellipsoid=ellipsoid, standard_parallel=true_scale_lat, + false_easting=false_easting, + false_northing=false_northing, ) expected = ccrs.Mercator( @@ -131,6 +152,8 @@ def test_extra_kwargs(self): ellipse=None, ), latitude_true_scale=true_scale_lat, + false_easting=false_easting, + false_northing=false_northing, ) res = merc_cs.as_cartopy_projection() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py index dfe2895f29..1b9857c0be 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py @@ -28,7 +28,7 @@ def _engine(cf_grid_var, cf_name): class TestHasSupportedMercatorParameters(tests.IrisTest): - def test_valid(self): + def test_valid_base(self): cf_name = "mercator" cf_grid_var = mock.Mock( spec=[], @@ -45,85 +45,50 @@ def test_valid(self): self.assertTrue(is_valid) - def test_invalid_scale_factor(self): - # Iris does not yet support scale factors other than one for - # Mercator projections + def test_valid_false_easting_northing(self): cf_name = "mercator" cf_grid_var = mock.Mock( spec=[], - longitude_of_projection_origin=0, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=0.9, + longitude_of_projection_origin=-90, + false_easting=15, + false_northing=10, + scale_factor_at_projection_origin=1, semi_major_axis=6377563.396, semi_minor_axis=6356256.909, ) engine = _engine(cf_grid_var, cf_name) - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_mercator_parameters(engine, cf_name) + is_valid = has_supported_mercator_parameters(engine, cf_name) - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex(str(warns[0]), "Scale factor") + self.assertTrue(is_valid) - def test_invalid_standard_parallel(self): - # Iris does not yet support standard parallels other than zero for - # Mercator projections + def test_valid_standard_parallel(self): cf_name = "mercator" cf_grid_var = mock.Mock( spec=[], - longitude_of_projection_origin=0, + longitude_of_projection_origin=-90, false_easting=0, false_northing=0, - standard_parallel=30, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_mercator_parameters(engine, cf_name) - - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex(str(warns[0]), "Standard parallel") - - def test_invalid_false_easting(self): - # Iris does not yet support false eastings other than zero for - # Mercator projections - cf_name = "mercator" - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=0, - false_easting=100, - false_northing=0, - scale_factor_at_projection_origin=1, + standard_parallel=15, semi_major_axis=6377563.396, semi_minor_axis=6356256.909, ) engine = _engine(cf_grid_var, cf_name) - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_mercator_parameters(engine, cf_name) + is_valid = has_supported_mercator_parameters(engine, cf_name) - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex(str(warns[0]), "False easting") + self.assertTrue(is_valid) - def test_invalid_false_northing(self): - # Iris does not yet support false northings other than zero for + def test_invalid_scale_factor(self): + # Iris does not yet support scale factors other than one for # Mercator projections cf_name = "mercator" cf_grid_var = mock.Mock( spec=[], longitude_of_projection_origin=0, false_easting=0, - false_northing=100, - scale_factor_at_projection_origin=1, + false_northing=0, + scale_factor_at_projection_origin=0.9, semi_major_axis=6377563.396, semi_minor_axis=6356256.909, ) @@ -135,7 +100,7 @@ def test_invalid_false_northing(self): self.assertFalse(is_valid) self.assertEqual(len(warns), 1) - self.assertRegex(str(warns[0]), "False northing") + self.assertRegex(str(warns[0]), "Scale factor") if __name__ == "__main__": From de88403ee5aece18c2fe89f5068dc9ea30fb9099 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Mar 2022 09:47:18 +0000 Subject: [PATCH 030/319] Bump peter-evans/create-pull-request from 3.13.0 to 3.14.0 (#4608) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3.13.0 to 3.14.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/89265e8d24a5dea438a2577fdc409a11e9f855ca...18f7dc018cc2cd597073088f7c7591b9d1c02672) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 5a06c97189..96572fb815 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -111,7 +111,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@89265e8d24a5dea438a2577fdc409a11e9f855ca + uses: peter-evans/create-pull-request@18f7dc018cc2cd597073088f7c7591b9d1c02672 with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From 0adcbfa12931455eacf4a537edfd7f9ed39d27e1 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 1 Mar 2022 17:22:35 +0000 Subject: [PATCH 031/319] Revert plotting-vs-y (#4601) * revert plotting-vs-y * whatsnew --- docs/src/whatsnew/dev.rst | 4 +++- lib/iris/plot.py | 2 +- lib/iris/tests/results/imagerepo.json | 10 ++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 5952dc45b0..b9d5989bfc 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -38,7 +38,9 @@ This document explains the changes made to Iris for this release 🐛 Bugs Fixed ============= -#. N/A +#. `@rcomer`_ reverted part of the change from :pull:`3906` so that + :func:`iris.plot.plot` no longer defaults to placing a "Y" coordinate (e.g. + latitude) on the y-axis of the plot. (:issue:`4493`, :pull:`4601`) 💣 Incompatible Changes diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 3cd54ef08f..aefca889cf 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -673,7 +673,7 @@ def _get_plot_objects(args): if ( isinstance(v_object, iris.cube.Cube) and isinstance(u_object, iris.coords.Coord) - and iris.util.guess_coord_axis(u_object) in ["Y", "Z"] + and iris.util.guess_coord_axis(u_object) == "Z" ): u_object, v_object = v_object, u_object u, v = v, u diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index 79560a5365..6a997c38b4 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -684,7 +684,10 @@ "https://scitools.github.io/test-iris-imagehash/images/v4/8bfe956b7c01c2f26300929dfc1e3c6690736f91817e3b0c84be6be5d1603ed1.png" ], "iris.tests.test_plot.TestPlot.test_y.0": [ - "https://scitools.github.io/test-iris-imagehash/images/v4/8ff99c067e01e7166101c9c6b04396b5cd4e2f0993163de9c4fe7b79207e36a1.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/8fe896266f068d873b83cb71e435725cd07c607ad07e70fcd0007a7881fe7ab8.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/8fe896066f068d873b83cb71e435725cd07c607ad07c70fcd0007af881fe7bb8.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/8fe896366f0f8d93398bcb71e435f24ed074646ed07670acf010726d81f2798c.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/aff8946c7a14c99fb193d263e42432d8d00c2d27944a3f8dc5223ef703ff6b90.png" ], "iris.tests.test_plot.TestPlot.test_z.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/8fffc1dc7e019c70f001b70ee4386de1814e7938837b6a7f84d07c9f15b02f21.png" @@ -874,7 +877,10 @@ "https://scitools.github.io/test-iris-imagehash/images/v4/82ff950b7f81c0d6620199bcfc5e986695734da1816e1b2c85be2b65d96276d1.png" ], "iris.tests.test_plot.TestQuickplotPlot.test_y.0": [ - "https://scitools.github.io/test-iris-imagehash/images/v4/a3f9bc067e01c6166009c9c6b5439ee5cd4e0d2993361de9ccf65b79887636a9.png" + "https://scitools.github.io/test-iris-imagehash/images/v4/a7ffb6067f008d87339bc973e435d86ef034c87ad07c586cd001da69897e5838.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/a7ffb6067f008d87339bc973e435d86ef034c87ad07cd86cd001da68897e58a8.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/a7efb6367f008d97338fc973e435d86ef030c86ed070d86cd030d86d89f0d82c.png", + "https://scitools.github.io/test-iris-imagehash/images/v4/a2fbb46e7f10c99f2013d863e46498dcd06c0d2798421fa5dd221e7789ff6f10.png" ], "iris.tests.test_plot.TestQuickplotPlot.test_z.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/a3ffc1de7e009c7030019786f438cde3810fd93c9b734a778ce47c9799b02731.png" From 9a89050ccb1605b4a2f5b99be7facbd188f218a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 10:02:33 +0000 Subject: [PATCH 032/319] Bump actions/stale from 4.1.0 to 5 (#4612) Bumps [actions/stale](https://github.com/actions/stale) from 4.1.0 to 5. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v4.1.0...v5) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a1bb0fca6c..008fe56deb 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -14,7 +14,7 @@ jobs: if: "github.repository == 'SciTools/iris'" runs-on: ubuntu-latest steps: - - uses: actions/stale@v4.1.0 + - uses: actions/stale@v5 with: repo-token: ${{ secrets.GITHUB_TOKEN }} From c83722b0b5241958309c2843d9d715f012faa48b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 10:03:27 +0000 Subject: [PATCH 033/319] Bump actions/checkout from 2 to 3 (#4611) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d4c01af48a..086f6a0bb4 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -23,7 +23,7 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 96572fb815..2be35e9f18 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -76,7 +76,7 @@ jobs: python: ['38'] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: install conda-lock run: | source $CONDA/bin/activate base @@ -98,7 +98,7 @@ jobs: needs: gen_lockfiles steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: get artifacts uses: actions/download-artifact@v2 with: From 2b409c3951a63f9e7c16268d8cc1aae69c7d4d5c Mon Sep 17 00:00:00 2001 From: Denis Sergeev Date: Wed, 2 Mar 2022 11:11:15 +0000 Subject: [PATCH 034/319] Use crs.globe in _crs_distance_differentials() (#4605) * Use crs.globe in _crs_distance_differentials(), add a relevant test, update whatsnew * add a ref to the issue and PR --- docs/src/whatsnew/3.2.rst | 5 ++++- lib/iris/analysis/cartography.py | 2 +- .../unit/analysis/cartography/test_rotate_winds.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index fac755ad72..38f4ce0b8e 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -38,7 +38,9 @@ v3.2.1 |build_date| [unreleased] 🐛 **Bugs Fixed** - #. N/A + #. `@dennissergeev`_ changed _crs_distance_differentials() so that it uses the `Globe` + attribute from a given CRS instead of creating a new `ccrs.Globe()` object. + Iris can now handle non-Earth semi-major axes, as discussed in :issue:`4582` (:pull:`4605`). 💼 **Internal** @@ -388,6 +390,7 @@ v3.2.1 |build_date| [unreleased] .. _@aaronspring: https://github.com/aaronspring .. _@akuhnregnier: https://github.com/akuhnregnier .. _@bsherratt: https://github.com/bsherratt +.. _@dennissergeev: https://github.com/dennissergeev .. _@larsbarring: https://github.com/larsbarring .. _@pdearnshaw: https://github.com/pdearnshaw .. _@SimonPeatman: https://github.com/SimonPeatman diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index 373487af53..116dfe0b70 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -927,7 +927,7 @@ def _crs_distance_differentials(crs, x, y): """ # Make a true-latlon coordinate system for distance calculations. - crs_latlon = ccrs.Geodetic(globe=ccrs.Globe(ellipse="sphere")) + crs_latlon = ccrs.Geodetic(globe=crs.globe) # Transform points to true-latlon (just to get the true latitudes). _, true_lat = _transform_xy(crs, x, y, crs_latlon) # Get coordinate differentials w.r.t. true-latlon. diff --git a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py index 9e3af90603..31a3a0510a 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py +++ b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py @@ -493,5 +493,18 @@ def test_rotated_to_unrotated(self): self.assertArrayAlmostEqual(res_y, y2d) +class TestNonEarthPlanet(tests.IrisTest): + def test_non_earth_semimajor_axis(self): + u, v = uv_cubes() + u.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(123) + u.coord("grid_longitude").coord_system = iris.coord_systems.GeogCS(123) + v.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(123) + v.coord("grid_longitude").coord_system = iris.coord_systems.GeogCS(123) + other_cs = iris.coord_systems.RotatedGeogCS( + 0, 0, ellipsoid=iris.coord_systems.GeogCS(123) + ) + rotate_winds(u, v, other_cs) + + if __name__ == "__main__": tests.main() From af97c70782bfc2a224d06ffb5b3f4d78009fb3c9 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 2 Mar 2022 21:29:36 +0000 Subject: [PATCH 035/319] add dennis (#4614) --- docs/src/whatsnew/3.2.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index 38f4ce0b8e..977f373ba8 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -34,6 +34,8 @@ v3.2.1 |build_date| [unreleased] :body: bg-light :animate: fade-in + 📢 **Welcome** to `@dennissergeev`_, who made his first contribution to Iris. Nice work! + The patches in this release of Iris include: 🐛 **Bugs Fixed** From b42c65da21cbf487723ee585cadbe26a5155afb2 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 3 Mar 2022 11:11:23 +0000 Subject: [PATCH 036/319] Final offline benchmark migration (#4562) --- benchmarks/benchmarks/__init__.py | 64 +++++ benchmarks/benchmarks/aux_factory.py | 3 +- benchmarks/benchmarks/coords.py | 20 +- benchmarks/benchmarks/cube.py | 44 ++- .../benchmarks/experimental/__init__.py | 9 + benchmarks/benchmarks/experimental/ugrid.py | 195 +++++++++++++ .../benchmarks/generate_data/__init__.py | 103 +++++++ benchmarks/benchmarks/generate_data/stock.py | 126 ++++++++ benchmarks/benchmarks/iterate.py | 3 +- benchmarks/benchmarks/mixin.py | 3 +- benchmarks/benchmarks/netcdf_save.py | 61 ++++ benchmarks/benchmarks/plot.py | 3 +- benchmarks/benchmarks/regions_combine.py | 268 ++++++++++++++++++ benchmarks/benchmarks/regridding.py | 21 +- benchmarks/benchmarks/ugrid_load.py | 128 +++++++++ 15 files changed, 1041 insertions(+), 10 deletions(-) create mode 100644 benchmarks/benchmarks/experimental/__init__.py create mode 100644 benchmarks/benchmarks/experimental/ugrid.py create mode 100644 benchmarks/benchmarks/generate_data/stock.py create mode 100644 benchmarks/benchmarks/netcdf_save.py create mode 100644 benchmarks/benchmarks/regions_combine.py create mode 100644 benchmarks/benchmarks/ugrid_load.py diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py index 4a964a648d..38502c9306 100644 --- a/benchmarks/benchmarks/__init__.py +++ b/benchmarks/benchmarks/__init__.py @@ -4,5 +4,69 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """Common code for benchmarks.""" +import resource + +from .generate_data import BENCHMARK_DATA, run_function_elsewhere ARTIFICIAL_DIM_SIZE = int(10e3) # For all artificial cubes, coords etc. + + +def disable_repeat_between_setup(benchmark_object): + """ + Decorator for benchmarks where object persistence would be inappropriate. + + E.g: + * Benchmarking data realisation + * Benchmarking Cube coord addition + + Can be applied to benchmark classes/methods/functions. + + https://asv.readthedocs.io/en/stable/benchmarks.html#timing-benchmarks + + """ + # Prevent repeat runs between setup() runs - object(s) will persist after 1st. + benchmark_object.number = 1 + # Compensate for reduced certainty by increasing number of repeats. + # (setup() is run between each repeat). + # Minimum 5 repeats, run up to 30 repeats / 20 secs whichever comes first. + benchmark_object.repeat = (5, 30, 20.0) + # ASV uses warmup to estimate benchmark time before planning the real run. + # Prevent this, since object(s) will persist after first warmup run, + # which would give ASV misleading info (warmups ignore ``number``). + benchmark_object.warmup_time = 0.0 + + return benchmark_object + + +class TrackAddedMemoryAllocation: + """ + Context manager which measures by how much process resident memory grew, + during execution of its enclosed code block. + + Obviously limited as to what it actually measures : Relies on the current + process not having significant unused (de-allocated) memory when the + tested codeblock runs, and only reliable when the code allocates a + significant amount of new memory. + + Example: + with TrackAddedMemoryAllocation() as mb: + initial_call() + other_call() + result = mb.addedmem_mb() + + """ + + @staticmethod + def process_resident_memory_mb(): + return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0 + + def __enter__(self): + self.mb_before = self.process_resident_memory_mb() + return self + + def __exit__(self, *_): + self.mb_after = self.process_resident_memory_mb() + + def addedmem_mb(self): + """Return measured memory growth, in Mb.""" + return self.mb_after - self.mb_before diff --git a/benchmarks/benchmarks/aux_factory.py b/benchmarks/benchmarks/aux_factory.py index 270119da71..45bfa1b515 100644 --- a/benchmarks/benchmarks/aux_factory.py +++ b/benchmarks/benchmarks/aux_factory.py @@ -10,9 +10,10 @@ import numpy as np -from benchmarks import ARTIFICIAL_DIM_SIZE from iris import aux_factory, coords +from . import ARTIFICIAL_DIM_SIZE + class FactoryCommon: # TODO: once https://github.com/airspeed-velocity/asv/pull/828 is released: diff --git a/benchmarks/benchmarks/coords.py b/benchmarks/benchmarks/coords.py index fce7318d49..5cea1e1e2e 100644 --- a/benchmarks/benchmarks/coords.py +++ b/benchmarks/benchmarks/coords.py @@ -10,9 +10,10 @@ import numpy as np -from benchmarks import ARTIFICIAL_DIM_SIZE from iris import coords +from . import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup + def setup(): """General variables needed by multiple benchmark classes.""" @@ -92,6 +93,23 @@ def setup(self): def create(self): return coords.AuxCoord(**self.create_kwargs) + def time_points(self): + _ = self.component.points + + def time_bounds(self): + _ = self.component.bounds + + +@disable_repeat_between_setup +class AuxCoordLazy(AuxCoord): + """Lazy equivalent of :class:`AuxCoord`.""" + + def setup(self): + super().setup() + self.create_kwargs["points"] = self.component.lazy_points() + self.create_kwargs["bounds"] = self.component.lazy_bounds() + self.setup_common() + class CellMeasure(CoordCommon): def setup(self): diff --git a/benchmarks/benchmarks/cube.py b/benchmarks/benchmarks/cube.py index 3cfa6b248b..8a12391684 100644 --- a/benchmarks/benchmarks/cube.py +++ b/benchmarks/benchmarks/cube.py @@ -10,11 +10,13 @@ import numpy as np -from benchmarks import ARTIFICIAL_DIM_SIZE from iris import analysis, aux_factory, coords, cube +from . import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup +from .generate_data.stock import sample_meshcoord -def setup(): + +def setup(*params): """General variables needed by multiple benchmark classes.""" global data_1d global data_2d @@ -170,6 +172,44 @@ def setup(self): self.setup_common() +class MeshCoord: + params = [ + 6, # minimal cube-sphere + int(1e6), # realistic cube-sphere size + ARTIFICIAL_DIM_SIZE, # To match size in :class:`AuxCoord` + ] + param_names = ["number of faces"] + + def setup(self, n_faces): + mesh_kwargs = dict( + n_nodes=n_faces + 2, n_edges=n_faces * 2, n_faces=n_faces + ) + + self.mesh_coord = sample_meshcoord(sample_mesh_kwargs=mesh_kwargs) + self.data = np.zeros(n_faces) + self.cube_blank = cube.Cube(data=self.data) + self.cube = self.create() + + def create(self): + return cube.Cube( + data=self.data, aux_coords_and_dims=[(self.mesh_coord, 0)] + ) + + def time_create(self, n_faces): + _ = self.create() + + @disable_repeat_between_setup + def time_add(self, n_faces): + self.cube_blank.add_aux_coord(self.mesh_coord, 0) + + @disable_repeat_between_setup + def time_remove(self, n_faces): + self.cube.remove_coord(self.mesh_coord) + + def time_return(self, n_faces): + _ = self.cube + + class Merge: def setup(self): self.cube_list = cube.CubeList() diff --git a/benchmarks/benchmarks/experimental/__init__.py b/benchmarks/benchmarks/experimental/__init__.py new file mode 100644 index 0000000000..f16e400bce --- /dev/null +++ b/benchmarks/benchmarks/experimental/__init__.py @@ -0,0 +1,9 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Benchmark tests for the experimental module. + +""" diff --git a/benchmarks/benchmarks/experimental/ugrid.py b/benchmarks/benchmarks/experimental/ugrid.py new file mode 100644 index 0000000000..609abbe77c --- /dev/null +++ b/benchmarks/benchmarks/experimental/ugrid.py @@ -0,0 +1,195 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Benchmark tests for the experimental.ugrid module. + +""" + +from copy import deepcopy + +import numpy as np + +from iris.experimental import ugrid + +from .. import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup +from ..generate_data.stock import sample_mesh + + +class UGridCommon: + """ + A base class running a generalised suite of benchmarks for any ugrid object. + Object to be specified in a subclass. + + ASV will run the benchmarks within this class for any subclasses. + + ASV will not benchmark this class as setup() triggers a NotImplementedError. + (ASV has not yet released ABC/abstractmethod support - asv#838). + + """ + + params = [ + 6, # minimal cube-sphere + int(1e6), # realistic cube-sphere size + ] + param_names = ["number of faces"] + + def setup(self, *params): + self.object = self.create() + + def create(self): + raise NotImplementedError + + def time_create(self, *params): + """Create an instance of the benchmarked object. create() method is + specified in the subclass.""" + self.create() + + def time_return(self, *params): + """Return an instance of the benchmarked object.""" + _ = self.object + + +class Connectivity(UGridCommon): + def setup(self, n_faces): + self.array = np.zeros([n_faces, 3], dtype=np.int) + super().setup(n_faces) + + def create(self): + return ugrid.Connectivity( + indices=self.array, cf_role="face_node_connectivity" + ) + + def time_indices(self, n_faces): + _ = self.object.indices + + def time_location_lengths(self, n_faces): + # Proofed against the Connectivity name change (633ed17). + if getattr(self.object, "src_lengths", False): + meth = self.object.src_lengths + else: + meth = self.object.location_lengths + _ = meth() + + def time_validate_indices(self, n_faces): + self.object.validate_indices() + + +@disable_repeat_between_setup +class ConnectivityLazy(Connectivity): + """Lazy equivalent of :class:`Connectivity`.""" + + def setup(self, n_faces): + super().setup(n_faces) + self.array = self.object.lazy_indices() + self.object = self.create() + + +class Mesh(UGridCommon): + def setup(self, n_faces, lazy=False): + #### + # Steal everything from the sample mesh for benchmarking creation of a + # brand new mesh. + source_mesh = sample_mesh( + n_nodes=n_faces + 2, + n_edges=n_faces * 2, + n_faces=n_faces, + lazy_values=lazy, + ) + + def get_coords_and_axes(location): + search_kwargs = {f"include_{location}s": True} + return [ + (source_mesh.coord(axis=axis, **search_kwargs), axis) + for axis in ("x", "y") + ] + + self.mesh_kwargs = dict( + topology_dimension=source_mesh.topology_dimension, + node_coords_and_axes=get_coords_and_axes("node"), + connectivities=source_mesh.connectivities(), + edge_coords_and_axes=get_coords_and_axes("edge"), + face_coords_and_axes=get_coords_and_axes("face"), + ) + #### + + super().setup(n_faces) + + self.face_node = self.object.face_node_connectivity + self.node_x = self.object.node_coords.node_x + # Kwargs for reuse in search and remove methods. + self.connectivities_kwarg = dict(cf_role="edge_node_connectivity") + self.coords_kwarg = dict(include_faces=True) + + # TODO: an opportunity for speeding up runtime if needed, since + # eq_object is not needed for all benchmarks. Just don't generate it + # within a benchmark - the execution time is large enough that it + # could be a significant portion of the benchmark - makes regressions + # smaller and could even pick up regressions in copying instead! + self.eq_object = deepcopy(self.object) + + def create(self): + return ugrid.Mesh(**self.mesh_kwargs) + + def time_add_connectivities(self, n_faces): + self.object.add_connectivities(self.face_node) + + def time_add_coords(self, n_faces): + self.object.add_coords(node_x=self.node_x) + + def time_connectivities(self, n_faces): + _ = self.object.connectivities(**self.connectivities_kwarg) + + def time_coords(self, n_faces): + _ = self.object.coords(**self.coords_kwarg) + + def time_eq(self, n_faces): + _ = self.object == self.eq_object + + def time_remove_connectivities(self, n_faces): + self.object.remove_connectivities(**self.connectivities_kwarg) + + def time_remove_coords(self, n_faces): + self.object.remove_coords(**self.coords_kwarg) + + +@disable_repeat_between_setup +class MeshLazy(Mesh): + """Lazy equivalent of :class:`Mesh`.""" + + def setup(self, n_faces, lazy=True): + super().setup(n_faces, lazy=lazy) + + +class MeshCoord(UGridCommon): + # Add extra parameter value to match AuxCoord benchmarking. + params = UGridCommon.params + [ARTIFICIAL_DIM_SIZE] + + def setup(self, n_faces, lazy=False): + self.mesh = sample_mesh( + n_nodes=n_faces + 2, + n_edges=n_faces * 2, + n_faces=n_faces, + lazy_values=lazy, + ) + + super().setup(n_faces) + + def create(self): + return ugrid.MeshCoord(mesh=self.mesh, location="face", axis="x") + + def time_points(self, n_faces): + _ = self.object.points + + def time_bounds(self, n_faces): + _ = self.object.bounds + + +@disable_repeat_between_setup +class MeshCoordLazy(MeshCoord): + """Lazy equivalent of :class:`MeshCoord`.""" + + def setup(self, n_faces, lazy=True): + super().setup(n_faces, lazy=lazy) diff --git a/benchmarks/benchmarks/generate_data/__init__.py b/benchmarks/benchmarks/generate_data/__init__.py index a56f2e4623..125e2e1b53 100644 --- a/benchmarks/benchmarks/generate_data/__init__.py +++ b/benchmarks/benchmarks/generate_data/__init__.py @@ -16,11 +16,18 @@ benchmark sequence runs over two different Python versions. """ +from contextlib import contextmanager from inspect import getsource from os import environ from pathlib import Path from subprocess import CalledProcessError, check_output, run from textwrap import dedent +from typing import Iterable + +from iris import load_cube as iris_loadcube +from iris._lazy_data import as_concrete_data +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD +from iris.fileformats import netcdf #: Python executable used by :func:`run_function_elsewhere`, set via env #: variable of same name. Must be path of Python within an environment that @@ -92,3 +99,99 @@ def run_function_elsewhere(func_to_run, *args, **kwargs): [DATA_GEN_PYTHON, "-c", python_string], capture_output=True, check=True ) return result.stdout + + +def generate_cube_like_2d_cubesphere( + n_cube: int, with_mesh: bool, output_path: str +): + """ + Construct and save to file an LFRIc cubesphere-like cube for a given + cubesphere size, *or* a simpler structured (UM-like) cube of equivalent + size. + + NOTE: this function is *NEVER* called from within this actual package. + Instead, it is to be called via benchmarks.remote_data_generation, + so that it can use up-to-date facilities, independent of the ASV controlled + environment which contains the "Iris commit under test". + This means: + * it must be completely self-contained : i.e. it includes all its + own imports, and saves results to an output file. + + """ + from iris import save + from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube + + n_face_nodes = n_cube * n_cube + n_faces = 6 * n_face_nodes + + # Set n_nodes=n_faces and n_edges=2*n_faces + # : Not exact, but similar to a 'real' cubesphere. + n_nodes = n_faces + n_edges = 2 * n_faces + if with_mesh: + mesh = sample_mesh( + n_nodes=n_nodes, n_faces=n_faces, n_edges=n_edges, lazy_values=True + ) + cube = sample_mesh_cube(mesh=mesh, n_z=1) + else: + cube = sample_mesh_cube(nomesh_faces=n_faces, n_z=1) + + # Strip off the 'extra' aux-coord mapping the mesh, which sample-cube adds + # but which we don't want. + cube.remove_coord("mesh_face_aux") + + # Save the result to a named file. + save(cube, output_path) + + +def make_cube_like_2d_cubesphere(n_cube: int, with_mesh: bool): + """ + Generate an LFRIc cubesphere-like cube for a given cubesphere size, + *or* a simpler structured (UM-like) cube of equivalent size. + + All the cube data, coords and mesh content are LAZY, and produced without + allocating large real arrays (to allow peak-memory testing). + + NOTE: the actual cube generation is done in a stable Iris environment via + benchmarks.remote_data_generation, so it is all channeled via cached netcdf + files in our common testdata directory. + + """ + identifying_filename = ( + f"cube_like_2d_cubesphere_C{n_cube}_Mesh={with_mesh}.nc" + ) + filepath = BENCHMARK_DATA / identifying_filename + if not filepath.exists(): + # Create the required testfile, by running the generation code remotely + # in a 'fixed' python environment. + run_function_elsewhere( + generate_cube_like_2d_cubesphere, + n_cube, + with_mesh=with_mesh, + output_path=str(filepath), + ) + + # File now *should* definitely exist: content is simply the desired cube. + with PARSE_UGRID_ON_LOAD.context(): + with load_realised(): + cube = iris_loadcube(str(filepath)) + return cube + + +@contextmanager +def load_realised(): + """ + Force NetCDF loading with realised arrays. + + Since passing between data generation and benchmarking environments is via + file loading, but some benchmarks are only meaningful if starting with real + arrays. + """ + from iris.fileformats.netcdf import _get_cf_var_data as pre_patched + + def patched(cf_var, filename): + return as_concrete_data(pre_patched(cf_var, filename)) + + netcdf._get_cf_var_data = patched + yield netcdf + netcdf._get_cf_var_data = pre_patched diff --git a/benchmarks/benchmarks/generate_data/stock.py b/benchmarks/benchmarks/generate_data/stock.py new file mode 100644 index 0000000000..e352147fc8 --- /dev/null +++ b/benchmarks/benchmarks/generate_data/stock.py @@ -0,0 +1,126 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Wrappers for using :mod:`iris.tests.stock` methods for benchmarking. + +See :mod:`benchmarks.generate_data` for an explanation of this structure. +""" + +from pathlib import Path +import pickle + +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD, load_mesh + +from . import BENCHMARK_DATA, REUSE_DATA, load_realised, run_function_elsewhere + + +def create_file__xios_2d_face_half_levels( + temp_file_dir, dataset_name, n_faces=866, n_times=1 +): + """ + Wrapper for :meth:`iris.tests.stock.netcdf.create_file__xios_2d_face_half_levels`. + + Have taken control of temp_file_dir + + todo: is create_file__xios_2d_face_half_levels still appropriate now we can + properly save Mesh Cubes? + """ + + def _external(*args, **kwargs): + from iris.tests.stock.netcdf import ( + create_file__xios_2d_face_half_levels, + ) + + print(create_file__xios_2d_face_half_levels(*args, **kwargs), end="") + + args_list = [dataset_name, n_faces, n_times] + args_hash = hash(str(args_list)) + save_path = ( + BENCHMARK_DATA / f"create_file__xios_2d_face_half_levels_{args_hash}" + ).with_suffix(".nc") + if not REUSE_DATA or not save_path.is_file(): + # create_file__xios_2d_face_half_levels takes control of save location + # so need to move to a more specific name that allows re-use. + actual_path = run_function_elsewhere( + _external, str(BENCHMARK_DATA), *args_list + ) + Path(actual_path.decode()).replace(save_path) + return save_path + + +def sample_mesh(n_nodes=None, n_faces=None, n_edges=None, lazy_values=False): + """Wrapper for :meth:iris.tests.stock.mesh.sample_mesh`.""" + + def _external(*args, **kwargs): + from iris.experimental.ugrid import save_mesh + from iris.tests.stock.mesh import sample_mesh + + save_path_ = kwargs.pop("save_path") + # Always saving, so laziness is irrelevant. Use lazy to save time. + kwargs["lazy_values"] = True + new_mesh = sample_mesh(*args, **kwargs) + save_mesh(new_mesh, save_path_) + + arg_list = [n_nodes, n_faces, n_edges] + args_hash = hash(str(arg_list)) + save_path = (BENCHMARK_DATA / f"sample_mesh_{args_hash}").with_suffix( + ".nc" + ) + if not REUSE_DATA or not save_path.is_file(): + _ = run_function_elsewhere( + _external, *arg_list, save_path=str(save_path) + ) + with PARSE_UGRID_ON_LOAD.context(): + if not lazy_values: + # Realise everything. + with load_realised(): + mesh = load_mesh(str(save_path)) + else: + mesh = load_mesh(str(save_path)) + return mesh + + +def sample_meshcoord(sample_mesh_kwargs=None, location="face", axis="x"): + """ + Wrapper for :meth:`iris.tests.stock.mesh.sample_meshcoord`. + + Parameters deviate from the original as cannot pass a + :class:`iris.experimental.ugrid.Mesh to the separate Python instance - must + instead generate the Mesh as well. + + MeshCoords cannot be saved to file, so the _external method saves the + MeshCoord's Mesh, then the original Python instance loads in that Mesh and + regenerates the MeshCoord from there. + """ + + def _external(sample_mesh_kwargs_, save_path_): + from iris.experimental.ugrid import save_mesh + from iris.tests.stock.mesh import sample_mesh, sample_meshcoord + + if sample_mesh_kwargs_: + input_mesh = sample_mesh(**sample_mesh_kwargs_) + else: + input_mesh = None + # Don't parse the location or axis arguments - only saving the Mesh at + # this stage. + new_meshcoord = sample_meshcoord(mesh=input_mesh) + save_mesh(new_meshcoord.mesh, save_path_) + + args_hash = hash(str(sample_mesh_kwargs)) + save_path = ( + BENCHMARK_DATA / f"sample_mesh_coord_{args_hash}" + ).with_suffix(".nc") + if not REUSE_DATA or not save_path.is_file(): + _ = run_function_elsewhere( + _external, + sample_mesh_kwargs_=sample_mesh_kwargs, + save_path_=str(save_path), + ) + with PARSE_UGRID_ON_LOAD.context(): + with load_realised(): + source_mesh = load_mesh(str(save_path)) + # Regenerate MeshCoord from its Mesh, which we saved. + return source_mesh.to_MeshCoord(location=location, axis=axis) diff --git a/benchmarks/benchmarks/iterate.py b/benchmarks/benchmarks/iterate.py index 20422750ef..0a5415ac2b 100644 --- a/benchmarks/benchmarks/iterate.py +++ b/benchmarks/benchmarks/iterate.py @@ -9,9 +9,10 @@ """ import numpy as np -from benchmarks import ARTIFICIAL_DIM_SIZE from iris import coords, cube, iterate +from . import ARTIFICIAL_DIM_SIZE + def setup(): """General variables needed by multiple benchmark classes.""" diff --git a/benchmarks/benchmarks/mixin.py b/benchmarks/benchmarks/mixin.py index e78b150438..bec5518eee 100644 --- a/benchmarks/benchmarks/mixin.py +++ b/benchmarks/benchmarks/mixin.py @@ -10,10 +10,11 @@ import numpy as np -from benchmarks import ARTIFICIAL_DIM_SIZE from iris import coords from iris.common.metadata import AncillaryVariableMetadata +from . import ARTIFICIAL_DIM_SIZE + LONG_NAME = "air temperature" STANDARD_NAME = "air_temperature" VAR_NAME = "air_temp" diff --git a/benchmarks/benchmarks/netcdf_save.py b/benchmarks/benchmarks/netcdf_save.py new file mode 100644 index 0000000000..c7580b3c63 --- /dev/null +++ b/benchmarks/benchmarks/netcdf_save.py @@ -0,0 +1,61 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Cubesphere-like netcdf saving benchmarks. + +Where possible benchmarks should be parameterised for two sizes of input data: + * minimal: enables detection of regressions in parts of the run-time that do + NOT scale with data size. + * large: large enough to exclusively detect regressions in parts of the + run-time that scale with data size. Aim for benchmark time ~20x + that of the minimal benchmark. + +""" +from iris import save +from iris.experimental.ugrid import save_mesh + +from . import TrackAddedMemoryAllocation +from .generate_data import make_cube_like_2d_cubesphere + + +class NetcdfSave: + params = [[1, 600], [False, True]] + param_names = ["cubesphere-N", "is_unstructured"] + + def setup(self, n_cubesphere, is_unstructured): + self.cube = make_cube_like_2d_cubesphere( + n_cube=n_cubesphere, with_mesh=is_unstructured + ) + + def _save_data(self, cube, do_copy=True): + if do_copy: + # Copy the cube, to avoid distorting the results by changing it + # Because we known that older Iris code realises lazy coords + cube = cube.copy() + save(cube, "tmp.nc") + + def _save_mesh(self, cube): + # In this case, we are happy that the mesh is *not* modified + save_mesh(cube.mesh, "mesh.nc") + + def time_netcdf_save_cube(self, n_cubesphere, is_unstructured): + self._save_data(self.cube) + + def time_netcdf_save_mesh(self, n_cubesphere, is_unstructured): + if is_unstructured: + self._save_mesh(self.cube) + + def track_addedmem_netcdf_save(self, n_cubesphere, is_unstructured): + cube = self.cube.copy() # Do this outside the testing block + with TrackAddedMemoryAllocation() as mb: + self._save_data(cube, do_copy=False) + return mb.addedmem_mb() + + +# Declare a 'Mb' unit for all 'track_addedmem_..' type benchmarks +for attr in dir(NetcdfSave): + if attr.startswith("track_addedmem_"): + getattr(NetcdfSave, attr).unit = "Mb" diff --git a/benchmarks/benchmarks/plot.py b/benchmarks/benchmarks/plot.py index 24899776dc..75195c86e9 100644 --- a/benchmarks/benchmarks/plot.py +++ b/benchmarks/benchmarks/plot.py @@ -10,9 +10,10 @@ import matplotlib import numpy as np -from benchmarks import ARTIFICIAL_DIM_SIZE from iris import coords, cube, plot +from . import ARTIFICIAL_DIM_SIZE + matplotlib.use("agg") diff --git a/benchmarks/benchmarks/regions_combine.py b/benchmarks/benchmarks/regions_combine.py new file mode 100644 index 0000000000..a99dc57263 --- /dev/null +++ b/benchmarks/benchmarks/regions_combine.py @@ -0,0 +1,268 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Benchmarks stages of operation of the function +:func:`iris.experimental.ugrid.utils.recombine_submeshes`. + +Where possible benchmarks should be parameterised for two sizes of input data: + * minimal: enables detection of regressions in parts of the run-time that do + NOT scale with data size. + * large: large enough to exclusively detect regressions in parts of the + run-time that scale with data size. Aim for benchmark time ~20x + that of the minimal benchmark. + +""" +import os + +import dask.array as da +import numpy as np + +from iris import load, load_cube, save +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD +from iris.experimental.ugrid.utils import recombine_submeshes + +from . import TrackAddedMemoryAllocation +from .generate_data import make_cube_like_2d_cubesphere + + +class MixinCombineRegions: + # Characterise time taken + memory-allocated, for various stages of combine + # operations on cubesphere-like test data. + params = [4, 500] + param_names = ["cubesphere-N"] + + def _parametrised_cache_filename(self, n_cubesphere, content_name): + return f"cube_C{n_cubesphere}_{content_name}.nc" + + def _make_region_cubes(self, full_mesh_cube): + """Make a fixed number of region cubes from a full meshcube.""" + # Divide the cube into regions. + n_faces = full_mesh_cube.shape[-1] + # Start with a simple list of face indices + # first extend to multiple of 5 + n_faces_5s = 5 * ((n_faces + 1) // 5) + i_faces = np.arange(n_faces_5s, dtype=int) + # reshape (5N,) to (N, 5) + i_faces = i_faces.reshape((n_faces_5s // 5, 5)) + # reorder [2, 3, 4, 0, 1] within each block of 5 + i_faces = np.concatenate([i_faces[:, 2:], i_faces[:, :2]], axis=1) + # flatten to get [2 3 4 0 1 (-) 8 9 10 6 7 (-) 13 14 15 11 12 ...] + i_faces = i_faces.flatten() + # reduce back to orignal length, wrap any overflows into valid range + i_faces = i_faces[:n_faces] % n_faces + + # Divide into regions -- always slightly uneven, since 7 doesn't divide + n_regions = 7 + n_facesperregion = n_faces // n_regions + i_face_regions = (i_faces // n_facesperregion) % n_regions + region_inds = [ + np.where(i_face_regions == i_region)[0] + for i_region in range(n_regions) + ] + # NOTE: this produces 7 regions, with near-adjacent value ranges but + # with some points "moved" to an adjacent region. + # Also, region-0 is bigger (because of not dividing by 7). + + # Finally, make region cubes with these indices. + region_cubes = [full_mesh_cube[..., inds] for inds in region_inds] + return region_cubes + + def setup_cache(self): + """Cache all the necessary source data on disk.""" + + # Control dask, to minimise memory usage + allow largest data. + self.fix_dask_settings() + + for n_cubesphere in self.params: + # Do for each parameter, since "setup_cache" is NOT parametrised + mesh_cube = make_cube_like_2d_cubesphere( + n_cube=n_cubesphere, with_mesh=True + ) + # Save to files which include the parameter in the names. + save( + mesh_cube, + self._parametrised_cache_filename(n_cubesphere, "meshcube"), + ) + region_cubes = self._make_region_cubes(mesh_cube) + save( + region_cubes, + self._parametrised_cache_filename(n_cubesphere, "regioncubes"), + ) + + def setup( + self, n_cubesphere, imaginary_data=True, create_result_cube=True + ): + """ + The combine-tests "standard" setup operation. + + Load the source cubes (full-mesh + region) from disk. + These are specific to the cubesize parameter. + The data is cached on disk rather than calculated, to avoid any + pre-loading of the process memory allocation. + + If 'imaginary_data' is set (default), the region cubes data is replaced + with lazy data in the form of a da.zeros(). Otherwise, the region data + is lazy data from the files. + + If 'create_result_cube' is set, create "self.combined_cube" containing + the (still lazy) result. + + NOTE: various test classes override + extend this. + + """ + + # Load source cubes (full-mesh and regions) + with PARSE_UGRID_ON_LOAD.context(): + self.full_mesh_cube = load_cube( + self._parametrised_cache_filename(n_cubesphere, "meshcube") + ) + self.region_cubes = load( + self._parametrised_cache_filename(n_cubesphere, "regioncubes") + ) + + # Remove all var-names from loaded cubes, which can otherwise cause + # problems. Also implement 'imaginary' data. + for cube in self.region_cubes + [self.full_mesh_cube]: + cube.var_name = None + for coord in cube.coords(): + coord.var_name = None + if imaginary_data: + # Replace cube data (lazy file data) with 'imaginary' data. + # This has the same lazy-array attributes, but is allocated by + # creating chunks on demand instead of loading from file. + data = cube.lazy_data() + data = da.zeros( + data.shape, dtype=data.dtype, chunks=data.chunksize + ) + cube.data = data + + if create_result_cube: + self.recombined_cube = self.recombine() + + # Fix dask usage mode for all the subsequent performance tests. + self.fix_dask_settings() + + def fix_dask_settings(self): + """ + Fix "standard" dask behaviour for time+space testing. + + Currently this is single-threaded mode, with known chunksize, + which is optimised for space saving so we can test largest data. + + """ + + import dask.config as dcfg + + # Use single-threaded, to avoid process-switching costs and minimise memory usage. + # N.B. generally may be slower, but use less memory ? + dcfg.set(scheduler="single-threaded") + # Configure iris._lazy_data.as_lazy_data to aim for 100Mb chunks + dcfg.set({"array.chunk-size": "128Mib"}) + + def recombine(self): + # A handy general shorthand for the main "combine" operation. + result = recombine_submeshes( + self.full_mesh_cube, + self.region_cubes, + index_coord_name="i_mesh_face", + ) + return result + + +class CombineRegionsCreateCube(MixinCombineRegions): + """ + Time+memory costs of creating a combined-regions cube. + + The result is lazy, and we don't do the actual calculation. + + """ + + def setup(self, n_cubesphere): + # In this case only, do *not* create the result cube. + # That is the operation we want to test. + super().setup(n_cubesphere, create_result_cube=False) + + def time_create_combined_cube(self, n_cubesphere): + self.recombine() + + def track_addedmem_create_combined_cube(self, n_cubesphere): + with TrackAddedMemoryAllocation() as mb: + self.recombine() + return mb.addedmem_mb() + + +CombineRegionsCreateCube.track_addedmem_create_combined_cube.unit = "Mb" + + +class CombineRegionsComputeRealData(MixinCombineRegions): + """ + Time+memory costs of computing combined-regions data. + """ + + def time_compute_data(self, n_cubesphere): + self.recombined_cube.data + + def track_addedmem_compute_data(self, n_cubesphere): + with TrackAddedMemoryAllocation() as mb: + self.recombined_cube.data + + return mb.addedmem_mb() + + +CombineRegionsComputeRealData.track_addedmem_compute_data.unit = "Mb" + + +class CombineRegionsSaveData(MixinCombineRegions): + """ + Test saving *only*, having replaced the input cube data with 'imaginary' + array data, so that input data is not loaded from disk during the save + operation. + + """ + + def time_save(self, n_cubesphere): + # Save to disk, which must compute data + stream it to file. + save(self.recombined_cube, "tmp.nc") + + def track_addedmem_save(self, n_cubesphere): + with TrackAddedMemoryAllocation() as mb: + save(self.recombined_cube, "tmp.nc") + + return mb.addedmem_mb() + + def track_filesize_saved(self, n_cubesphere): + save(self.recombined_cube, "tmp.nc") + return os.path.getsize("tmp.nc") * 1.0e-6 + + +CombineRegionsSaveData.track_addedmem_save.unit = "Mb" +CombineRegionsSaveData.track_filesize_saved.unit = "Mb" + + +class CombineRegionsFileStreamedCalc(MixinCombineRegions): + """ + Test the whole cost of file-to-file streaming. + Uses the combined cube which is based on lazy data loading from the region + cubes on disk. + """ + + def setup(self, n_cubesphere): + # In this case only, do *not* replace the loaded regions data with + # 'imaginary' data, as we want to test file-to-file calculation+save. + super().setup(n_cubesphere, imaginary_data=False) + + def time_stream_file2file(self, n_cubesphere): + # Save to disk, which must compute data + stream it to file. + save(self.recombined_cube, "tmp.nc") + + def track_addedmem_stream_file2file(self, n_cubesphere): + with TrackAddedMemoryAllocation() as mb: + save(self.recombined_cube, "tmp.nc") + + return mb.addedmem_mb() + + +CombineRegionsFileStreamedCalc.track_addedmem_stream_file2file.unit = "Mb" diff --git a/benchmarks/benchmarks/regridding.py b/benchmarks/benchmarks/regridding.py index 6db33aa192..c315119c11 100644 --- a/benchmarks/benchmarks/regridding.py +++ b/benchmarks/benchmarks/regridding.py @@ -25,16 +25,31 @@ def setup(self) -> None: ) self.cube = iris.load_cube(cube_file_path) + # Prepare a tougher cube and chunk it + chunked_cube_file_path = tests.get_data_path( + ["NetCDF", "regrid", "regrid_xyt.nc"] + ) + self.chunked_cube = iris.load_cube(chunked_cube_file_path) + + # Chunked data makes the regridder run repeatedly + self.cube.data = self.cube.lazy_data().rechunk((1, -1, -1)) + template_file_path = tests.get_data_path( ["NetCDF", "regrid", "regrid_template_global_latlon.nc"] ) self.template_cube = iris.load_cube(template_file_path) - # Chunked data makes the regridder run repeatedly - self.cube.data = self.cube.lazy_data().rechunk((1, -1, -1)) + # Prepare a regridding scheme + self.scheme_area_w = AreaWeighted() def time_regrid_area_w(self) -> None: # Regrid the cube onto the template. - out = self.cube.regrid(self.template_cube, AreaWeighted()) + out = self.cube.regrid(self.template_cube, self.scheme_area_w) # Realise the data out.data + + def time_regrid_area_w_new_grid(self) -> None: + # Regrid the chunked cube + out = self.chunked_cube.regrid(self.template_cube, self.scheme_area_w) + # Realise data + out.data diff --git a/benchmarks/benchmarks/ugrid_load.py b/benchmarks/benchmarks/ugrid_load.py new file mode 100644 index 0000000000..352450dcec --- /dev/null +++ b/benchmarks/benchmarks/ugrid_load.py @@ -0,0 +1,128 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Mesh data loading benchmark tests. + +Where possible benchmarks should be parameterised for two sizes of input data: + * minimal: enables detection of regressions in parts of the run-time that do + NOT scale with data size. + * large: large enough to exclusively detect regressions in parts of the + run-time that scale with data size. Aim for benchmark time ~20x + that of the minimal benchmark. + +""" + +from iris import load_cube as iris_load_cube +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD +from iris.experimental.ugrid import load_mesh as iris_load_mesh + +from .generate_data.stock import create_file__xios_2d_face_half_levels + + +def synthetic_data(**kwargs): + # Ensure all uses of the synthetic data function use the common directory. + # File location is controlled by :mod:`generate_data`, hence temp_file_dir=None. + return create_file__xios_2d_face_half_levels(temp_file_dir=None, **kwargs) + + +def load_cube(*args, **kwargs): + with PARSE_UGRID_ON_LOAD.context(): + return iris_load_cube(*args, **kwargs) + + +def load_mesh(*args, **kwargs): + with PARSE_UGRID_ON_LOAD.context(): + return iris_load_mesh(*args, **kwargs) + + +class BasicLoading: + params = [1, int(4.1e6)] + param_names = ["number of faces"] + + def setup_common(self, **kwargs): + self.data_path = synthetic_data(**kwargs) + + def setup(self, *args): + self.setup_common(dataset_name="Loading", n_faces=args[0]) + + def time_load_file(self, *args): + _ = load_cube(str(self.data_path)) + + def time_load_mesh(self, *args): + _ = load_mesh(str(self.data_path)) + + +class BasicLoadingTime(BasicLoading): + """Same as BasicLoading, but scaling over a time series - an unlimited dimension.""" + + param_names = ["number of time steps"] + + def setup(self, *args): + self.setup_common(dataset_name="Loading", n_faces=1, n_times=args[0]) + + +class DataRealisation: + # Prevent repeat runs between setup() runs - data won't be lazy after 1st. + number = 1 + # Compensate for reduced certainty by increasing number of repeats. + repeat = (10, 10, 10.0) + # Prevent ASV running its warmup, which ignores `number` and would + # therefore get a false idea of typical run time since the data would stop + # being lazy. + warmup_time = 0.0 + timeout = 300.0 + + params = [1, int(4e6)] + param_names = ["number of faces"] + + def setup_common(self, **kwargs): + data_path = synthetic_data(**kwargs) + self.cube = load_cube(str(data_path)) + + def setup(self, *args): + self.setup_common(dataset_name="Realisation", n_faces=args[0]) + + def time_realise_data(self, *args): + assert self.cube.has_lazy_data() + _ = self.cube.data[0] + + +class DataRealisationTime(DataRealisation): + """Same as DataRealisation, but scaling over a time series - an unlimited dimension.""" + + param_names = ["number of time steps"] + + def setup(self, *args): + self.setup_common( + dataset_name="Realisation", n_faces=1, n_times=args[0] + ) + + +class Callback: + params = [1, int(4.5e6)] + param_names = ["number of faces"] + + def setup_common(self, **kwargs): + def callback(cube, field, filename): + return cube[::2] + + self.data_path = synthetic_data(**kwargs) + self.callback = callback + + def setup(self, *args): + self.setup_common(dataset_name="Loading", n_faces=args[0]) + + def time_load_file_callback(self, *args): + _ = load_cube(str(self.data_path), callback=self.callback) + + +class CallbackTime(Callback): + """Same as Callback, but scaling over a time series - an unlimited dimension.""" + + param_names = ["number of time steps"] + + def setup(self, *args): + self.setup_common(dataset_name="Loading", n_faces=1, n_times=args[0]) From d0569c79e803951aa3461b5a2335c91008bf9c40 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 4 Mar 2022 10:33:31 +0000 Subject: [PATCH 037/319] Overnight benchmarks remove ambiguity between file and commit names. (#4620) --- .github/workflows/benchmark.yml | 7 ++++--- noxfile.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 086f6a0bb4..6b155fc8bc 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -79,12 +79,13 @@ jobs: cd benchmarks/.asv/performance-shifts for commit_file in * do - pr_number=$(git log "$commit_file"^! --oneline | grep -o "#[0-9]*" | tail -1 | cut -c 2-) + commit="${commit_file%.*}" + pr_number=$(git log "$commit"^! --oneline | grep -o "#[0-9]*" | tail -1 | cut -c 2-) assignee=$(gh pr view $pr_number --json author -q '.["author"]["login"]' --repo $GITHUB_REPOSITORY) - title="Performance Shift(s): \`$commit_file\`" + title="Performance Shift(s): \`$commit\`" body=" Benchmark comparison has identified performance shifts at commit \ - $commit_file (#$pr_number). Please review the report below and \ + $commit (#$pr_number). Please review the report below and \ take corrective/congratulatory action as appropriate \ :slightly_smiling_face: diff --git a/noxfile.py b/noxfile.py index e4d91c6bab..c65319c35f 100755 --- a/noxfile.py +++ b/noxfile.py @@ -432,7 +432,7 @@ def asv_compare(*commits): # Dir is used by .github/workflows/benchmarks.yml, # but not cached - intended to be discarded after run. shifts_dir.mkdir(exist_ok=True, parents=True) - shifts_path = shifts_dir / after + shifts_path = (shifts_dir / after).with_suffix(".txt") with shifts_path.open("w") as shifts_file: shifts_file.write(shifts) From 35d97ed9bed0ccefa6d940d7d1d40a89b6ce0d24 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 4 Mar 2022 14:58:48 +0000 Subject: [PATCH 038/319] purge deploy key (#4615) --- .github/deploy_key.scitools-docs.enc | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .github/deploy_key.scitools-docs.enc diff --git a/.github/deploy_key.scitools-docs.enc b/.github/deploy_key.scitools-docs.enc deleted file mode 100644 index 165a7c1970..0000000000 --- a/.github/deploy_key.scitools-docs.enc +++ /dev/null @@ -1 +0,0 @@ -gAAAAABZSMeGIlHxHu4oCV_h8shbCRf1qJYoLO9Z0q9uKRDTlytoigzlvfxhN-9WMjc3Js1f1Zg55PfEpTOpL82p6QHF-gqW0k0qGjanO3lnQzM6EzIu3KyJPrVrL-O6edwoPMYKqwsNO3VQHNuEspsFKY0TbjnTPHc45SPU5LjEGX4c_SADSDcLDJm2rbrU2eVkT-gFHy_-ZzK0Di83WlDc79YzIkVe5BAn5PbWv3O9BROR4fJzecbjmWRT_rp1cqI_gaUpVcwTdRK3II9YnazBtW4h2WbCeTcySLD7N4o9K0P71SR6gG_XFbpML3Haf5IUdRi0qPBuvJ_4YVnnuJo6mhiIOJfUEcNj_bbLOYVzPmKyQMHvrPf_lK5JhdX6MUvqluhqHuc0i_z_j1O2y32lB7b1iiY6eE_BsNlXJHlOX1GiXkX0nZLI48p-D22jya44WshWSnVcoalcCDkdbvdFbpOscwXDR3nB-PCOmRUF_d1BlMbp1if-VP0yt3tJ_5yyCrqSRWwFusaibQTF6yoImetl7Am95hh2FjFDNkalHqtarnUv86w-26v1ukcTIjJ0iHzNbCK1m0VMkvE6uDeqRgIZnVKON5cesmM3YbulRrHpaOiSly_sMhLhfg5jTxAuOa319AQGoHEOcRLRUYdw2TQkDEiHGiUh_U4-nC7GTGDGcXyeBIa4ciuC2Qi0QXf9qyEGoIRcU8BP34LDNdtovJoZOBDzhr5Ajnu7yA3GB3TD_kiZrgm6agFuu7a51OMfjezhwGzUJ4X-empPctwm9woOJmPCTFqCvxB2VwVV0L6yngsTooyAHCi5st_AG-p5FIT3VZGx7EgCd68ze9XlRoACoe9XOdSFklbaSMGRbJlvKCPAA0zj4__PfIhlD8Cxwwjq_VXlSr_QxygIGZJlhkT46P9TroolgdipaBp1aQ3_PKHfgw5Y9ZqBKCZF5DOJejqUbfVKUp2JdqoX3yQBD0ByQFdfCuLvoiYcM2ofKdIMvel3Jwn0Nx4NYR2qg3h7FYti0jdrNlC89gnL4tKsf0DAGxZ1UYmqQMWJ3-GKCKrlKyeaHYB2djPRGP8VeoRZh_UorSNHU56KSztK_hTP6P0nFymRJRUSRBMKTaTfJf1aBlk9zJHSe9hOKwxyUNkwcTftGn5P0WNcnaTk3ecTVe-1QJKbPWwMBDzqQtTCsCizgN4UdQsmy4iMYq-LT2TC-JXXo0CPTNDybUj92wSa7KeKTvKnbN8DMZbGRdgy5BOSGw4hMIoIFSB-6tnBIvTntNfMT9ac9e9jKm47Q4qXpaeF3AsvBqxkMRQLaYVppPng6cA49VjJQDZ0gTdPKSSKZkApfeeQL0LLCGwzQ4C52TWK2NJSQ3pvRYI1F0taDQWopIiwFfox-OSYnOJECHkHjxaxhHQzVb3w47xKKZNXbLb-LV7QI-kGuKLfoqO1lq94cw1H-EVrXaGJcDDLjK2jRgdVfDyPsHMcW1oUDJqu8gQ6fCXYPbqJzdmFNFsc1hywHWCU7crV61D2QubwzbLRnP8053MvsMnbdhWtwocTlvvdG-qW6CiEA9Eanfpf0RW1W9oh6yQJ__0vS9UWswqq5ahkkpHY9LTE0US4L3xbFOrq7HgbA2jelTdPVfxo3BfUHuL8oKpFDTzgZi07gNmkhIZfpuXj2KFnm9XM31AsY6V2rXL0xSx-9rvi4FP0LK6V5vQ8OKI8aRPCDyzLUv2xnayMW4yaYg3GHD5yo7pIOswKc6GOEmetPnay3j0dVN3hfpkpfJWhss3vjZ2Zl0NmjJ7OuS25tjUGLy82A1yFSpL8mKRkHZJuMDZbd_Or6gaPVoVT_Otbkh-6pMZuDeOHOUfgey0Z374jCjRpyQ9k-Fpw8ykow8iIIQ088kC5CeQy6jRhD7mO3iR4-U1XKDJQNlNg1z_JYyDrwykp7FFN2sQn7RRYHIXx2iMrEDXdrdTrujMFN6omC13yDuXJukAgZb6zBBUTlonxRUBjUJWt2P-1sRRTsG8mr9EaE5K-xhR5Ust_37L3svNQ0vwLtPLIpWGZHhD8P_dYNR2RL4679xyzI8A7wLY82wFBHrcghAd4UtLJH9ul6IuS_CaVo-gbfowNRaQ0Zw7WHZGIXpZWEx1_zck6qDEaCY8TpQeciBWpH5uJDSYqdLdMwigdQEGzAJ1DHSWsyTrmOR7Lhwi9WqOzfWe4ahxAkAUH_Jdr_i-nGfl_x3OgQdHM7jWVMXDcXEmR0bkw-s0EKXCn20q2bxDkm5SUWkYtWAZ2aZRgo4wHOqGBcP99xZ25mq9uxtNOkLBF81lnVbn_4BAZBNnnKwwj4SafeIW4KR1ZOpnEI47sGUR6NhEk9VtJsv0zeZIv8VjRbNLh3QCxkNMue60SjJ48kjotZSX1RQJN0xwPftiABBf8MX9tyZe8emQvPeIcdQTSQPnYEUx22xZGeeJTNrZ9soQyP6mrkkRihp6o9tG7HT9QEVLGM19wAigwAAMMXGqdGzWwpar30JtJU94gAmIlwFUJqeO_fdJKFspnUyJ6gt5_oHsKNEV7Uz5EJwGpa94tlPJXjvZpu-wWQfu8U0trTU2mTCA0bmZIDID-Xk4vCW_SD4OVnsvWyga4QHSg3AqVTjnjlapAjsYcFjiOo2C_U3besloprpyuAwpTdn7zdfMHIJO0ckBFnXlk8XB3kT0YGrCpBvW6gYMXlnePVcr3wJehCvMg1Q9Dc5fVQUqt65zcjbgiudfzFGtTe9T4f1IttoAtrJgTN4W1mtbZzSK864I_ngaX5YWgZSinjkbocCCFEJDcbiXMnV7OWOZefqW6VZu4BZKEKlN9k2kH3UCECCK3uRAQIPn_48DgaVnAff2-fMADltiosSPJ_a3057acJP0cf-1QsJuV7r3zdzL3shgrMRjpSsSTCYdMhZ6disFGcJg7hJJvtH1FieZ76jps5FYi5lE8Ua9yBKlG4dCGuUBnikvpfy2FLMLFNn-iXLflu2oiBbcLvn_ReZUnFIR6KgGRN8xKEBaXATQVtb2E678GtQptK8PHP2DoAtbsIXUDn60YH04D9pEck8NnmWYAz7sWbiL6OKdaO7jQep4mt3CgkyFC0NCKP9zCbVNtmfHRVmHtckjgfHF-tK_v59KeAuwWPtm7ow2BjynAK42IGR9nWtQFRUZIboaND8UF76YGKFF7kOf_XTvoNrVTCRkD6b8KJy2IFfdoHP6WET9QLvwDSXgYLPlCX9z7aQ_lc57u5d_dGO-7NZ_Qbs69ByyIvQoztVBjw6fa7EzSwccqPfMQL_fiecNCng-r4gHaH6TlgSbfqQOISHxTtvmbym1no560ZsHfnQfuL6BCI8s6OoygxhOnQhaDqyOUVBut_x3VR_DKFMyUazXYNgLbRsdITaAvR-0gIx5TAX9n3A4HwHuiBZCtwRYaiJnW8FX9lk1Y_g5UHL2OC3rsNFui3aBLzAFhx58lALxnxhlUItuHHK9BgexnR2yCj2nOWLoWQzfFaf2_fpjEh_QBHTqUxdQZ8ighg_8lh6hmLbW4PcUxKX71RFmikLyS3-idlzsiEomNlPNaVllRF21vE6dR-nZ6xsxzTvNB4wumP2irQ9mFBTN1WpiLMyNoEEucA2I848YHUfkZrjTG_dcCQNp7H_2gKdIsZ135lUEG6lYfhLMHTmP5uYxxx3Pipjp6wF2GFCsZPIlIPsgrhbSxqkWg1EOViHtpw6ypFKn7wQHHfnrnHkFWnrKbMARVBjJUB-FhK4b6qLU_k_MTMipemneMUFXlj3EkEhKM18MIHGkIOkwG5QtPYcjUAf_2sZlxSMVnh6sQ8kVwF6lfk_l8jhoO93HUTntZUSv7GrE3s80yJgII4Qw37AdgcJiAkoPn1-17HfSsAy6uRh5-OvrCtkDqQxfuJSyn_4pRMh6hZT7N9pI5limMXXn2nHnxU93UT3qU-smA8q0ECfvK3JwoaYy_llSx0wSBvpmxjLQ302sFYM5FVZ9zRbHuLCCZShVopiyMDLHVJe_1g9Ou1KL-h6RVZgg3Ttyb5m2KDfoHEVLeZkW81YLCsyo7uNb6SVRM-615TIVGT6Eq7oJ6wO2LMDKjEpHKFiOFpY2fpR8noM81UqgLddYfl_lei7RVjaNO98otqE4iSNtpgJgyhAx4CdYm__yQRSXhckR4K7yAhM9Kh5BLbQQnf2_0WS1sWTmNMZZNMfOSqmTCRVwcYvg4TDGOA-vZARbZW1M7npVMldV_SbvgcEZD6InY9c40eheRqS0YD2W2HEZIiNeLRw0y5WBcYuJIpXhI3ViTXx-frJnv0Mo9uwmuLbJmWFcn6RdIVcU68_oPZZlZD4Vm7SjikbuZKF1BF3lXamTTDIBcWiDLwuNDv2lUkURDCWa5WJsfUCfTAJ6PTe8= \ No newline at end of file From 3820ae219b5d0d5cdca33fa3baa3655942714e68 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Tue, 8 Mar 2022 13:27:26 +0000 Subject: [PATCH 039/319] Remove no_clobber task from Refresh lockfiles Action (#4618) * Remove no_clobber task from Refresh lockfiles Action * review actions - add extra infor to pr message --- .github/workflows/refresh-lockfiles.yml | 43 ++----------------- .../contributing_ci_tests.rst | 13 +++--- 2 files changed, 8 insertions(+), 48 deletions(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 2be35e9f18..614bd7bb65 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -2,7 +2,7 @@ # available packages and dependencies. # # Environment specifications are given as conda environment.yml files found in -# `requirements/ci/py**.yml`. These state the pacakges required, the conda channels +# `requirements/ci/py**.yml`. These state the packages required, the conda channels # that the packages will be pulled from, and any versions of packages that need to be # pinned at specific versions. # @@ -14,12 +14,6 @@ name: Refresh Lockfiles on: workflow_dispatch: - inputs: - clobber: - description: | - Force the workflow to run, potentially clobbering any commits already made to the branch. - Enter "yes" or "true" to run. - default: "no" schedule: # Run once a week on a Saturday night # N.B. "should" be quoted, according to @@ -28,38 +22,6 @@ on: jobs: - - no_clobber: - if: "github.repository == 'SciTools/iris'" - runs-on: ubuntu-latest - steps: - # check if the auto-update-lockfiles branch exists. If it does, and someone other than - # the lockfile bot has made the head commit, abort the workflow. - # This job can be manually overridden by running directly from the github actions panel - # (known as a "workflow_dispatch") and setting the `clobber` input to "yes". - - uses: actions/script@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - if (context.eventName == "workflow_dispatch") { - const clobber = context.payload.inputs.clobber || "no"; - if (["yes", "true", "y"].includes(clobber.trim().toLowerCase())) { - core.info("Manual override, continuing workflow, potentially overwriting previous commits to auto-update-lockfiles"); - return - } - } - github.repos.getBranch({...context.repo, branch: "auto-update-lockfiles"}).then(res => { - const committer = res.data.commit.commit.committer; - if (committer && committer.name === "Lockfile bot") { - core.info("Lockfile bot was the last to push to auto-update-lockfiles. Continue."); - } else { - core.setFailed("New commits to auto-update-lockfiles since bot last ran. Abort!"); - } - }).catch(err => { - if (err.status === 404) { - core.info("auto-update-lockfiles branch not found, continue"); - } - }) gen_lockfiles: # this is a matrix job: it splits to create new lockfiles for each @@ -69,7 +31,6 @@ jobs: # ref: https://tomasvotruba.com/blog/2020/11/16/how-to-make-dynamic-matrix-in-github-actions/ if: "github.repository == 'SciTools/iris'" runs-on: ubuntu-latest - needs: no_clobber strategy: matrix: @@ -121,6 +82,8 @@ jobs: title: "[iris.ci] environment lockfiles auto-update" body: | Lockfiles updated to the latest resolvable environment. + + If the CI test suite fails, create a new branch based of this pull request and add the required fixes to that branch. labels: | New: Pull Request Bot diff --git a/docs/src/developers_guide/contributing_ci_tests.rst b/docs/src/developers_guide/contributing_ci_tests.rst index 0257ff7cff..46848166b3 100644 --- a/docs/src/developers_guide/contributing_ci_tests.rst +++ b/docs/src/developers_guide/contributing_ci_tests.rst @@ -72,14 +72,11 @@ New lockfiles are generated automatically each week to ensure that Iris continue tested against the latest available version of its dependencies. Each week the yaml files in ``requirements/ci`` are resolved by a GitHub Action. If the resolved environment has changed, a pull request is created with the new lock files. -The CI test suite will run on this pull request and fixes for failed tests can be pushed to -the ``auto-update-lockfiles`` branch to be included in the PR. -Once a developer has pushed to this branch, the auto-update process will not run again until -the PR is merged, to prevent overwriting developer commits. -The auto-updater can still be invoked manually in this situation by going to the `GitHub Actions`_ -page for the workflow, and manually running using the "Run Workflow" button. -By default, this will also not override developer commits. To force an update, you must -confirm "yes" in the "Run Worflow" prompt. +The CI test suite will run on this pull request. If the tests fail, a developer +will need to create a new branch based off the ``auto-update-lockfiles`` branch +and add the required fixes to this new branch. If the fixes are made to the +``auto-update-lockfiles`` branch these will be overwritten the next time the +Github Action is run. .. _skipping Cirrus-CI tasks: From 77462d2caa51fca8ee6faff7c5b0a154cb641c21 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 8 Mar 2022 15:15:10 +0000 Subject: [PATCH 040/319] Scalar Scatter Plot (#4616) * add failing test * pass test * initial review actions * test 2nd arg scalar * whatsnew --- docs/src/whatsnew/dev.rst | 3 ++ lib/iris/plot.py | 4 +- .../tests/unit/plot/test__get_plot_objects.py | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 lib/iris/tests/unit/plot/test__get_plot_objects.py diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index b9d5989bfc..5cc0769c57 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -41,6 +41,9 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ reverted part of the change from :pull:`3906` so that :func:`iris.plot.plot` no longer defaults to placing a "Y" coordinate (e.g. latitude) on the y-axis of the plot. (:issue:`4493`, :pull:`4601`) + +#. `@rcomer`_ enabled passing of scalar objects to :func:`~iris.plot.plot` and + :func:`~iris.plot.scatter`. (:pull:`4616`) 💣 Incompatible Changes diff --git a/lib/iris/plot.py b/lib/iris/plot.py index aefca889cf..d886ac1cf9 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -652,13 +652,13 @@ def _get_plot_objects(args): u_object, v_object = args[:2] u, v = _uv_from_u_object_v_object(u_object, v_object) args = args[2:] - if len(u) != len(v): + if u.size != v.size: msg = ( "The x and y-axis objects are not compatible. They should " "have equal sizes but got ({}: {}) and ({}: {})." ) raise ValueError( - msg.format(u_object.name(), len(u), v_object.name(), len(v)) + msg.format(u_object.name(), u.size, v_object.name(), v.size) ) else: # single argument diff --git a/lib/iris/tests/unit/plot/test__get_plot_objects.py b/lib/iris/tests/unit/plot/test__get_plot_objects.py new file mode 100644 index 0000000000..8586faa756 --- /dev/null +++ b/lib/iris/tests/unit/plot/test__get_plot_objects.py @@ -0,0 +1,45 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the `iris.plot._get_plot_objects` function.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import iris.cube + +if tests.MPL_AVAILABLE: + from iris.plot import _get_plot_objects + + +@tests.skip_plot +class Test__get_plot_objects(tests.IrisTest): + def test_scalar(self): + cube1 = iris.cube.Cube(1) + cube2 = iris.cube.Cube(1) + expected = (cube1, cube2, 1, 1, ()) + result = _get_plot_objects((cube1, cube2)) + self.assertTupleEqual(expected, result) + + def test_mismatched_size_first_scalar(self): + cube1 = iris.cube.Cube(1) + cube2 = iris.cube.Cube([1, 42]) + with self.assertRaisesRegex( + ValueError, "x and y-axis objects are not compatible" + ): + _get_plot_objects((cube1, cube2)) + + def test_mismatched_size_second_scalar(self): + cube1 = iris.cube.Cube(1) + cube2 = iris.cube.Cube([1, 42]) + with self.assertRaisesRegex( + ValueError, "x and y-axis objects are not compatible" + ): + _get_plot_objects((cube2, cube1)) + + +if __name__ == "__main__": + tests.main() From ad17088e9081ee81d8d8f55da4554aadeb1249a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Mar 2022 17:56:07 +0000 Subject: [PATCH 041/319] Updated environment lockfiles (#4624) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 95 +++++++++++---------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index caf6a739b3..a612138dfa 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 0b8e98b045b5545a96321ab961f5e97fe2da8aa929328cc8df2d4d5f33ed8159 +# input_hash: fc890d56b881193a2422ceb96d07b1b2bb857890e1d48fb24a765ec2f886d4d2 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 @@ -9,20 +9,20 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed3 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_12.tar.bz2#f547bf125ab234cec9c89491b262fc2f -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_12.tar.bz2#7ff3b832ba5e6918c0d026976359d065 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_13.tar.bz2#b62e87134ec17e1180cfcb3951624db4 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_13.tar.bz2#573a74710fad22a27da784cc238150b9 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-ha770c72_0.tar.bz2#56594fdd5a80774a80d546fbbccf2c03 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_12.tar.bz2#33c165be455015cc74e8d857182f3f58 -https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_12.tar.bz2#763c5ec8116d984b4a33342236d7da36 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_13.tar.bz2#a3a07a89af69d1eada078695b42e4961 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_13.tar.bz2#8e91f1f21417c9ab1265240ee4f9db1e https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2#561e277319a41d4f24f5c05a9ef63c04 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_12.tar.bz2#d34efbb8d7d6312c816b4bb647b818b1 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2#63eaf0f146cc80abd84743d48d667da4 https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.4-h9c3ff4c_0.tar.bz2#3cedab1fd76644efd516e1b271f2da95 +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.6-h27087fc_0.tar.bz2#90dec9e76bc164857cc200f81e981dab https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#fe9a66a351bfa7a84c3108304c7bcba5 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -30,13 +30,13 @@ https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.t https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h7f98852_0.tar.bz2#5c214edc675a7fb7cbb34b1d854e5141 +https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h7f98852_6.tar.bz2#b0f44f63f7d771d7670747a1dd5d5ac1 -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.8-h7f98852_0.tar.bz2#91d22aefa665265e8e31988b15145c8a +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.10-h7f98852_0.tar.bz2#ffa3a757a97e851293909b49f49f28fb https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 -https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.0-hf817b99_0.tar.bz2#b10bb2ebebfffa8800fa80ad3285719e https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 @@ -47,7 +47,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h36c2ea0_1013.tar.bz2#dcddf696ff5dfcab567100d691678e18 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpich-3.4.3-h846660c_100.tar.bz2#1bb747e2de717cb9a6501d72539d6556 +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.1-h846660c_100.tar.bz2#4b85205b094808088bb0862e08251653 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h9c3ff4c_0.tar.bz2#fb31bcb7af058244479ca635d20f0f4a https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1l-h7f98852_0.tar.bz2#de7b38a1542dbe6f41653a8ae71adc53 @@ -68,30 +68,32 @@ https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.t https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-13_linux64_openblas.tar.bz2#8a4038563ed92dfa622bd72c0d8f31d3 https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h7f98852_6.tar.bz2#c7c03a2592cac92246a13a0732bd1573 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h7f98852_6.tar.bz2#28bfe0a70154e6881da7bae97517c948 -https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.0-default_hc23dcda_0.tar.bz2#7b140452b5bc91e46410b84807307249 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h36c2ea0_1013.tar.bz2#cf7190238072a41e9579e4476a6a60b8 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h7f98852_6.tar.bz2#9e94bf16f14c78a36561d5019f490d22 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.2-h3790be6_4.tar.bz2#dbbd32092ee31aab0f2d213e8f9f1b40 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-13_linux64_openblas.tar.bz2#b17676dbd6688396c3a3076259fb7907 -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_1.tar.bz2#d03a54631298fd1ab732ff65f6ed3a07 +https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-13_linux64_openblas.tar.bz2#018b80e8f21d8560ae4961567e3e00c9 -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.46.0-h812cca2_0.tar.bz2#507fa47e9075f889af8e8b72925379be +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h6f004c6_2.tar.bz2#34fda41ca84e67232888c9a885903055 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-hfa10184_0.tar.bz2#aac17542e50a474e2e632878dc696d50 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.0-h9cd32fc_0.tar.bz2#eb66fc098824d25518a79e83d12a81d6 -https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.11-h27826a3_1.tar.bz2#84e76fb280e735fec1efd2d21fd9cb27 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h7f98852_6.tar.bz2#612385c4a83edb0619fe911d9da317f4 @@ -100,7 +102,8 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.b https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.6-h04a7f16_0.tar.bz2#b24a1e18325a6e8f8b6b4a2ec5860ce2 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.18.5-h9f60fe5_3.tar.bz2#511aa83cdfcc0132380db5daf2f15f27 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.2-hcc1bbae_3.tar.bz2#e29650992ae593bc05fc93722483e5c3 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.81.0-h2574ce0_0.tar.bz2#1f8655741d0269ca6756f131522da1e8 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/nss-3.74-hb5efdd6_0.tar.bz2#136876ca50177058594f6c2944e95c40 @@ -109,28 +112,29 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.ta https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.11-pyhd8ed1ab_0.tar.bz2#e51530e33440ea8044edb0076cb40a0f +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f +https://conda.anaconda.org/conda-forge/linux-64/curl-7.81.0-h2574ce0_0.tar.bz2#3a95d393b490f82aa406f1892fad84d9 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.4.2-pyhd8ed1ab_1.tar.bz2#d3f5797d3f9625c64860c93fc4359e64 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.13.94-ha180cfb_0.tar.bz2#c534c5248da4913002473919d76d0161 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.1.0-pyhd8ed1ab_0.tar.bz2#188e095f4dc38887bb48b065734b9e8d +https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.13.96-ha180cfb_0.tar.bz2#d190a1c55c84ba1c9a33484a38ece029 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.2.0-pyhd8ed1ab_0.tar.bz2#f31e31092035d427b05233ab924c7613 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.18.5-hf529b03_3.tar.bz2#524a9f1718bac53a6cf4906bcc51d044 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.81.0-h2574ce0_0.tar.bz2#1f8655741d0269ca6756f131522da1e8 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.1-hd57d9b9_1.tar.bz2#a7024916bfdf33a014a0cc803580c9a1 https://conda.anaconda.org/conda-forge/noarch/locket-0.2.0-py_2.tar.bz2#709e8671651c7ec3d1ad07800339ff1d https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/nose-1.3.7-py_1006.tar.bz2#382019d5f8e9362ef6f60a8d4e7bce8f https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.3.0-pyhd8ed1ab_0.tar.bz2#7bc119135be2a43e1701432399d8c28a +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 +https://conda.anaconda.org/conda-forge/linux-64/proj-8.2.1-h277dcde_0.tar.bz2#f2ceb1be6565c35e2db0ac948754751d https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.7-pyhd8ed1ab_0.tar.bz2#727e2216d9c47455d8ddc060eb2caad9 -https://conda.anaconda.org/conda-forge/noarch/pyshp-2.1.3-pyh44b312d_0.tar.bz2#2d1867b980785eb44b8122184d8b42a6 +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.2.0-pyhd8ed1ab_0.tar.bz2#2aa546be05be34b8e1744afd327b623f https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2021.3-pyhd8ed1ab_0.tar.bz2#7e4f811bff46a5a6a7e0094921389395 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 @@ -149,75 +153,72 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#7 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha00ac49_1009.tar.bz2#d1dff57b8731c245d3247b46d002e1c9 https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_1.tar.bz2#52a6cee65a5d10ed1c3f0af24fb48dd3 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a -https://conda.anaconda.org/conda-forge/linux-64/curl-7.81.0-h2574ce0_0.tar.bz2#3a95d393b490f82aa406f1892fad84d9 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h9c45103_3.tar.bz2#4f1a733e563d27b98010b62888e149c9 -https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.10.1-py38h578d9bd_0.tar.bz2#26da12e39b1b93e82fb865e967d0cbe0 +https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.2-py38h578d9bd_0.tar.bz2#0c1ffd6807cbf6c15456c49ca9baa668 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.3.2-py38h1fd1430_1.tar.bz2#085365abfe53d5d13bb68b1dda0b439e https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h3cfcdeb_1.tar.bz2#37d7568c595f0cfcd0c493f5ca0344ab -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.0.1-py38h497a2fe_1.tar.bz2#1ef7b5f4826ca48a15e2cd98a5c3436d +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.0-py38h0a891b7_1.tar.bz2#60eff55f2a845f35e58bd0be235fe4b7 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38he865349_0.tar.bz2#b1b3d6847a68251a1465206ab466b475 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.2-py38h6ae9a64_0.tar.bz2#065a900932f904e0182acfcfadc467e3 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_0.tar.bz2#90b4ee61abb81fb3f3995ec9d4c734f0 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-8.2.1-h277dcde_0.tar.bz2#f2ceb1be6565c35e2db0ac948754751d https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_4.tar.bz2#9c4bbee6f682f2fc7d7803df3996e77e https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-2.0.2-py38h497a2fe_1.tar.bz2#977d03222271270ea8fe35388bf13752 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h497a2fe_3.tar.bz2#131de7d638aa59fb8afbce59f1a8aa98 https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-ha98a1a1_5.tar.bz2#9b27fa0b1044a2119fb1b290617fe06f -https://conda.anaconda.org/conda-forge/linux-64/setuptools-60.7.1-py38h578d9bd_0.tar.bz2#8bf9c51a7e371df1673de909c1f46e6c +https://conda.anaconda.org/conda-forge/linux-64/setuptools-60.9.3-py38h578d9bd_0.tar.bz2#864b832ea94d9c0b37ddfbbb8adb42f1 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h497a2fe_2.tar.bz2#63b3b55c98b4239134e0be080f448944 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.13.0-py38h578d9bd_0.tar.bz2#561081f4a30990533541979c9ee84732 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.13.3-py38h578d9bd_0.tar.bz2#4f2dd671de7a8666acdc51a9dd6d4324 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h497a2fe_1003.tar.bz2#9189b42c42b9c87b2b2068cbe31901a8 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.5.2-py38h6c62de6_0.tar.bz2#73892e60ccea826c7f7a2215e48d22cf +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.1-py38h3e25421_0.tar.bz2#acc14d0d71dbf74f6a15f2456951b6cf -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.1.1-pyhd8ed1ab_0.tar.bz2#7968db84df10b74d9792d66d7da216df +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.2.1-pyhd8ed1ab_0.tar.bz2#0cb751f07e68fda1d631a02faa66f0de https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.29.1-py38h497a2fe_0.tar.bz2#121e02be214af4980911bb2cbd5b2742 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-3.3.1-hb4a5f5f_0.tar.bz2#abe529a4b140720078f0febe1b6014a4 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-3.4.0-hb4a5f5f_0.tar.bz2#42190c4597593e9742513d7b39b02c49 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.0.3-pyhd8ed1ab_0.tar.bz2#036d872c653780cb26e797e2e2f61b4c -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.0-py38h43a58ef_0.tar.bz2#23427f52c81076594a95c006ebf7552e -https://conda.anaconda.org/conda-forge/noarch/pip-22.0.3-pyhd8ed1ab_0.tar.bz2#45dedae69a0ea21cb8566d04b2ca5536 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.1-py38h43a58ef_0.tar.bz2#1083ebe2edc30e4fb9568d1f66e3588b +https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38h5383654_1.tar.bz2#5b600e019fa7c33be73bdb626236936b https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h6c62de6_1.tar.bz2#a350e3f4ca899e95122f66806e048858 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.2.0-py38h6c62de6_1.tar.bz2#2953d3fc0113fc6ffb955a5b72811fb0 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.7.3-py38h56a6a73_0.tar.bz2#2d318049369bb52d2687b0ac2be82751 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h596eeab_5.tar.bz2#ec3b783081e14a9dc0eb5ce609649728 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h1fd1430_1.tar.bz2#c494f75082f9c052944fda1b22c83336 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 -https://conda.anaconda.org/conda-forge/noarch/identify-2.4.8-pyhd8ed1ab_0.tar.bz2#d4d25c0b7c1a7a1b0442e061fdd49260 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/noarch/identify-2.4.11-pyhd8ed1ab_0.tar.bz2#979d7dfda4d04702391e80158c322039 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.3-h9967ed3_0.tar.bz2#37f1c68380bc5dfe0f5bb2655e207a73 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.5-h4dcc4a0_0.tar.bz2#56ce3e3bec0d5c9e6db22083a3ef5e13 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38ha217159_3.tar.bz2#d7461e191f7a0522e4709612786bdf4e -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_2.tar.bz2#aa768fdaad03509a97df37f81163346b https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.17.0-py38h578d9bd_0.tar.bz2#839ac9dba9a6126c9532781a9ea4506b https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.8-pyhd8ed1ab_1.tar.bz2#53f1387c68c21cecb386e2cde51b3f7c -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-2.50.0-h8e749b2_2.tar.bz2#8c20fd968c8b6af73444b1199d5fb0cb +https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_0.tar.bz2#e5521af56c6e927397ca9851eecb2f48 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.1-py38h578d9bd_0.tar.bz2#0d78be9cf1c400ba8e3077cf060492f1 https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.4.0-pyh6c4a22f_1.tar.bz2#a9025d14c2a609e0d895ad3e75b5369c -https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.4.0-pyhd8ed1ab_0.tar.bz2#80fd2cc25ad45911b4e42d5b91593e2f +https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-1.0.0-pyhd8ed1ab_0.tar.bz2#9f633f2f2869184e31acfeae95b24345 From 45f188bb45d96dfdd064020c6cbd949b0807198c Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 9 Mar 2022 16:01:35 +0000 Subject: [PATCH 042/319] Overnight benchmarks - find a valid issue assignee (#4627) * Overnight benchmarks find a valid issue assignee. * Overnight benchmarks safer check for a valid issue assignee. --- .github/workflows/benchmark.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6b155fc8bc..04e26686ea 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -81,7 +81,16 @@ jobs: do commit="${commit_file%.*}" pr_number=$(git log "$commit"^! --oneline | grep -o "#[0-9]*" | tail -1 | cut -c 2-) - assignee=$(gh pr view $pr_number --json author -q '.["author"]["login"]' --repo $GITHUB_REPOSITORY) + author=$(gh pr view $pr_number --json author -q '.["author"]["login"]' --repo $GITHUB_REPOSITORY) + merger=$(gh pr view $pr_number --json mergedBy -q '.["mergedBy"]["login"]' --repo $GITHUB_REPOSITORY) + # Find a valid assignee from author/merger/nothing. + if curl -s https://api.github.com/users/$author | grep -q "login"; then + assignee=$author + elif curl -s https://api.github.com/users/$merger | grep -q "login"; then + assignee=$merger + else + assignee="" + fi title="Performance Shift(s): \`$commit\`" body=" Benchmark comparison has identified performance shifts at commit \ From 63775948fac6fa5153610fdaefa9c62f9aba12b6 Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Thu, 10 Mar 2022 07:38:43 +0000 Subject: [PATCH 043/319] Votable Issues (#4617) * Create test2.yml * gha test * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * Update refresh-votable-issues.yml * initial * tidy * updated whatsnew * removed defunct ext links * Update docs/src/votable_issues.rst Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> * added suggestion to subscribe to voted issues. * Renamed votable to voted. Layout tweaks to the voted issues page. Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- docs/src/common_links.inc | 1 + docs/src/conf.py | 9 +++++++ docs/src/index.rst | 1 + docs/src/voted_issues.rst | 55 +++++++++++++++++++++++++++++++++++++++ docs/src/whatsnew/dev.rst | 3 ++- 5 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 docs/src/voted_issues.rst diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index 67fc493e3e..ce7f498d80 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -38,6 +38,7 @@ .. _using git: https://docs.github.com/en/github/using-git .. _requirements/ci/: https://github.com/SciTools/iris/tree/main/requirements/ci .. _CF-UGRID: https://ugrid-conventions.github.io/ugrid-conventions/ +.. _issues on GitHub: https://github.com/SciTools/iris/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc .. comment diff --git a/docs/src/conf.py b/docs/src/conf.py index db2cdc3633..9c379ea730 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -302,6 +302,15 @@ def _dotv(version): html_static_path = ["_static"] html_style = "theme_override.css" +# this allows for using datatables: https://datatables.net/ +html_css_files = [ + "https://cdn.datatables.net/1.10.23/css/jquery.dataTables.min.css", +] + +html_js_files = [ + "https://cdn.datatables.net/1.10.23/js/jquery.dataTables.min.js", +] + # url link checker. Some links work but report as broken, lets ignore them. # See https://www.sphinx-doc.org/en/1.2/config.html#options-for-the-linkcheck-builder linkcheck_ignore = [ diff --git a/docs/src/index.rst b/docs/src/index.rst index e6a787a220..d247b93411 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -165,3 +165,4 @@ For **Iris 2.4** and earlier documentation please see the generated/api/iris techpapers/index copyright + voted_issues diff --git a/docs/src/voted_issues.rst b/docs/src/voted_issues.rst new file mode 100644 index 0000000000..edc1c860a2 --- /dev/null +++ b/docs/src/voted_issues.rst @@ -0,0 +1,55 @@ +.. include:: common_links.inc + +.. _voted_issues: + +Voted Issues +============ + +You can help us to prioritise development of new features by leaving a 👍 +reaction on the header (not subsequent comments) of any issue. + +.. tip:: We suggest you subscribe to the issue so you will be updated. + When viewing the issue there is a **Notifications** + section where you can select to subscribe. + +Below is a sorted table of all issues that have 1 or more 👍 from our github +project. Please note that there is more development activity than what is on +the below table. + +.. _voted-issues.json: https://github.com/scitools/voted_issues/blob/main/voted-issues.json + +.. raw:: html + + + + + + + + + + +
👍IssueAuthorTitle
+ + + + +

+ +.. note:: The data in this table is updated daily and is sourced from + `voted-issues.json`_. + For the latest data please see the `issues on GitHub`_. + Note that the list on Github does not show the number of votes 👍 + only the total number of comments for the whole issue. \ No newline at end of file diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 5cc0769c57..a9e960c173 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -75,7 +75,8 @@ This document explains the changes made to Iris for this release 📚 Documentation ================ -#. N/A +#. `@tkknight`_ added a page to show the issues that have been voted for. See + :ref:`voted_issues`. (:issue:`3307`, :pull:`4617`) 💼 Internal From 9e5575babdcdd78585d68f4541dbcd60c01c2596 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 10 Mar 2022 16:35:40 +0000 Subject: [PATCH 044/319] Avoid dimensionality mismatch between data array and CF variables. (#4610) --- docs/src/whatsnew/3.2.rst | 6 +++++- lib/iris/fileformats/netcdf.py | 8 ++++++++ .../unit/fileformats/netcdf/test_Saver.py | 18 +++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index 977f373ba8..fad2c61b1d 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -44,6 +44,10 @@ v3.2.1 |build_date| [unreleased] attribute from a given CRS instead of creating a new `ccrs.Globe()` object. Iris can now handle non-Earth semi-major axes, as discussed in :issue:`4582` (:pull:`4605`). + #. `@trexfeathers`_ avoided a dimensionality mismatch when streaming the + :attr:`~iris.coords.Coord.bounds` array for a scalar + :class:`~iris.coords.Coord`. (:pull:`4610`). + 💼 **Internal** #. N/A @@ -198,7 +202,7 @@ v3.2.1 |build_date| [unreleased] from assuming the globe to be the Earth (:issue:`4408`, :pull:`4497`) #. `@rcomer`_ corrected the ``long_name`` mapping from UM stash code ``m01s09i215`` - to indicate cloud fraction greater than 7.9 oktas, rather than 7.5 + to indicate cloud fraction greater than 7.9 oktas, rather than 7.5 (:issue:`3305`, :pull:`4535`) #. `@lbdreyer`_ fixed a bug in :class:`iris.io.load_http` which was missing an import diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index dd819fb63f..8050fc9b72 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -2865,6 +2865,14 @@ def _increment_name(self, varname): @staticmethod def _lazy_stream_data(data, fill_value, fill_warn, cf_var): + if hasattr(data, "shape") and data.shape == (1,) + cf_var.shape: + # (Don't do this check for string data). + # Reduce dimensionality where the data array has an extra dimension + # versus the cf_var - to avoid a broadcasting ambiguity. + # Happens when bounds data is for a scalar point - array is 2D but + # contains just 1 row, so the cf_var is 1D. + data = data.squeeze(axis=0) + if is_lazy_data(data): def store(data, cf_var, fill_value): diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py index ee814ea168..6b534b52b5 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py @@ -30,7 +30,7 @@ TransverseMercator, VerticalPerspective, ) -from iris.coords import DimCoord +from iris.coords import AuxCoord, DimCoord from iris.cube import Cube from iris.fileformats.netcdf import Saver import iris.tests.stock as stock @@ -299,6 +299,22 @@ def test_with_climatology(self): saver.write(cube) self.assertCDL(nc_path) + def test_dimensional_to_scalar(self): + # Bounds for 1 point are still in a 2D array. + scalar_bounds = self.array_lib.arange(2).reshape(1, 2) + scalar_point = scalar_bounds.mean() + scalar_data = self.array_lib.zeros(1) + scalar_coord = AuxCoord(points=scalar_point, bounds=scalar_bounds) + cube = Cube(scalar_data, aux_coords_and_dims=[(scalar_coord, 0)])[0] + with self.temp_filename(".nc") as nc_path: + with Saver(nc_path, "NETCDF4") as saver: + saver.write(cube) + ds = nc.Dataset(nc_path) + # Confirm that the only dimension is the one denoting the number + # of bounds - have successfully saved the 2D bounds array into 1D. + self.assertEqual(["bnds"], list(ds.dimensions.keys())) + ds.close() + class Test__create_cf_bounds(tests.IrisTest): # Method is substituted in test_Saver__lazy. From c27f524e257992c3a9e9d8bb1e25bacbe85afdaf Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 10 Mar 2022 16:55:08 +0000 Subject: [PATCH 045/319] Sperf & Cperf Benchmarks (#4621) --- benchmarks/README.md | 23 +- benchmarks/asv.conf.json | 1 + benchmarks/benchmarks/__init__.py | 61 ++++- benchmarks/benchmarks/cperf/__init__.py | 97 +++++++ benchmarks/benchmarks/cperf/equality.py | 58 ++++ benchmarks/benchmarks/cperf/load.py | 57 ++++ benchmarks/benchmarks/cperf/save.py | 47 ++++ .../{ugrid.py => ugrid/__init__.py} | 4 +- .../ugrid}/regions_combine.py | 41 +-- .../benchmarks/generate_data/__init__.py | 80 ------ benchmarks/benchmarks/generate_data/stock.py | 68 +++-- benchmarks/benchmarks/generate_data/ugrid.py | 195 ++++++++++++++ .../{loading.py => load/__init__.py} | 4 +- .../{ugrid_load.py => load/ugrid.py} | 2 +- .../benchmarks/{netcdf_save.py => save.py} | 21 +- benchmarks/benchmarks/sperf/__init__.py | 39 +++ .../benchmarks/sperf/combine_regions.py | 250 ++++++++++++++++++ benchmarks/benchmarks/sperf/equality.py | 36 +++ benchmarks/benchmarks/sperf/load.py | 32 +++ benchmarks/benchmarks/sperf/save.py | 56 ++++ .../asv_example_images/commits.png | Bin 0 -> 166632 bytes .../asv_example_images/comparison.png | Bin 0 -> 17405 bytes .../asv_example_images/scalability.png | Bin 0 -> 46256 bytes .../contributing_benchmarks.rst | 62 +++++ .../contributing_testing_index.rst | 1 + docs/src/whatsnew/dev.rst | 5 +- noxfile.py | 56 +++- 27 files changed, 1141 insertions(+), 155 deletions(-) create mode 100644 benchmarks/benchmarks/cperf/__init__.py create mode 100644 benchmarks/benchmarks/cperf/equality.py create mode 100644 benchmarks/benchmarks/cperf/load.py create mode 100644 benchmarks/benchmarks/cperf/save.py rename benchmarks/benchmarks/experimental/{ugrid.py => ugrid/__init__.py} (98%) rename benchmarks/benchmarks/{ => experimental/ugrid}/regions_combine.py (90%) create mode 100644 benchmarks/benchmarks/generate_data/ugrid.py rename benchmarks/benchmarks/{loading.py => load/__init__.py} (97%) rename benchmarks/benchmarks/{ugrid_load.py => load/ugrid.py} (98%) rename benchmarks/benchmarks/{netcdf_save.py => save.py} (78%) create mode 100644 benchmarks/benchmarks/sperf/__init__.py create mode 100644 benchmarks/benchmarks/sperf/combine_regions.py create mode 100644 benchmarks/benchmarks/sperf/equality.py create mode 100644 benchmarks/benchmarks/sperf/load.py create mode 100644 benchmarks/benchmarks/sperf/save.py create mode 100644 docs/src/developers_guide/asv_example_images/commits.png create mode 100644 docs/src/developers_guide/asv_example_images/comparison.png create mode 100644 docs/src/developers_guide/asv_example_images/scalability.png create mode 100644 docs/src/developers_guide/contributing_benchmarks.rst diff --git a/benchmarks/README.md b/benchmarks/README.md index baa1afe700..6ea53c3ae8 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -21,13 +21,20 @@ automated overnight run locally. See the session docstring for detail. ### Environment variables -* ``DATA_GEN_PYTHON`` - required - path to a Python executable that can be +* `OVERRIDE_TEST_DATA_REPOSITORY` - required - some benchmarks use +`iris-test-data` content, and your local `site.cfg` is not available for +benchmark scripts. +* `DATA_GEN_PYTHON` - required - path to a Python executable that can be used to generate benchmark test objects/files; see [Data generation](#data-generation). The Nox session sets this automatically, but will defer to any value already set in the shell. -* ``BENCHMARK_DATA`` - optional - path to a directory for benchmark synthetic +* `BENCHMARK_DATA` - optional - path to a directory for benchmark synthetic test data, which the benchmark scripts will create if it doesn't already -exist. Defaults to ``/benchmarks/.data/`` if not set. +exist. Defaults to `/benchmarks/.data/` if not set. +* `ON_DEMAND_BENCHMARKS` - optional - when set (to any value): benchmarks +decorated with `@on_demand_benchmark` are included in the ASV run. Usually +coupled with the ASV `--bench` argument to only run the benchmark(s) of +interest. Is set during the Nox `cperf` and `sperf` sessions. ## Writing benchmarks @@ -65,6 +72,16 @@ be significantly larger (e.g. a 1000x1000 `Cube`). Performance differences might only be seen for the larger value, or the smaller, or both, getting you closer to the root cause. +### On-demand benchmarks + +Some benchmarks provide useful insight but are inappropriate to be included in +a benchmark run by default, e.g. those with long run-times or requiring a local +file. These benchmarks should be decorated with `@on_demand_benchmark` +(see [benchmarks init](./benchmarks/__init__.py)), which +sets the benchmark to only be included in a run when the `ON_DEMAND_BENCHMARKS` +environment variable is set. Examples include the CPerf and SPerf benchmark +suites for the UK Met Office NG-VAT project. + ## Benchmark environments We have disabled ASV's standard environment management, instead using an diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json index 3468b2fca9..7337eaa8c7 100644 --- a/benchmarks/asv.conf.json +++ b/benchmarks/asv.conf.json @@ -5,6 +5,7 @@ "repo": "..", "environment_type": "conda-delegated", "show_commit_url": "http://github.com/scitools/iris/commit/", + "branches": ["upstream/main"], "benchmark_dir": "./benchmarks", "env_dir": ".asv/env", diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py index 38502c9306..765eb2195d 100644 --- a/benchmarks/benchmarks/__init__.py +++ b/benchmarks/benchmarks/__init__.py @@ -4,10 +4,10 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """Common code for benchmarks.""" +from functools import wraps +from os import environ import resource -from .generate_data import BENCHMARK_DATA, run_function_elsewhere - ARTIFICIAL_DIM_SIZE = int(10e3) # For all artificial cubes, coords etc. @@ -70,3 +70,60 @@ def __exit__(self, *_): def addedmem_mb(self): """Return measured memory growth, in Mb.""" return self.mb_after - self.mb_before + + @staticmethod + def decorator(changed_params: list = None): + """ + Decorates this benchmark to track growth in resident memory during execution. + + Intended for use on ASV ``track_`` benchmarks. Applies the + :class:`TrackAddedMemoryAllocation` context manager to the benchmark + code, sets the benchmark ``unit`` attribute to ``Mb``. Optionally + replaces the benchmark ``params`` attribute with ``changed_params`` - + useful to avoid testing very small memory volumes, where the results + are vulnerable to noise. + + Parameters + ---------- + changed_params : list + Replace the benchmark's ``params`` attribute with this list. + + """ + if changed_params: + # Must make a copy for re-use safety! + _changed_params = list(changed_params) + else: + _changed_params = None + + def _inner_decorator(decorated_func): + @wraps(decorated_func) + def _inner_func(*args, **kwargs): + assert decorated_func.__name__[:6] == "track_" + # Run the decorated benchmark within the added memory context manager. + with TrackAddedMemoryAllocation() as mb: + decorated_func(*args, **kwargs) + return mb.addedmem_mb() + + if _changed_params: + # Replace the params if replacement provided. + _inner_func.params = _changed_params + _inner_func.unit = "Mb" + return _inner_func + + return _inner_decorator + + +def on_demand_benchmark(benchmark_object): + """ + Decorator. Disables these benchmark(s) unless ON_DEMAND_BENCHARKS env var is set. + + For benchmarks that, for whatever reason, should not be run by default. + E.g: + * Require a local file + * Used for scalability analysis instead of commit monitoring. + + Can be applied to benchmark classes/methods/functions. + + """ + if "ON_DEMAND_BENCHMARKS" in environ: + return benchmark_object diff --git a/benchmarks/benchmarks/cperf/__init__.py b/benchmarks/benchmarks/cperf/__init__.py new file mode 100644 index 0000000000..fb311c44dc --- /dev/null +++ b/benchmarks/benchmarks/cperf/__init__.py @@ -0,0 +1,97 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Benchmarks for the CPerf scheme of the UK Met Office's NG-VAT project. + +CPerf = comparing performance working with data in UM versus LFRic formats. + +Files available from the UK Met Office: + moo ls moose:/adhoc/projects/avd/asv/data_for_nightly_tests/ +""" +import numpy as np + +from iris import load_cube + +# TODO: remove uses of PARSE_UGRID_ON_LOAD once UGRID parsing is core behaviour. +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD + +from ..generate_data import BENCHMARK_DATA +from ..generate_data.ugrid import make_cubesphere_testfile + +# The data of the core test UM files has dtype=np.float32 shape=(1920, 2560) +_UM_DIMS_YX = (1920, 2560) +# The closest cubesphere size in terms of datapoints is sqrt(1920*2560 / 6) +# This gives ~= 905, i.e. "C905" +_N_CUBESPHERE_UM_EQUIVALENT = int(np.sqrt(np.prod(_UM_DIMS_YX) / 6)) + + +class SingleDiagnosticMixin: + """For use in any benchmark classes that work on a single diagnostic file.""" + + params = [ + ["LFRic", "UM", "UM_lbpack0", "UM_netcdf"], + [False, True], + [False, True], + ] + param_names = ["file type", "height dim (len 71)", "time dim (len 3)"] + + def setup(self, file_type, three_d, three_times): + if file_type == "LFRic": + # Generate an appropriate synthetic LFRic file. + if three_times: + n_times = 3 + else: + n_times = 1 + + # Use a cubesphere size ~equivalent to our UM test data. + cells_per_panel_edge = _N_CUBESPHERE_UM_EQUIVALENT + create_kwargs = dict(c_size=cells_per_panel_edge, n_times=n_times) + + if three_d: + create_kwargs["n_levels"] = 71 + + # Will re-use a file if already present. + file_path = make_cubesphere_testfile(**create_kwargs) + + else: + # Locate the appropriate UM file. + if three_times: + # pa/pb003 files + numeric = "003" + else: + # pa/pb000 files + numeric = "000" + + if three_d: + # theta diagnostic, N1280 file w/ 71 levels (1920, 2560, 71) + file_name = f"umglaa_pb{numeric}-theta" + else: + # surface_temp diagnostic, N1280 file (1920, 2560) + file_name = f"umglaa_pa{numeric}-surfacetemp" + + file_suffices = { + "UM": "", # packed FF (WGDOS lbpack = 1) + "UM_lbpack0": ".uncompressed", # unpacked FF (lbpack = 0) + "UM_netcdf": ".nc", # UM file -> Iris -> NetCDF file + } + suffix = file_suffices[file_type] + + file_path = (BENCHMARK_DATA / file_name).with_suffix(suffix) + if not file_path.exists(): + message = "\n".join( + [ + f"Expected local file not found: {file_path}", + "Available from the UK Met Office.", + ] + ) + raise FileNotFoundError(message) + + self.file_path = file_path + self.file_type = file_type + + def load(self): + with PARSE_UGRID_ON_LOAD.context(): + return load_cube(str(self.file_path)) diff --git a/benchmarks/benchmarks/cperf/equality.py b/benchmarks/benchmarks/cperf/equality.py new file mode 100644 index 0000000000..47eb255513 --- /dev/null +++ b/benchmarks/benchmarks/cperf/equality.py @@ -0,0 +1,58 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Equality benchmarks for the CPerf scheme of the UK Met Office's NG-VAT project. +""" +from . import SingleDiagnosticMixin +from .. import on_demand_benchmark + + +class EqualityMixin(SingleDiagnosticMixin): + """ + Uses :class:`SingleDiagnosticMixin` as the realistic case will be comparing + :class:`~iris.cube.Cube`\\ s that have been loaded from file. + """ + + # Cut down the parent parameters. + params = [["LFRic", "UM"]] + + def setup(self, file_type, three_d=False, three_times=False): + super().setup(file_type, three_d, three_times) + self.cube = self.load() + self.other_cube = self.load() + + +@on_demand_benchmark +class CubeEquality(EqualityMixin): + """ + Benchmark time and memory costs of comparing LFRic and UM + :class:`~iris.cube.Cube`\\ s. + """ + + def _comparison(self): + _ = self.cube == self.other_cube + + def peakmem_eq(self, file_type): + self._comparison() + + def time_eq(self, file_type): + self._comparison() + + +@on_demand_benchmark +class MeshEquality(EqualityMixin): + """Provides extra context for :class:`CubeEquality`.""" + + params = [["LFRic"]] + + def _comparison(self): + _ = self.cube.mesh == self.other_cube.mesh + + def peakmem_eq(self, file_type): + self._comparison() + + def time_eq(self, file_type): + self._comparison() diff --git a/benchmarks/benchmarks/cperf/load.py b/benchmarks/benchmarks/cperf/load.py new file mode 100644 index 0000000000..04bb7e1a61 --- /dev/null +++ b/benchmarks/benchmarks/cperf/load.py @@ -0,0 +1,57 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +File loading benchmarks for the CPerf scheme of the UK Met Office's NG-VAT project. +""" +from . import SingleDiagnosticMixin +from .. import on_demand_benchmark + + +@on_demand_benchmark +class SingleDiagnosticLoad(SingleDiagnosticMixin): + def time_load(self, _, __, ___): + """ + The 'real world comparison' + * UM coords are always realised (DimCoords). + * LFRic coords are not realised by default (MeshCoords). + + """ + cube = self.load() + assert cube.has_lazy_data() + # UM files load lon/lat as DimCoords, which are always realised. + expecting_lazy_coords = self.file_type == "LFRic" + for coord_name in "longitude", "latitude": + coord = cube.coord(coord_name) + assert coord.has_lazy_points() == expecting_lazy_coords + assert coord.has_lazy_bounds() == expecting_lazy_coords + + def time_load_w_realised_coords(self, _, __, ___): + """A valuable extra comparison where both UM and LFRic coords are realised.""" + cube = self.load() + for coord_name in "longitude", "latitude": + coord = cube.coord(coord_name) + # Don't touch actual points/bounds objects - permanent + # realisation plays badly with ASV's re-run strategy. + if coord.has_lazy_points(): + coord.core_points().compute() + if coord.has_lazy_bounds(): + coord.core_bounds().compute() + + +@on_demand_benchmark +class SingleDiagnosticRealise(SingleDiagnosticMixin): + # The larger files take a long time to realise. + timeout = 600.0 + + def setup(self, file_type, three_d, three_times): + super().setup(file_type, three_d, three_times) + self.loaded_cube = self.load() + + def time_realise(self, _, __, ___): + # Don't touch loaded_cube.data - permanent realisation plays badly with + # ASV's re-run strategy. + assert self.loaded_cube.has_lazy_data() + self.loaded_cube.core_data().compute() diff --git a/benchmarks/benchmarks/cperf/save.py b/benchmarks/benchmarks/cperf/save.py new file mode 100644 index 0000000000..63eb5c25fb --- /dev/null +++ b/benchmarks/benchmarks/cperf/save.py @@ -0,0 +1,47 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +File saving benchmarks for the CPerf scheme of the UK Met Office's NG-VAT project. +""" + +from iris import save + +from . import _N_CUBESPHERE_UM_EQUIVALENT, _UM_DIMS_YX +from .. import TrackAddedMemoryAllocation, on_demand_benchmark +from ..generate_data.ugrid import ( + make_cube_like_2d_cubesphere, + make_cube_like_umfield, +) + + +@on_demand_benchmark +class NetcdfSave: + """ + Benchmark time and memory costs of saving ~large-ish data cubes to netcdf. + Parametrised by file type. + + """ + + params = ["LFRic", "UM"] + param_names = ["data type"] + + def setup(self, data_type): + if data_type == "LFRic": + self.cube = make_cube_like_2d_cubesphere( + n_cube=_N_CUBESPHERE_UM_EQUIVALENT, with_mesh=True + ) + else: + self.cube = make_cube_like_umfield(_UM_DIMS_YX) + + def _save_data(self, cube): + save(cube, "tmp.nc") + + def time_save_data_netcdf(self, data_type): + self._save_data(self.cube) + + @TrackAddedMemoryAllocation.decorator() + def track_addedmem_save_data_netcdf(self, data_type): + self._save_data(self.cube) diff --git a/benchmarks/benchmarks/experimental/ugrid.py b/benchmarks/benchmarks/experimental/ugrid/__init__.py similarity index 98% rename from benchmarks/benchmarks/experimental/ugrid.py rename to benchmarks/benchmarks/experimental/ugrid/__init__.py index 609abbe77c..3e5f1ae440 100644 --- a/benchmarks/benchmarks/experimental/ugrid.py +++ b/benchmarks/benchmarks/experimental/ugrid/__init__.py @@ -14,8 +14,8 @@ from iris.experimental import ugrid -from .. import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup -from ..generate_data.stock import sample_mesh +from ... import ARTIFICIAL_DIM_SIZE, disable_repeat_between_setup +from ...generate_data.stock import sample_mesh class UGridCommon: diff --git a/benchmarks/benchmarks/regions_combine.py b/benchmarks/benchmarks/experimental/ugrid/regions_combine.py similarity index 90% rename from benchmarks/benchmarks/regions_combine.py rename to benchmarks/benchmarks/experimental/ugrid/regions_combine.py index a99dc57263..0cac84d0a8 100644 --- a/benchmarks/benchmarks/regions_combine.py +++ b/benchmarks/benchmarks/experimental/ugrid/regions_combine.py @@ -24,8 +24,8 @@ from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from iris.experimental.ugrid.utils import recombine_submeshes -from . import TrackAddedMemoryAllocation -from .generate_data import make_cube_like_2d_cubesphere +from ... import TrackAddedMemoryAllocation +from ...generate_data.ugrid import make_cube_like_2d_cubesphere class MixinCombineRegions: @@ -33,6 +33,8 @@ class MixinCombineRegions: # operations on cubesphere-like test data. params = [4, 500] param_names = ["cubesphere-N"] + # For use on 'track_addedmem_..' type benchmarks - result is too noisy. + no_small_params = params[1:] def _parametrised_cache_filename(self, n_cubesphere, content_name): return f"cube_C{n_cubesphere}_{content_name}.nc" @@ -188,13 +190,9 @@ def setup(self, n_cubesphere): def time_create_combined_cube(self, n_cubesphere): self.recombine() + @TrackAddedMemoryAllocation.decorator(MixinCombineRegions.no_small_params) def track_addedmem_create_combined_cube(self, n_cubesphere): - with TrackAddedMemoryAllocation() as mb: - self.recombine() - return mb.addedmem_mb() - - -CombineRegionsCreateCube.track_addedmem_create_combined_cube.unit = "Mb" + self.recombine() class CombineRegionsComputeRealData(MixinCombineRegions): @@ -203,16 +201,11 @@ class CombineRegionsComputeRealData(MixinCombineRegions): """ def time_compute_data(self, n_cubesphere): - self.recombined_cube.data + _ = self.recombined_cube.data + @TrackAddedMemoryAllocation.decorator(MixinCombineRegions.no_small_params) def track_addedmem_compute_data(self, n_cubesphere): - with TrackAddedMemoryAllocation() as mb: - self.recombined_cube.data - - return mb.addedmem_mb() - - -CombineRegionsComputeRealData.track_addedmem_compute_data.unit = "Mb" + _ = self.recombined_cube.data class CombineRegionsSaveData(MixinCombineRegions): @@ -227,18 +220,15 @@ def time_save(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. save(self.recombined_cube, "tmp.nc") + @TrackAddedMemoryAllocation.decorator(MixinCombineRegions.no_small_params) def track_addedmem_save(self, n_cubesphere): - with TrackAddedMemoryAllocation() as mb: - save(self.recombined_cube, "tmp.nc") - - return mb.addedmem_mb() + save(self.recombined_cube, "tmp.nc") def track_filesize_saved(self, n_cubesphere): save(self.recombined_cube, "tmp.nc") return os.path.getsize("tmp.nc") * 1.0e-6 -CombineRegionsSaveData.track_addedmem_save.unit = "Mb" CombineRegionsSaveData.track_filesize_saved.unit = "Mb" @@ -258,11 +248,6 @@ def time_stream_file2file(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. save(self.recombined_cube, "tmp.nc") + @TrackAddedMemoryAllocation.decorator(MixinCombineRegions.no_small_params) def track_addedmem_stream_file2file(self, n_cubesphere): - with TrackAddedMemoryAllocation() as mb: - save(self.recombined_cube, "tmp.nc") - - return mb.addedmem_mb() - - -CombineRegionsFileStreamedCalc.track_addedmem_stream_file2file.unit = "Mb" + save(self.recombined_cube, "tmp.nc") diff --git a/benchmarks/benchmarks/generate_data/__init__.py b/benchmarks/benchmarks/generate_data/__init__.py index 125e2e1b53..8874a2c214 100644 --- a/benchmarks/benchmarks/generate_data/__init__.py +++ b/benchmarks/benchmarks/generate_data/__init__.py @@ -22,11 +22,8 @@ from pathlib import Path from subprocess import CalledProcessError, check_output, run from textwrap import dedent -from typing import Iterable -from iris import load_cube as iris_loadcube from iris._lazy_data import as_concrete_data -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from iris.fileformats import netcdf #: Python executable used by :func:`run_function_elsewhere`, set via env @@ -101,83 +98,6 @@ def run_function_elsewhere(func_to_run, *args, **kwargs): return result.stdout -def generate_cube_like_2d_cubesphere( - n_cube: int, with_mesh: bool, output_path: str -): - """ - Construct and save to file an LFRIc cubesphere-like cube for a given - cubesphere size, *or* a simpler structured (UM-like) cube of equivalent - size. - - NOTE: this function is *NEVER* called from within this actual package. - Instead, it is to be called via benchmarks.remote_data_generation, - so that it can use up-to-date facilities, independent of the ASV controlled - environment which contains the "Iris commit under test". - This means: - * it must be completely self-contained : i.e. it includes all its - own imports, and saves results to an output file. - - """ - from iris import save - from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube - - n_face_nodes = n_cube * n_cube - n_faces = 6 * n_face_nodes - - # Set n_nodes=n_faces and n_edges=2*n_faces - # : Not exact, but similar to a 'real' cubesphere. - n_nodes = n_faces - n_edges = 2 * n_faces - if with_mesh: - mesh = sample_mesh( - n_nodes=n_nodes, n_faces=n_faces, n_edges=n_edges, lazy_values=True - ) - cube = sample_mesh_cube(mesh=mesh, n_z=1) - else: - cube = sample_mesh_cube(nomesh_faces=n_faces, n_z=1) - - # Strip off the 'extra' aux-coord mapping the mesh, which sample-cube adds - # but which we don't want. - cube.remove_coord("mesh_face_aux") - - # Save the result to a named file. - save(cube, output_path) - - -def make_cube_like_2d_cubesphere(n_cube: int, with_mesh: bool): - """ - Generate an LFRIc cubesphere-like cube for a given cubesphere size, - *or* a simpler structured (UM-like) cube of equivalent size. - - All the cube data, coords and mesh content are LAZY, and produced without - allocating large real arrays (to allow peak-memory testing). - - NOTE: the actual cube generation is done in a stable Iris environment via - benchmarks.remote_data_generation, so it is all channeled via cached netcdf - files in our common testdata directory. - - """ - identifying_filename = ( - f"cube_like_2d_cubesphere_C{n_cube}_Mesh={with_mesh}.nc" - ) - filepath = BENCHMARK_DATA / identifying_filename - if not filepath.exists(): - # Create the required testfile, by running the generation code remotely - # in a 'fixed' python environment. - run_function_elsewhere( - generate_cube_like_2d_cubesphere, - n_cube, - with_mesh=with_mesh, - output_path=str(filepath), - ) - - # File now *should* definitely exist: content is simply the desired cube. - with PARSE_UGRID_ON_LOAD.context(): - with load_realised(): - cube = iris_loadcube(str(filepath)) - return cube - - @contextmanager def load_realised(): """ diff --git a/benchmarks/benchmarks/generate_data/stock.py b/benchmarks/benchmarks/generate_data/stock.py index e352147fc8..bbc7dc0a63 100644 --- a/benchmarks/benchmarks/generate_data/stock.py +++ b/benchmarks/benchmarks/generate_data/stock.py @@ -10,13 +10,36 @@ """ from pathlib import Path -import pickle from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD, load_mesh from . import BENCHMARK_DATA, REUSE_DATA, load_realised, run_function_elsewhere +def _create_file__xios_common(func_name, **kwargs): + def _external(func_name_, temp_file_dir, **kwargs_): + from iris.tests.stock import netcdf + + func = getattr(netcdf, func_name_) + print(func(temp_file_dir, **kwargs_), end="") + + args_hash = hash(str(kwargs)) + save_path = (BENCHMARK_DATA / f"{func_name}_{args_hash}").with_suffix( + ".nc" + ) + if not REUSE_DATA or not save_path.is_file(): + # The xios functions take control of save location so need to move to + # a more specific name that allows re-use. + actual_path = run_function_elsewhere( + _external, + func_name_=func_name, + temp_file_dir=str(BENCHMARK_DATA), + **kwargs, + ) + Path(actual_path.decode()).replace(save_path) + return save_path + + def create_file__xios_2d_face_half_levels( temp_file_dir, dataset_name, n_faces=866, n_times=1 ): @@ -29,26 +52,33 @@ def create_file__xios_2d_face_half_levels( properly save Mesh Cubes? """ - def _external(*args, **kwargs): - from iris.tests.stock.netcdf import ( - create_file__xios_2d_face_half_levels, - ) + return _create_file__xios_common( + func_name="create_file__xios_2d_face_half_levels", + dataset_name=dataset_name, + n_faces=n_faces, + n_times=n_times, + ) - print(create_file__xios_2d_face_half_levels(*args, **kwargs), end="") - args_list = [dataset_name, n_faces, n_times] - args_hash = hash(str(args_list)) - save_path = ( - BENCHMARK_DATA / f"create_file__xios_2d_face_half_levels_{args_hash}" - ).with_suffix(".nc") - if not REUSE_DATA or not save_path.is_file(): - # create_file__xios_2d_face_half_levels takes control of save location - # so need to move to a more specific name that allows re-use. - actual_path = run_function_elsewhere( - _external, str(BENCHMARK_DATA), *args_list - ) - Path(actual_path.decode()).replace(save_path) - return save_path +def create_file__xios_3d_face_half_levels( + temp_file_dir, dataset_name, n_faces=866, n_times=1, n_levels=38 +): + """ + Wrapper for :meth:`iris.tests.stock.netcdf.create_file__xios_3d_face_half_levels`. + + Have taken control of temp_file_dir + + todo: is create_file__xios_3d_face_half_levels still appropriate now we can + properly save Mesh Cubes? + """ + + return _create_file__xios_common( + func_name="create_file__xios_3d_face_half_levels", + dataset_name=dataset_name, + n_faces=n_faces, + n_times=n_times, + n_levels=n_levels, + ) def sample_mesh(n_nodes=None, n_faces=None, n_edges=None, lazy_values=False): diff --git a/benchmarks/benchmarks/generate_data/ugrid.py b/benchmarks/benchmarks/generate_data/ugrid.py new file mode 100644 index 0000000000..527b49a6bb --- /dev/null +++ b/benchmarks/benchmarks/generate_data/ugrid.py @@ -0,0 +1,195 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Scripts for generating supporting data for UGRID-related benchmarking. +""" +from iris import load_cube as iris_loadcube +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD + +from . import BENCHMARK_DATA, REUSE_DATA, load_realised, run_function_elsewhere +from .stock import ( + create_file__xios_2d_face_half_levels, + create_file__xios_3d_face_half_levels, +) + + +def generate_cube_like_2d_cubesphere( + n_cube: int, with_mesh: bool, output_path: str +): + """ + Construct and save to file an LFRIc cubesphere-like cube for a given + cubesphere size, *or* a simpler structured (UM-like) cube of equivalent + size. + + NOTE: this function is *NEVER* called from within this actual package. + Instead, it is to be called via benchmarks.remote_data_generation, + so that it can use up-to-date facilities, independent of the ASV controlled + environment which contains the "Iris commit under test". + This means: + * it must be completely self-contained : i.e. it includes all its + own imports, and saves results to an output file. + + """ + from iris import save + from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube + + n_face_nodes = n_cube * n_cube + n_faces = 6 * n_face_nodes + + # Set n_nodes=n_faces and n_edges=2*n_faces + # : Not exact, but similar to a 'real' cubesphere. + n_nodes = n_faces + n_edges = 2 * n_faces + if with_mesh: + mesh = sample_mesh( + n_nodes=n_nodes, n_faces=n_faces, n_edges=n_edges, lazy_values=True + ) + cube = sample_mesh_cube(mesh=mesh, n_z=1) + else: + cube = sample_mesh_cube(nomesh_faces=n_faces, n_z=1) + + # Strip off the 'extra' aux-coord mapping the mesh, which sample-cube adds + # but which we don't want. + cube.remove_coord("mesh_face_aux") + + # Save the result to a named file. + save(cube, output_path) + + +def make_cube_like_2d_cubesphere(n_cube: int, with_mesh: bool): + """ + Generate an LFRIc cubesphere-like cube for a given cubesphere size, + *or* a simpler structured (UM-like) cube of equivalent size. + + All the cube data, coords and mesh content are LAZY, and produced without + allocating large real arrays (to allow peak-memory testing). + + NOTE: the actual cube generation is done in a stable Iris environment via + benchmarks.remote_data_generation, so it is all channeled via cached netcdf + files in our common testdata directory. + + """ + identifying_filename = ( + f"cube_like_2d_cubesphere_C{n_cube}_Mesh={with_mesh}.nc" + ) + filepath = BENCHMARK_DATA / identifying_filename + if not filepath.exists(): + # Create the required testfile, by running the generation code remotely + # in a 'fixed' python environment. + run_function_elsewhere( + generate_cube_like_2d_cubesphere, + n_cube, + with_mesh=with_mesh, + output_path=str(filepath), + ) + + # File now *should* definitely exist: content is simply the desired cube. + with PARSE_UGRID_ON_LOAD.context(): + cube = iris_loadcube(str(filepath)) + + # Ensure correct laziness. + _ = cube.data + for coord in cube.coords(mesh_coords=False): + assert not coord.has_lazy_points() + assert not coord.has_lazy_bounds() + if cube.mesh: + for coord in cube.mesh.coords(): + assert coord.has_lazy_points() + for conn in cube.mesh.connectivities(): + assert conn.has_lazy_indices() + + return cube + + +def make_cube_like_umfield(xy_dims): + """ + Create a "UM-like" cube with lazy content, for save performance testing. + + Roughly equivalent to a single current UM cube, to be compared with + a "make_cube_like_2d_cubesphere(n_cube=_N_CUBESPHERE_UM_EQUIVALENT)" + (see below). + + Note: probably a bit over-simplified, as there is no time coord, but that + is probably equally true of our LFRic-style synthetic data. + + Args: + * xy_dims (2-tuple): + Set the horizontal dimensions = n-lats, n-lons. + + """ + + def _external(xy_dims_, save_path_): + from dask import array as da + import numpy as np + + from iris import save + from iris.coords import DimCoord + from iris.cube import Cube + + nz, ny, nx = (1,) + xy_dims_ + + # Base data : Note this is float32 not float64 like LFRic/XIOS outputs. + lazy_data = da.zeros((nz, ny, nx), dtype=np.float32) + cube = Cube(lazy_data, long_name="structured_phenom") + + # Add simple dim coords also. + z_dimco = DimCoord(np.arange(nz), long_name="level", units=1) + y_dimco = DimCoord( + np.linspace(-90.0, 90.0, ny), + standard_name="latitude", + units="degrees", + ) + x_dimco = DimCoord( + np.linspace(-180.0, 180.0, nx), + standard_name="longitude", + units="degrees", + ) + for idim, co in enumerate([z_dimco, y_dimco, x_dimco]): + cube.add_dim_coord(co, idim) + + save(cube, save_path_) + + save_path = ( + BENCHMARK_DATA / f"make_cube_like_umfield_{xy_dims}" + ).with_suffix(".nc") + if not REUSE_DATA or not save_path.is_file(): + _ = run_function_elsewhere(_external, xy_dims, str(save_path)) + with PARSE_UGRID_ON_LOAD.context(): + with load_realised(): + cube = iris_loadcube(str(save_path)) + + return cube + + +def make_cubesphere_testfile(c_size, n_levels=0, n_times=1): + """ + Build a C cubesphere testfile in a given directory, with a standard naming. + If n_levels > 0 specified: 3d file with the specified number of levels. + Return the file path. + + todo: is create_file__xios... still appropriate now we can properly save + Mesh Cubes? + + """ + n_faces = 6 * c_size * c_size + stem_name = f"mesh_cubesphere_C{c_size}_t{n_times}" + kwargs = dict( + temp_file_dir=None, + dataset_name=stem_name, # N.B. function adds the ".nc" extension + n_times=n_times, + n_faces=n_faces, + ) + + three_d = n_levels > 0 + if three_d: + kwargs["n_levels"] = n_levels + kwargs["dataset_name"] += f"_{n_levels}levels" + func = create_file__xios_3d_face_half_levels + else: + func = create_file__xios_2d_face_half_levels + + file_path = func(**kwargs) + return file_path diff --git a/benchmarks/benchmarks/loading.py b/benchmarks/benchmarks/load/__init__.py similarity index 97% rename from benchmarks/benchmarks/loading.py rename to benchmarks/benchmarks/load/__init__.py index 4558c3b5cb..74a751a46b 100644 --- a/benchmarks/benchmarks/loading.py +++ b/benchmarks/benchmarks/load/__init__.py @@ -19,8 +19,8 @@ from iris.cube import Cube from iris.fileformats.um import structured_um_loading -from .generate_data import BENCHMARK_DATA, REUSE_DATA, run_function_elsewhere -from .generate_data.um_files import create_um_files +from ..generate_data import BENCHMARK_DATA, REUSE_DATA, run_function_elsewhere +from ..generate_data.um_files import create_um_files class LoadAndRealise: diff --git a/benchmarks/benchmarks/ugrid_load.py b/benchmarks/benchmarks/load/ugrid.py similarity index 98% rename from benchmarks/benchmarks/ugrid_load.py rename to benchmarks/benchmarks/load/ugrid.py index 352450dcec..8227a4c5a0 100644 --- a/benchmarks/benchmarks/ugrid_load.py +++ b/benchmarks/benchmarks/load/ugrid.py @@ -19,7 +19,7 @@ from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from iris.experimental.ugrid import load_mesh as iris_load_mesh -from .generate_data.stock import create_file__xios_2d_face_half_levels +from ..generate_data.stock import create_file__xios_2d_face_half_levels def synthetic_data(**kwargs): diff --git a/benchmarks/benchmarks/netcdf_save.py b/benchmarks/benchmarks/save.py similarity index 78% rename from benchmarks/benchmarks/netcdf_save.py rename to benchmarks/benchmarks/save.py index c7580b3c63..d4c36ef983 100644 --- a/benchmarks/benchmarks/netcdf_save.py +++ b/benchmarks/benchmarks/save.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Cubesphere-like netcdf saving benchmarks. +File saving benchmarks. Where possible benchmarks should be parameterised for two sizes of input data: * minimal: enables detection of regressions in parts of the run-time that do @@ -18,12 +18,15 @@ from iris.experimental.ugrid import save_mesh from . import TrackAddedMemoryAllocation -from .generate_data import make_cube_like_2d_cubesphere +from .generate_data.ugrid import make_cube_like_2d_cubesphere class NetcdfSave: params = [[1, 600], [False, True]] param_names = ["cubesphere-N", "is_unstructured"] + # For use on 'track_addedmem_..' type benchmarks - result is too noisy. + no_small_params = params + no_small_params[0] = params[0][1:] def setup(self, n_cubesphere, is_unstructured): self.cube = make_cube_like_2d_cubesphere( @@ -48,14 +51,8 @@ def time_netcdf_save_mesh(self, n_cubesphere, is_unstructured): if is_unstructured: self._save_mesh(self.cube) + @TrackAddedMemoryAllocation.decorator(no_small_params) def track_addedmem_netcdf_save(self, n_cubesphere, is_unstructured): - cube = self.cube.copy() # Do this outside the testing block - with TrackAddedMemoryAllocation() as mb: - self._save_data(cube, do_copy=False) - return mb.addedmem_mb() - - -# Declare a 'Mb' unit for all 'track_addedmem_..' type benchmarks -for attr in dir(NetcdfSave): - if attr.startswith("track_addedmem_"): - getattr(NetcdfSave, attr).unit = "Mb" + # Don't need to copy the cube here since track_ benchmarks don't + # do repeats between self.setup() calls. + self._save_data(self.cube, do_copy=False) diff --git a/benchmarks/benchmarks/sperf/__init__.py b/benchmarks/benchmarks/sperf/__init__.py new file mode 100644 index 0000000000..696c8ef4df --- /dev/null +++ b/benchmarks/benchmarks/sperf/__init__.py @@ -0,0 +1,39 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Benchmarks for the SPerf scheme of the UK Met Office's NG-VAT project. + +SPerf = assessing performance against a series of increasingly large LFRic +datasets. +""" +from iris import load_cube + +# TODO: remove uses of PARSE_UGRID_ON_LOAD once UGRID parsing is core behaviour. +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD + +from ..generate_data.ugrid import make_cubesphere_testfile + + +class FileMixin: + """For use in any benchmark classes that work on a file.""" + + params = [ + [12, 384, 640, 960, 1280, 1668], + [1, 36, 72], + [1, 3, 36, 72], + ] + param_names = ["cubesphere_C", "N levels", "N time steps"] + # cubesphere_C: notation refers to faces per panel. + # e.g. C1 is 6 faces, 8 nodes + + def setup(self, c_size, n_levels, n_times): + self.file_path = make_cubesphere_testfile( + c_size=c_size, n_levels=n_levels, n_times=n_times + ) + + def load_cube(self): + with PARSE_UGRID_ON_LOAD.context(): + return load_cube(str(self.file_path)) diff --git a/benchmarks/benchmarks/sperf/combine_regions.py b/benchmarks/benchmarks/sperf/combine_regions.py new file mode 100644 index 0000000000..fd2c95c7fc --- /dev/null +++ b/benchmarks/benchmarks/sperf/combine_regions.py @@ -0,0 +1,250 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Region combine benchmarks for the SPerf scheme of the UK Met Office's NG-VAT project. +""" +import os.path + +from dask import array as da +import numpy as np + +from iris import load, load_cube, save +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD +from iris.experimental.ugrid.utils import recombine_submeshes + +from .. import TrackAddedMemoryAllocation, on_demand_benchmark +from ..generate_data.ugrid import make_cube_like_2d_cubesphere + + +class Mixin: + # Characterise time taken + memory-allocated, for various stages of combine + # operations on cubesphere-like test data. + timeout = 180.0 + params = [100, 200, 300, 500, 1000, 1668] + param_names = ["cubesphere_C"] + # Fix result units for the tracking benchmarks. + unit = "Mb" + + def _parametrised_cache_filename(self, n_cubesphere, content_name): + return f"cube_C{n_cubesphere}_{content_name}.nc" + + def _make_region_cubes(self, full_mesh_cube): + """Make a fixed number of region cubes from a full meshcube.""" + # Divide the cube into regions. + n_faces = full_mesh_cube.shape[-1] + # Start with a simple list of face indices + # first extend to multiple of 5 + n_faces_5s = 5 * ((n_faces + 1) // 5) + i_faces = np.arange(n_faces_5s, dtype=int) + # reshape (5N,) to (N, 5) + i_faces = i_faces.reshape((n_faces_5s // 5, 5)) + # reorder [2, 3, 4, 0, 1] within each block of 5 + i_faces = np.concatenate([i_faces[:, 2:], i_faces[:, :2]], axis=1) + # flatten to get [2 3 4 0 1 (-) 8 9 10 6 7 (-) 13 14 15 11 12 ...] + i_faces = i_faces.flatten() + # reduce back to orignal length, wrap any overflows into valid range + i_faces = i_faces[:n_faces] % n_faces + + # Divide into regions -- always slightly uneven, since 7 doesn't divide + n_regions = 7 + n_facesperregion = n_faces // n_regions + i_face_regions = (i_faces // n_facesperregion) % n_regions + region_inds = [ + np.where(i_face_regions == i_region)[0] + for i_region in range(n_regions) + ] + # NOTE: this produces 7 regions, with near-adjacent value ranges but + # with some points "moved" to an adjacent region. + # Also, region-0 is bigger (because of not dividing by 7). + + # Finally, make region cubes with these indices. + region_cubes = [full_mesh_cube[..., inds] for inds in region_inds] + return region_cubes + + def setup_cache(self): + """Cache all the necessary source data on disk.""" + + # Control dask, to minimise memory usage + allow largest data. + self.fix_dask_settings() + + for n_cubesphere in self.params: + # Do for each parameter, since "setup_cache" is NOT parametrised + mesh_cube = make_cube_like_2d_cubesphere( + n_cube=n_cubesphere, with_mesh=True + ) + # Save to files which include the parameter in the names. + save( + mesh_cube, + self._parametrised_cache_filename(n_cubesphere, "meshcube"), + ) + region_cubes = self._make_region_cubes(mesh_cube) + save( + region_cubes, + self._parametrised_cache_filename(n_cubesphere, "regioncubes"), + ) + + def setup( + self, n_cubesphere, imaginary_data=True, create_result_cube=True + ): + """ + The combine-tests "standard" setup operation. + + Load the source cubes (full-mesh + region) from disk. + These are specific to the cubesize parameter. + The data is cached on disk rather than calculated, to avoid any + pre-loading of the process memory allocation. + + If 'imaginary_data' is set (default), the region cubes data is replaced + with lazy data in the form of a da.zeros(). Otherwise, the region data + is lazy data from the files. + + If 'create_result_cube' is set, create "self.combined_cube" containing + the (still lazy) result. + + NOTE: various test classes override + extend this. + + """ + + # Load source cubes (full-mesh and regions) + with PARSE_UGRID_ON_LOAD.context(): + self.full_mesh_cube = load_cube( + self._parametrised_cache_filename(n_cubesphere, "meshcube") + ) + self.region_cubes = load( + self._parametrised_cache_filename(n_cubesphere, "regioncubes") + ) + + # Remove all var-names from loaded cubes, which can otherwise cause + # problems. Also implement 'imaginary' data. + for cube in self.region_cubes + [self.full_mesh_cube]: + cube.var_name = None + for coord in cube.coords(): + coord.var_name = None + if imaginary_data: + # Replace cube data (lazy file data) with 'imaginary' data. + # This has the same lazy-array attributes, but is allocated by + # creating chunks on demand instead of loading from file. + data = cube.lazy_data() + data = da.zeros( + data.shape, dtype=data.dtype, chunks=data.chunksize + ) + cube.data = data + + if create_result_cube: + self.recombined_cube = self.recombine() + + # Fix dask usage mode for all the subsequent performance tests. + self.fix_dask_settings() + + def fix_dask_settings(self): + """ + Fix "standard" dask behaviour for time+space testing. + + Currently this is single-threaded mode, with known chunksize, + which is optimised for space saving so we can test largest data. + + """ + + import dask.config as dcfg + + # Use single-threaded, to avoid process-switching costs and minimise memory usage. + # N.B. generally may be slower, but use less memory ? + dcfg.set(scheduler="single-threaded") + # Configure iris._lazy_data.as_lazy_data to aim for 100Mb chunks + dcfg.set({"array.chunk-size": "128Mib"}) + + def recombine(self): + # A handy general shorthand for the main "combine" operation. + result = recombine_submeshes( + self.full_mesh_cube, + self.region_cubes, + index_coord_name="i_mesh_face", + ) + return result + + +@on_demand_benchmark +class CreateCube(Mixin): + """ + Time+memory costs of creating a combined-regions cube. + + The result is lazy, and we don't do the actual calculation. + + """ + + def setup( + self, n_cubesphere, imaginary_data=True, create_result_cube=False + ): + # In this case only, do *not* create the result cube. + # That is the operation we want to test. + super().setup(n_cubesphere, imaginary_data, create_result_cube) + + def time_create_combined_cube(self, n_cubesphere): + self.recombine() + + @TrackAddedMemoryAllocation.decorator() + def track_addedmem_create_combined_cube(self, n_cubesphere): + self.recombine() + + +@on_demand_benchmark +class ComputeRealData(Mixin): + """ + Time+memory costs of computing combined-regions data. + """ + + def time_compute_data(self, n_cubesphere): + _ = self.recombined_cube.data + + @TrackAddedMemoryAllocation.decorator() + def track_addedmem_compute_data(self, n_cubesphere): + _ = self.recombined_cube.data + + +@on_demand_benchmark +class SaveData(Mixin): + """ + Test saving *only*, having replaced the input cube data with 'imaginary' + array data, so that input data is not loaded from disk during the save + operation. + + """ + + def time_save(self, n_cubesphere): + # Save to disk, which must compute data + stream it to file. + save(self.recombined_cube, "tmp.nc") + + @TrackAddedMemoryAllocation.decorator() + def track_addedmem_save(self, n_cubesphere): + save(self.recombined_cube, "tmp.nc") + + def track_filesize_saved(self, n_cubesphere): + save(self.recombined_cube, "tmp.nc") + return os.path.getsize("tmp.nc") * 1.0e-6 + + +@on_demand_benchmark +class FileStreamedCalc(Mixin): + """ + Test the whole cost of file-to-file streaming. + Uses the combined cube which is based on lazy data loading from the region + cubes on disk. + """ + + def setup( + self, n_cubesphere, imaginary_data=False, create_result_cube=True + ): + # In this case only, do *not* replace the loaded regions data with + # 'imaginary' data, as we want to test file-to-file calculation+save. + super().setup(n_cubesphere, imaginary_data, create_result_cube) + + def time_stream_file2file(self, n_cubesphere): + # Save to disk, which must compute data + stream it to file. + save(self.recombined_cube, "tmp.nc") + + @TrackAddedMemoryAllocation.decorator() + def track_addedmem_stream_file2file(self, n_cubesphere): + save(self.recombined_cube, "tmp.nc") diff --git a/benchmarks/benchmarks/sperf/equality.py b/benchmarks/benchmarks/sperf/equality.py new file mode 100644 index 0000000000..85c73ab92b --- /dev/null +++ b/benchmarks/benchmarks/sperf/equality.py @@ -0,0 +1,36 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Equality benchmarks for the SPerf scheme of the UK Met Office's NG-VAT project. +""" +from . import FileMixin +from .. import on_demand_benchmark + + +@on_demand_benchmark +class CubeEquality(FileMixin): + """ + Benchmark time and memory costs of comparing :class:`~iris.cube.Cube`\\ s + with attached :class:`~iris.experimental.ugrid.mesh.Mesh`\\ es. + + Uses :class:`FileMixin` as the realistic case will be comparing + :class:`~iris.cube.Cube`\\ s that have been loaded from file. + + """ + + # Cut down paremt parameters. + params = [FileMixin.params[0]] + + def setup(self, c_size, n_levels=1, n_times=1): + super().setup(c_size, n_levels, n_times) + self.cube = self.load_cube() + self.other_cube = self.load_cube() + + def peakmem_eq(self, n_cube): + _ = self.cube == self.other_cube + + def time_eq(self, n_cube): + _ = self.cube == self.other_cube diff --git a/benchmarks/benchmarks/sperf/load.py b/benchmarks/benchmarks/sperf/load.py new file mode 100644 index 0000000000..c1d1db43a9 --- /dev/null +++ b/benchmarks/benchmarks/sperf/load.py @@ -0,0 +1,32 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +File loading benchmarks for the SPerf scheme of the UK Met Office's NG-VAT project. +""" +from . import FileMixin +from .. import on_demand_benchmark + + +@on_demand_benchmark +class Load(FileMixin): + def time_load_cube(self, _, __, ___): + _ = self.load_cube() + + +@on_demand_benchmark +class Realise(FileMixin): + # The larger files take a long time to realise. + timeout = 600.0 + + def setup(self, c_size, n_levels, n_times): + super().setup(c_size, n_levels, n_times) + self.loaded_cube = self.load_cube() + + def time_realise_cube(self, _, __, ___): + # Don't touch loaded_cube.data - permanent realisation plays badly with + # ASV's re-run strategy. + assert self.loaded_cube.has_lazy_data() + self.loaded_cube.core_data().compute() diff --git a/benchmarks/benchmarks/sperf/save.py b/benchmarks/benchmarks/sperf/save.py new file mode 100644 index 0000000000..62c84a2619 --- /dev/null +++ b/benchmarks/benchmarks/sperf/save.py @@ -0,0 +1,56 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +File saving benchmarks for the SPerf scheme of the UK Met Office's NG-VAT project. +""" +import os.path + +from iris import save +from iris.experimental.ugrid import save_mesh + +from .. import TrackAddedMemoryAllocation, on_demand_benchmark +from ..generate_data.ugrid import make_cube_like_2d_cubesphere + + +@on_demand_benchmark +class NetcdfSave: + """ + Benchmark time and memory costs of saving ~large-ish data cubes to netcdf. + + """ + + params = [[1, 100, 200, 300, 500, 1000, 1668], [False, True]] + param_names = ["cubesphere_C", "is_unstructured"] + # Fix result units for the tracking benchmarks. + unit = "Mb" + + def setup(self, n_cubesphere, is_unstructured): + self.cube = make_cube_like_2d_cubesphere( + n_cube=n_cubesphere, with_mesh=is_unstructured + ) + + def _save_cube(self, cube): + save(cube, "tmp.nc") + + def _save_mesh(self, cube): + save_mesh(cube.mesh, "mesh.nc") + + def time_save_cube(self, n_cubesphere, is_unstructured): + self._save_cube(self.cube) + + @TrackAddedMemoryAllocation.decorator() + def track_addedmem_save_cube(self, n_cubesphere, is_unstructured): + self._save_cube(self.cube) + + def time_save_mesh(self, n_cubesphere, is_unstructured): + if is_unstructured: + self._save_mesh(self.cube) + + # The filesizes make a good reference point for the 'addedmem' memory + # usage results. + def track_filesize_save_cube(self, n_cubesphere, is_unstructured): + self._save_cube(self.cube) + return os.path.getsize("tmp.nc") * 1.0e-6 diff --git a/docs/src/developers_guide/asv_example_images/commits.png b/docs/src/developers_guide/asv_example_images/commits.png new file mode 100644 index 0000000000000000000000000000000000000000..4e0d69532277f78ecc36182f12c9bbe6191e151e GIT binary patch literal 166632 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�aS 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

l{#0eYSO(^XUF$ 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 literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/asv_example_images/comparison.png b/docs/src/developers_guide/asv_example_images/comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..e146d306965a2e8794070e78d0a85a4e5dacc11a GIT binary patch literal 17405 zcmeHucUV)~w{7h02r5;isDOwH9Hb)wEEtM{(mM)D??p;zR+JKoQlu&bktQOc2qY*- zQF`d135awsKoTGU-r5PCdw%zod+%TGyYC&oPuV1Euf6tKbIm#Cm}5P=p@}%SpKCu1 z20N&xdPN%s+l_$1wg>Fp4L*rPX5zrFZ64Z)OR#q>+*9C>oi-P*UxdL5BM;DS?*f1C zb5%9+fWeM z1K!-Wd@!XF_VDJN;2XKtCvQC|e&fli8uIe>wshabRi#@o?Xvr@&62WH9}FVaOm?^O zr@!uK-^p(mth{TTBeKWZrBaUDyt775W@631o8VwUEhh(f@R_DI4-Hex15@r*od#nA zKj+=9ecVI8YIxwflW`IHf8#(RE!07G@>% zPlNgAo*Z+2`sB%z#kX^N5%!YO(s3g6Z@#N0Njb#1qOp1-4alLT)O)Lu+vs1&B}mw4 z7IL6E@1NmTLl&vhKYT^XTpJcx7?e79rO?G zT$J6;xKR1uUu1U7bHvWgN=Zx44@UA_D6y5Hbfj%y9I@^${$s})8E^FC?i`_5+@U)! zJ%xPm;6arNk(Y9d`_ZmrvCi}dC{vH1=oc`f!>Gzj6K0gIcr1@diSr;=$D8Y6UAYGF z@WYV`i>k8&ABz+S1{xDpTvO}3K{VpxUiy8;@ve{7muLGV*hNh_FF*N17-jIz^r*$^ zH-k`eblihzOx-}7h{-ozdShF&>cjMl<7ca*@4c|T1Ez9*5bq`^a#qR?kH%Vsafpc` z$l(FBF+MSC58W|4qg=_ElPNtkDKQF-aQ6e=rkvDmGG?PhHS%B1;DhIlzr!z@1W`+a zDkuRCo0V~O9jz~iN)x3%+^%_2w)!)Pvig#~BCm?~A_!{xdGzxP52tM`XIZT-j@K&k zWGSC?3L+T)E}IG9)DLcUp~4-AC|HM3kvg4(&OagL&B_YtDj&tX%k8oovUS=8&s%jeduVwUCUoyy`L@y{K9yxop`CDG_+ z*CDlSml7XG$EB;Q;5!WUxZx>9o>TaAXT<(v1Y^*X$c{M0v5}mEf!Zs?qO|oqTLUE< zC%4t>Nz!a4YpYD`+*_-32!(mB>&-7B6}vw^OYp7Yulvlb_tmDgs9Hg59fh{->6YhG zopJuU3dQVJs(o{!QeJI;mvM4t8TC!|m!qJ%O=*Yn343!Eltw#Gy zqU71k{U~QFsr%%WzG4Rhq96SwY(;>#eCuda;1`ufwyH;o5SrJQvy>uaaKs3=AukIN zB-8L((lC}nqBoDqSRP+BDzavq!)QI#aU*SHU1Enw)u3tVF)0-;mH*sgX zl}RAXMI=B_0Y43~cZ;W+Mjr9g|N3;+#)#qCf~8L}ULN-0GY=)o{+6+zSIAC8V7Qtm{;Y7>W^erscWSG_2Ntg(!vl zF8yR{yv@@z-IZ(Am7^!_JWw_{@X@2a^loEZFBa(y9Cnc`Blx_XaRnRLtK+&%?Wg-Y z8dKvk$BX7Gms&XGJtq|>oCnLLq@4#0n-p%kFk-bTD)GVWo~2G5t=fV>Qm)U8$*?hb zHu^#I{9px#gqC)GGmcr99J_%Bv?9;;)rQ9yXN!JKlyb1F z3qOjC@05>PRt#eo2|eaqcDw4Ra2MWe!KePh=whRo7=hl2@{I4|SD~NKWBKTBTGx^maO}DtU=v{;nkM(gY;f7 zscFEy61{^c-YO=t?tAy_M`*W`kGWKepSqTC2#Q@u%GV}cVzBI%><+fQp%`YW{N?eA`xqDDjJ-xl0TB(6*mE__AU$+Ydv%BQWj}42L zInvz;f&f6NRd=3|%7wXOTwI~|Oytj0N1ypFyZYlfw2P57(EuydM#v0t0(|oH#N{Wf z0mV5)$gAB~B`E@ncOueQQ3`%_Pkmlfr`%|6_3>C`M0Z=2&AB9hUthX6;kK)eu6gf1 z{;dpxbzTKXGliul3F;$$;aS7?w=P~wQ-lLxVz}I;USL*aeZ46`oL}C1-v1L$oeecR z>F2jR<7U}+z*ASt2`MUiVM3A-@-Qbx8cv{J@-6*r4*U9Cs2=PF__0FvYXChf9`6dd+(=4+&ERUO5d-Ea18Au621;yN&VxrU?@s1U`{;h7?*Qb1xa}ep z*KGaCMQI|0>gda|1FmRt<%RjOj7g-R6rx3ugiiQ#dBA<=q;1^0wFMuVOzW3Sn>4P{ zz+cRb=1>RURZU8r%?A9Jn(2WP)g7~{3-7zYM8D<7 z#9Jf%ToFV41BUAM_V* zOErdbuM&E2?5&Da$K{zGG5vgFHl@|CT9$aU8ps7AEg218!Hex71Elo?tsBtV`POeH zd*aoIgA0K&Ai-rdF!#M{>e;SRo6xrve315Cu0I}gm_HRUDfyC}m1^9?>$@@%GWOtr z=+m)CB}GjI;uipM8QE!@xv>=Y6o3;D7=bVdz(s{_V|APgKCa>=*3F5Q>|ehMrI~Uw znqxxr4l@13YX&~i^bSS`xz}n8 zAb`lD64qZxJr;Z*riW)N_bD*WYNsxF3Vn--potMU0O0NB4@BeShFH zotD<-X#}_2dElQ-QyGA(jSLLMm)vIqVNha8DK`J~ry>7i@}9QIuGRa-`xv8f zD$uKm9z$IaE%%yX$08}BNXF;W8emz4QhlJ%B|l#_hPq+GK%j3C`MA7pD2-Yv1*t!$ zjI(Jab#P(GCP~5u)y8OJYXD{eZ8-z7r--1{_@HONz@(wHZMJCrYZ~p@gIUHaR{+Bo zU^59?N*d-iF!Rz2GvU&S;-P!17yo73;s5h zpoLgTr)I$RBlP=TVWG_znxA#($8{IK4#i!4I$#mSclFWNCz#y1HFa7F#4Dz*pLbC^ z+0~%6!OJn;doB~q2LRLcYzC%>&+6y(+B{LU&%#N@wQMc081!mKPma|`Ms{dk$bTz^ z@9i~o%uZRu8_tC@#t>Z6J-w@sN}f9gd5UC@7CARkmqERrff?Hc@A>eeR>cb~@-umh z=je|NCbv(IGyqC~)LZH6;XiA|7Y0}h)@@%w($6uxxj`A*qqK6-m%%70Q*FVNb+NBf zf;K7XC8~*XG2F=6LUGT@wz3~F8lxt|YjOWFySw=8x>b!Yoqc>VR@dfE+pCL(AgQqY zx|L><@i`-^6D=uW)iYX3>z;i}F`Yfd4n4A9!M8}G;F2CNW6y+AIWw+Cg!a2s!J&kN ze6?LO!0umMlA4AcWPnc&9%Fwlq=lm3DA=vNAzI2{Kiay1L8|#t=6M#7!_?7-e6(Ap zUR`>OR$I;>OP?7L!}`E9fYe2GERg23v$qe0+%ZV{^vSb@KEzS5@dhgL&9UX&eHp7h zgst5ZmBoJqg(+bTZvqhO-fZtpkQ7})LAf0;6iYxiiMtF{8e(kQhl20)yfdp^0V!V? z(LM=e8{)s?+S5n;lp&-`_%WGxy}Z6ZqDOtc#t0H=ORe!X+_>1Y26dqC42!D3yRE!- zyT1Z_7ItzW<$|~98pPgQ4q=L*E>u;?4t?~naU@`b*C-1XzkRTu>|$3zpcIaj?^63~ zq-N}O2=5td=j6b(DdO`qngAdcTwd7lq_PInK>B#j`Vo5ky*+8No&Pthha~?!b)03) zl{;7%Rb%or;B}0XgEm%G5^gq=eVQH2S>i1MeQDIHO=_Tq70oN7XeseGUG5^Co%%XvxFqL|V zwKu!=`pPk}qjX;*a>U$U{X)kK39&g9Y=3SH@ej9no{261zk``o58eNIutv3fr85u;|F z)7-vtpH*WNhac=ZV7azDwdWvWcPc^T6ch5g+;&gN(jkhWQtT@AagoawMm(brz@kDdm= z8Cy|XT+@0M5sdm7hwMRg{N!=#lgq08@HPJT*yHR&vTg|26}Y1H4tx;G{=PP$w7FWg zjMo@5Tktbb09k|EM2dG^d=+gxUN3yzb>LpVQ!Os@mVjE?Z4e%69is)<9JM|-G(__= z%hfQv_+$bEcv?4UtpGP(UY_ncJ72ZAvC`s=W2p&Yjs%nPn$tl<_?PPE`W?&)H_r9M zS_kO%17s3rBxomGkgb!&?xm@{`Qz*Juv>QkU;7uiV%K%I7~d2X>HmY@jm{Db5m9AR zZ;~u$y7N_T$1VgcAc?hNf#MshX`6B&F6raaasPrhd7O>%B~AFdj!pHKx+qWvJXCJR zE?>?REYOsnLjNmLk{fx%lJ31QR0v3!m2t5kqjLqMwVo;*r^dyvGv<`?@)RisNcr&^ zIR1BL6?F;XR->p3^G~bRa;riQh*iGt#chxZ>cKeZE-UPD2V?&c)Io%-=S4cwhM><;@u@i$iou*XXW^DtOv5dcPt>r-0wYa}R=@~9xIAMF!y{=3^1#|2%j;1%v?H`%W(e~p+@_z zKRtFX-u_8Jl^3D4#iC@-oOG2r0ii3Ng|@`oFX}x0-fL+h|0Xc}-(o$T#Lz_e>5nQ00tP;HWcrLV& zVK9PuZa25uXNX`P1>x~&qiBU=)f|WxG9G^eK?>B_A|iGnKx~LyQd(^2AwbIN6J&Hi zM;cZrxl@|5BabGBtAhF%i2a5I2$OQeFDzsEE&&ey02_t#T)6jw?xbKaQpOfugFvXI zUB1%hl2Rmd&`J%}F>STUX7r^^s&FfVTeL;yn@6n=Ar~S>YC`HEM;-xcQ;PkN*~vws1);bY9Z=F5NG`g>LVx#HW(<&K;ZLk3 z#sh@t)^|6)_WS3Dl)e@?#q>8=yVqq$3Hx@B%ysP8Si8j4FQ|7}^`C|YM+qYnP#W*i zh@AmU0jg{^xx=#WN8?}FGaw6_5~Xr&vjQ$(zTEDtkuSfsB(H|{F?jdjyuJS)UvNu} zsOsqGmU@P5h@vd~SHn-E_5jvtr=a(cugxr>ICqkL!vGKKdrf`HZ!{WPEi5C^)HXZmc(^#-le_^DwNb0wAnJf@J&!!$_1h<>vx# z_Puk+)@;~f(N%0?sw20^>-XM9`DpnCfaLBBxHpQ3`~3RO3HS2^^)k(`Ti}5DSk&&` zx8FXvNDD4}3b!gXzPGJ?V6CQplsK%w63?U{h5K`)`K}nWMO@MOU<)advbEUeKygWh zVaV0dTmZ|wglmQ8iV><>_O*%oui9wUij^vI&^f38%H!X+ua9z6JzZUz;BW$LoFp?s z`+D?pe2)T6bt3b@48$V!J6GgSLio2(OI!BCov$(J4M@MV|07mXiVP6GMPE}?9teEr zHb(e1UfUi(FunGbjX7stS=QV0gs(W@tiii*DTi*>i)wl=u$ee@C)xAwy-UmPt@zSk z*nMHmmQcwL2t$ma^y=boVATE2%;eEYHRNM{FSDri8$)Z|rFA)axuM?$)aV8RO3G}t zeG4BaQb%Q*xr_k54Cj)vue!x>whc8mc~I!Y6M*=PNOHk_Odv&*r^<2!W2_UdB}wP+ zy@K~KR(AjlCbe@bQ(tuNJeS+W4x-cQdq#M{FgMyWJR(MXD-(}m`{x4o&m@2i85LW; z4*`G?8{l0d>gwv&OVpm9QUPAnZ3yt$lw(=}d+@XXz%?)|$Tug+6%CpapK1A@j1#lK zd7G3t8kvH42c_zqvaYud+dB?bg5aNA*6Irs4#LBCgGvYQnG|X*#Fn6^w_Ki1Pow>y zM=Km+Z8?Et#jO(bIa(I9xlv1ejqW?ethjOu%2)SV-~kU+koas1%Vs*)M6*Ip_#{+Z zgZnL?UMq3zJ5u4jfJ%iU+3Q0-c+GTAf>Ibj^|`appc3aYkX>;8er)MT!=U#t4@eoz zA(G^7YwCMQCIFQ~YjR{_TIL{#1W0`#VBq?FPy9#&$kHt##S26>3+JitmxKs4PzUHY zc`nd(T+UFLF}xeO9rGAXy%Zy?pVv9Fa!H8X@>a{=p*A6Ej5+OGHu5ALU>7Lk{#~WK z9H-kEut8h>?OWRo)Ff{5!mX}AWr$=i^`5ey9|DG%CuN2&LN>i59W7TUnA9` zL$1hb%l9^+;x0XZ*nHkjSXRz3LHiggIY7N^3n!=zLIWw$2tW_;I^yuaL?NM>(5WnoUH&pE<4M=q676)iv8fCEKhRp0Basc1VV$U{4$I)x1 zEh!Zg8sKuljo{YQX&~QwUHh{+$!he=vqJyXMQlG*HY7ro&+5kqtUx$d*{t+kvIGUL zLO;^9Wu+k9g<1etTPUc<;MJqj%J(Dg%?}(+l6DH+Sf|FrspSheYPwdMqPXRkr=vdK zKiyZk;SO9~iR>|f=JUQF$+a3Nb8G+TG4VIrKA6_2Ckwd5zXTk!eLaBdgs26ThMPwv zZErxeQFZ{l@%*_f^i#=Q-qH=~*i+^+j2pc+P z3{a=ogV>*VWai6LQ0|2^k73jdf@u&xXW#iu*A(7$2&$xn3KnS}KOeL~vn*)nH)l!B5Fpfu`+`c8ErrGwH09*h6x;r=sx6;ua<^)!t?|w%i>7)XmHlC|ZyB#ULgPz|w8?0hGbg z;cHQG)*-saiF)quU!I}@1Xy8WdVy96m1CVhdIv~44IPe8@i2ype_x4{$>DHFgOac+?%4aj7h|UbNFR8ukGxpdX|*Ed#66%erX&hjgR*sU zf7nres6N%Bz{!9Ji#NbL8vnEh&bRi6ksW(IohU=+9E{l>1HwJqeN`^4i3m`DTUay{ zrX^hPb{0=U$L#+o^zYm;1-RpwyT!-1s<6T2t+Fia9}yYkM*bTJ=h28`hG#_e;K<$|m+2~I(s=#mx@Qx1Y4dDk zFv<}WeL&S%hqyRffS z-*N8y{&eQ#6|VCMzl&^!fv2Lm@^+qfmo(%F-qo&>bP?`smcIzWX$_G3*{%4xX)ZXc z9Gf)6t^d*?ri29PVfrQhi+bz-S&E%GUmXoF;?*tQ!no3hs5oY)5wPb2*sx%`%ny0| z4UXpm2IB-I<~V2cK>z@9^VtqJwkY%sD*#U+A+cp8;mx&|+To=?O8TA8l0jYh?B5`d z3{Vs*Li!7S1PT;H{a6F+(oi}9R`@`RYzrxIW(;8Ng5yF%Mr%wU4 zuvSQs_zWTk`iiQcVm~M}X4tf*-fjKt1f+iQUel~tKhhcGNx=AW1W?H$zukYQ*G00u zwA#WDHtd(cF0NxT&fza5YB0+D=iJ@NP&JH^yq~ zxiAC=Vbq|LOHJyE_Q9Bky9DShVa4)unlrjxuUUQajEm1e$Qw#c`YNgbAyl8P5@xI* z#^$aip=Ax~I!y{{c*hrjQmBWBI+N3mv?9{+4B>KMAQG^4 zXSsmdX$*qq4I{hx)z0MMZhd}`pct?gIPvED9guQ}BgwN^m7!eMJDqQDLOPZ70tj8V z7ipNn(H2nY#ln1DSK*_V%^ycy-Ti-lyGW-*+ROt>MoPu!W|-OL3NNqKzJyaO&hW{u zt-rU^$g?Ntu$anf^Y7vZrZXti_Exvi=K9O?nY}_HyN<`kJ$U}`u`pCb%JPh$Sh!o$ zAF}pUZ0s)$QGBl087K(|CNF*xE3FIJoV%aCzM;_?TI82vh`Em%-xKMCLh@0sL*O2N z-Vidxu!{)1|Gxa+GA>|Q11`@%(xj_x7K6d)$5=<8w=WcL)5rv;6?*@JD20jV+oW`e zZ^ZGj&Z*V3hI#mTpCu^QFV2Oeg)S)2a?=M>M^pz{?`T~_?7u4Q*n0?+Xhk1{+5=2v zES?(oIZ@ur9srV}Nv(;l-TZc6^8ol^KcVrzg#V?x`uZRK1^-{+0Q&a@v_9RSUh(|@ z%&Y!C|L&fI_4E%ZSU`XHZ0&sU|1-09+@)1Dfb^sXlt+HW6R%%rNc=$8KdX-yu?-5F_dPw2T%KCo+weJFi53(KJ_9Wu zx}ur((ZktvY>0ML{8%Ks&g*G;%)85J>*N|0YOj?%8}gOa+Ta33fyp>%sUi&CxdVjB zi?Z4wk3d#CvX}JjvTy^!@ASC*6>K(^$*ipaQMgAwbs~sD^_lJI4=ZtfAP#S`4G&3#Yr^7Wa2Jp7UAX1smD<+Sp}bW3JWg<|H+NBo^E|8wsTXAY6RdF@?1 z4(N5-gJH*q(|8awb3PXCSIf;lvSDltG%!av?galxEOMnMQsBH z?J`pRSR8n1L!7@afY)NsNgE)y6m9Jtb}AjzpN$tW(RWnqis!c968>p`M1(TvZSj|+ zotazq{a$rQd(LiyL>v94mP8>%5nbNW`??N{&qpmwGX*-Z<>~0fh61P;V%yCgamIqm zTCGH@0t*Pd=aA+Qzck9X@b97-lN8WeeA2kLFa#a$dS#WH1W1-Y`he^O(k0CLeg(QP zD-e3e+coI2cS+#yHGn8Z^4?oFx_>#YL=SX z8i(LsDqA9SG5e@a!iD4ZS7@DHwzRg z*tGi1wT)J@kXH|>E&NHNL!>H{uC*;`xDpH9Srt?$r-+$FM5pQp-kSUfMZIr_U-4(Z zyIciyOQQ#87M^ag`*MVP5JF%VGdG>h0zhQ;IV%c+D%LH@Ms2iLmr}}T0*A{^O?lDL&<<~o8HX$` zm1fGtlCTI`o|L=$>?(~+m3h~g?uo5AGrg8&IRhu)K z#MuS;+J(TXey_dq?>%|c%lWD%Q01+(lgDVzYjUx}oW{pHeWgPpWt8KBe65F=dDxm)AJ2Mu+z#asZL<(*1+r`M zhSJOC+Bo)_M*Lf44BHt4xwxIw62N8QS+?{;33aW(MTi z^`EN~Ac~=IkFo2pBu#hSZN=pffiGn*T`qr^A_0!z+1lHm85Xf5j@H)hhJ{!Gp^`Y@ z;S7Nd_Z1ZoX4K11Nmb2v<_w*6%0Bq1ef$h@NSx zSK1)%Fn2*=2(9>->4o%iA&guhY)MfK2Z*PsBa{h6ksZsh) zTD*~H!IC@Uuy#~ov+<$pqjRPJr$D^RdI=Dizp%mhw*V%=vL#s#8KkBO;Ul0`{s>ZA z6I%T`oH9T^ZxacdoCF)32S8FMan6;<&6P;YO8)Y>lK#+jz@LPQWUs+&gQkueX;^Hi zQ~rdCd;p$eXXO^P_Z9)Hw>a5VvBl5SlIoYLfS3J#92~ki43W_a%WaqMGQ+s#gwp)o zlYM`_B!*eg(r5el;hgey5fcLLbByiF78CY@wf9J9DH03gTcSMCiias~sV&f4=v`aA)aBzDy%_H{k`rKLw zxHk6z)fIEnVtv{mY-2i^AgGQ1SZrmNi{;3PGtaJLDsjBCMs_2ufg<2l;BV6n3e3lC zuT%xDhrT);v8`<*2N^TXE2vgQ>40A=kN1_!nQ*kreRs4uUdJ)8Is2W=RJrF;vkb@m zI>0GV%(IxtdGA({nGUxXfj)sFXx_|wP|tBsg>!o$G`aW)#ChPzEiH1_1WiTii$N{W z5R}lvbPjC$?wT2*tTrd}cmXJXAURPZKg`{&MQ-8?NGB+wr3t+3`4&}*(79diDs8B0 zN>|WJqm4CvKt!URn^|dg36y{m3rmH;rE|uZw>~#sw5UyJrpS9OxPP%zfL~Lms7^_= zq$ySTL_Wj@#muKbu?u8nXXC{B08dt!tQIe7_Durf!rn4Z4G9cp=;k2fr_FzktM)`L z2Tu7#r{-sh31%j@EuvX>zQ`{#GOmSE_*(aC$JaJWH0&US&WYdZQ zY2OPQtbo~0yjW9?vzolndJGT9dYzJSlpUM#s5lX)FY`XEbnxBs@oKu~m07Qc#)`hwhcroc%htwH(vO3;|29$!u zt<)?edO49m%>nAG&2tZ;mp?V`I31j!vGhxEumX)th^wBv{BdP8j3UaH{Eh`ED$$h(y!wl@!xr^9_~;I-e*pZMM(3UGbi42kr4FB z0AKnVukxyV8!Wi{1@3S%?r1Wwp8JM=e|F&`f)+v|CuBLjz zeWZ%Gn03c8{K)ra@=NEJRDOaSmc0m`nA@V%R#YAbMl5y$b9bBXbfoJ}8 zdbM&rgjH&H)x3FVH^sM5tl^Nk$1<*SyX;z~OUE0RJl<~PucSL2aM#`#??_nzeCU&JGLM`=onvL#l(b`l1m`1mAX zZGACSKkxfx?q4u*(6{o?*sW1QrI*olLi8dkbr2UgBjEb6gO5#%tDW zFpL2FoIS8WUeQdFi-}+F^LG+cNuMDVDN(s8#u$dEn&wT=h0Q_ktok!chcQL{1p<$Q1u zVJ=@U2D40fxIPH`HLdp>C{nm>VGJg&zXg$|LNI$#6@fFs+gb|vvnqCJdf zMs)lR=~4yX>#j%#T<`{p!CJSN>HE4A`KhHh4t+qE+&wD0>qOUOOwK1KlB@7z8HnGQ zey)qB`h)Z4!!G)P#w?2Y2dh9?aWnwmnzq54(|S)9S|0f+^Rd~YR?)zC1F0)9%NH(~ z9ng*%#207nzlK|%bK#3NikND3HNdm=so(`e|AFD1}%gJ;6hk2Q?8OkHV(cErskIb1)xzrQgKiAa(*VDUb%mr zFwh(51}n*fC=W>h!WOrnn`X>8abDmdD~ZH zv%peU1%@UTcT{t1YDxcbM@MT^=$Jm`V)jr2&^`~1;)F#CV%|ASt;N0b=xR+EvH{u` zk=@*Ob%Bc?TLa$hz5MRu&%{yLfD#N;$3R>y-<|nheqFO5&^v2*Y3d^wROQEIYT&Q$ zpH*Ck2^#i#__P%Jojr<_T&%U;egCyQVM)2(qH@nbKQk|jxWMkS@5(MUM>ly9Qbwf! zVmXeSO2Wtt`B;gEZ74H;fx+arZ=FaE&SO;b5ZRbySL2gEl<`L*n&X?~Il=cn#rX5j z0N56rUg1?WMiFl{{&Csl(3V`$sBlZ9_uoJ+5dCzMujtK@i?rHXG7x@KQ_;Ng?$WIX F{{z$YgKhu- literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/asv_example_images/scalability.png b/docs/src/developers_guide/asv_example_images/scalability.png new file mode 100644 index 0000000000000000000000000000000000000000..260c3ef5364f092fe5877339ae01a1a36a39b679 GIT binary patch literal 46256 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 L6Wwxc`_TUYOcv3v literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/contributing_benchmarks.rst b/docs/src/developers_guide/contributing_benchmarks.rst new file mode 100644 index 0000000000..65bc9635b6 --- /dev/null +++ b/docs/src/developers_guide/contributing_benchmarks.rst @@ -0,0 +1,62 @@ +.. include:: ../common_links.inc + +.. _contributing.benchmarks: + +Benchmarking +============ +Iris includes architecture for benchmarking performance and other metrics of +interest. This is done using the `Airspeed Velocity`_ (ASV) package. + +Full detail on the setup and how to run or write benchmarks is in +`benchmarks/README.md`_ in the Iris repository. + +Continuous Integration +---------------------- +The primary purpose of `Airspeed Velocity`_, and Iris' specific benchmarking +setup, is to monitor for performance changes using statistical comparison +between commits, and this forms part of Iris' continuous integration. + +Accurately assessing performance takes longer than functionality pass/fail +tests, so the benchmark suite is not automatically run against open pull +requests, instead it is **run overnight against each the commits of the +previous day** to check if any commit has introduced performance shifts. +Detected shifts are reported in a new Iris GitHub issue. + +If a pull request author/reviewer suspects their changes may cause performance +shifts, a convenience is available (currently via Nox) to replicate the +overnight benchmark run but comparing the current ``HEAD`` with a requested +branch (e.g. ``upstream/main``). Read more in `benchmarks/README.md`_. + +Other Uses +---------- +Even when not statistically comparing commits, ASV's accurate execution time +results - recorded using a sophisticated system of repeats - have other +applications. + +* Absolute numbers can be interpreted providing they are recorded on a + dedicated resource. +* Results for a series of commits can be visualised for an intuitive + understanding of when and why changes occurred. + + .. image:: asv_example_images/commits.png + :width: 300 + +* Parameterised benchmarks make it easy to visualise: + + * Comparisons + + .. image:: asv_example_images/comparison.png + :width: 300 + + * Scalability + + .. image:: asv_example_images/scalability.png + :width: 300 + +This also isn't limited to execution times. ASV can also measure memory demand, +and even arbitrary numbers (e.g. file size, regridding accuracy), although +without the repetition logic that execution timing has. + + +.. _Airspeed Velocity: https://github.com/airspeed-velocity/asv +.. _benchmarks/README.md: https://github.com/SciTools/iris/blob/main/benchmarks/README.md diff --git a/docs/src/developers_guide/contributing_testing_index.rst b/docs/src/developers_guide/contributing_testing_index.rst index c5cf1b997b..7c6eb1b3cc 100644 --- a/docs/src/developers_guide/contributing_testing_index.rst +++ b/docs/src/developers_guide/contributing_testing_index.rst @@ -11,3 +11,4 @@ Testing imagehash_index contributing_running_tests contributing_ci_tests + contributing_benchmarks diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index a9e960c173..bb37e39c45 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -82,7 +82,10 @@ This document explains the changes made to Iris for this release 💼 Internal =========== -#. N/A +#. `@trexfeathers`_ and `@pp-mo`_ finished implementing a mature benchmarking + infrastructure (see :ref:`contributing.benchmarks`), building on 2 hard + years of lessons learned 🎉. (:pull:`4477`, :pull:`4562`, :pull:`4571`, + :pull:`4583`, :pull:`4621`) .. comment diff --git a/noxfile.py b/noxfile.py index c65319c35f..00a866f814 100755 --- a/noxfile.py +++ b/noxfile.py @@ -5,6 +5,7 @@ """ +from datetime import datetime import hashlib import os from pathlib import Path @@ -294,12 +295,12 @@ def linkcheck(session: nox.sessions.Session): @nox.session @nox.parametrize( "run_type", - ["overnight", "branch", "custom"], - ids=["overnight", "branch", "custom"], + ["overnight", "branch", "cperf", "sperf", "custom"], + ids=["overnight", "branch", "cperf", "sperf", "custom"], ) def benchmarks( session: nox.sessions.Session, - run_type: Literal["overnight", "branch", "custom"], + run_type: Literal["overnight", "branch", "cperf", "sperf", "custom"], ): """ Perform Iris performance benchmarks (using Airspeed Velocity). @@ -327,6 +328,11 @@ def benchmarks( merged. **For maximum accuracy, avoid using the machine that is running this session. Run time could be >1 hour for the full benchmark suite.** + * ``cperf``: Run the on-demand CPerf suite of benchmarks (part of the + UK Met Office NG-VAT project) for the ``HEAD`` of ``upstream/main`` + only, and publish the results to the input **publish directory**, + within a unique subdirectory for this run. + * ``sperf``: As with CPerf, but for the SPerf suite. * ``custom``: run ASV with the input **ASV sub-command**, without any preset arguments - must all be supplied by the user. So just like running ASV manually, with the convenience of re-using the session's @@ -338,6 +344,7 @@ def benchmarks( * ``nox --session="benchmarks(branch)" -- upstream/main`` * ``nox --session="benchmarks(branch)" -- upstream/mesh-data-model`` * ``nox --session="benchmarks(branch)" -- upstream/main --bench=regridding`` + * ``nox --session="benchmarks(cperf)" -- my_publish_dir * ``nox --session="benchmarks(custom)" -- continuous a1b23d4 HEAD --quick`` """ @@ -395,6 +402,8 @@ def benchmarks( run_type_arg = { "overnight": "first commit", "branch": "base branch", + "cperf": "publish directory", + "sperf": "publish directory", "custom": "ASV sub-command", } if run_type not in run_type_arg.keys(): @@ -436,8 +445,11 @@ def asv_compare(*commits): with shifts_path.open("w") as shifts_file: shifts_file.write(shifts) - # Common ASV arguments used for both `overnight` and `bench` run_types. - asv_harness = "asv run {posargs} --attribute rounds=4 --interleave-rounds --strict --show-stderr" + # Common ASV arguments for all run_types except `custom`. + asv_harness = ( + "asv run {posargs} --attribute rounds=4 --interleave-rounds --strict " + "--show-stderr" + ) if run_type == "overnight": first_commit = first_arg @@ -469,6 +481,40 @@ def asv_compare(*commits): asv_compare(merge_base, "HEAD") + elif run_type in ("cperf", "sperf"): + publish_dir = Path(first_arg) + if not publish_dir.is_dir(): + message = ( + f"Input 'publish directory' is not a directory: {publish_dir}" + ) + raise NotADirectoryError(message) + publish_subdir = ( + publish_dir + / f"{run_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + ) + publish_subdir.mkdir() + + # Activate on demand benchmarks (C/SPerf are deactivated for 'standard' runs). + session.env["ON_DEMAND_BENCHMARKS"] = "True" + commit_range = "upstream/main^!" + + asv_command = ( + asv_harness.format(posargs=commit_range) + f" --bench={run_type}" + ) + session.run(*asv_command.split(" "), *asv_args) + + asv_command = f"asv publish {commit_range} --html-dir={publish_subdir}" + session.run(*asv_command.split(" ")) + + # Print completion message. + location = Path().cwd() / ".asv" + print( + f'New ASV results for "{run_type}".\n' + f'See "{publish_subdir}",' + f'\n html in "{location / "html"}".' + f'\n or JSON files under "{location / "results"}".' + ) + else: asv_subcommand = first_arg assert run_type == "custom" From 530e4d86de32a72f1b897bd6311cd140f5e4e3a7 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 11 Mar 2022 15:00:38 +0000 Subject: [PATCH 046/319] Update version to 3.2.1 and fix whatsnew (#4635) --- docs/src/whatsnew/3.2.rst | 8 ++------ lib/iris/__init__.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index fad2c61b1d..4a82b24857 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -25,8 +25,8 @@ This document explains the changes made to Iris for this release any issues or feature requests for improving Iris. Enjoy! -v3.2.1 |build_date| [unreleased] -******************************** +v3.2.1 (11 Mar 2022) +==================== .. dropdown:: :opticon:`alert` v3.2.1 Patches :container: + shadow @@ -48,10 +48,6 @@ v3.2.1 |build_date| [unreleased] :attr:`~iris.coords.Coord.bounds` array for a scalar :class:`~iris.coords.Coord`. (:pull:`4610`). - 💼 **Internal** - - #. N/A - 📢 Announcements ================ diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index a28a7cd479..619ac7bd44 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -108,7 +108,7 @@ def callback(cube, field, filename): # Iris revision. -__version__ = "3.2.0.post0" +__version__ = "3.2.1" # Restrict the names imported when using "from iris import *" __all__ = [ From 3ee7c56bcfea032fc5770f6c9b84b91511441d88 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 11 Mar 2022 15:19:18 +0000 Subject: [PATCH 047/319] Improve Small Benchmark Accuracy (#4636) * Remove another memory benchmark that's smaller than the noise level. * Fixed import benchmarks. --- benchmarks/benchmarks/import_iris.py | 179 ++++++++++++++------------- benchmarks/benchmarks/save.py | 3 +- 2 files changed, 94 insertions(+), 88 deletions(-) diff --git a/benchmarks/benchmarks/import_iris.py b/benchmarks/benchmarks/import_iris.py index 3e83ea8cfe..ad54c23122 100644 --- a/benchmarks/benchmarks/import_iris.py +++ b/benchmarks/benchmarks/import_iris.py @@ -3,240 +3,247 @@ # This file is part of Iris and is released under the LGPL license. # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. -import sys +from importlib import import_module, reload class Iris: - warmup_time = 0 - number = 1 - repeat = 10 - - def setup(self): - self.before = set(sys.modules.keys()) - - def teardown(self): - after = set(sys.modules.keys()) - diff = after - self.before - for module in diff: - sys.modules.pop(module) + @staticmethod + def _import(module_name): + """ + Have experimented with adding sleep() commands into the imported + modules. The results reveal: + + ASV avoids invoking `import x` if nothing gets called in the + benchmark (some imports were timed, but only those where calls + happened during import). + + Using reload() is not identical to importing, but does produce + results that are very close to expected import times, so this is fine + for monitoring for regressions. + It is also ideal for accurate repetitions, without the need to mess + with the ASV `number` attribute etc, since cached imports are not used + and the repetitions are therefore no faster than the first run. + """ + mod = import_module(module_name) + reload(mod) def time_iris(self): - import iris + self._import("iris") def time__concatenate(self): - import iris._concatenate + self._import("iris._concatenate") def time__constraints(self): - import iris._constraints + self._import("iris._constraints") def time__data_manager(self): - import iris._data_manager + self._import("iris._data_manager") def time__deprecation(self): - import iris._deprecation + self._import("iris._deprecation") def time__lazy_data(self): - import iris._lazy_data + self._import("iris._lazy_data") def time__merge(self): - import iris._merge + self._import("iris._merge") def time__representation(self): - import iris._representation + self._import("iris._representation") def time_analysis(self): - import iris.analysis + self._import("iris.analysis") def time_analysis__area_weighted(self): - import iris.analysis._area_weighted + self._import("iris.analysis._area_weighted") def time_analysis__grid_angles(self): - import iris.analysis._grid_angles + self._import("iris.analysis._grid_angles") def time_analysis__interpolation(self): - import iris.analysis._interpolation + self._import("iris.analysis._interpolation") def time_analysis__regrid(self): - import iris.analysis._regrid + self._import("iris.analysis._regrid") def time_analysis__scipy_interpolate(self): - import iris.analysis._scipy_interpolate + self._import("iris.analysis._scipy_interpolate") def time_analysis_calculus(self): - import iris.analysis.calculus + self._import("iris.analysis.calculus") def time_analysis_cartography(self): - import iris.analysis.cartography + self._import("iris.analysis.cartography") def time_analysis_geomerty(self): - import iris.analysis.geometry + self._import("iris.analysis.geometry") def time_analysis_maths(self): - import iris.analysis.maths + self._import("iris.analysis.maths") def time_analysis_stats(self): - import iris.analysis.stats + self._import("iris.analysis.stats") def time_analysis_trajectory(self): - import iris.analysis.trajectory + self._import("iris.analysis.trajectory") def time_aux_factory(self): - import iris.aux_factory + self._import("iris.aux_factory") def time_common(self): - import iris.common + self._import("iris.common") def time_common_lenient(self): - import iris.common.lenient + self._import("iris.common.lenient") def time_common_metadata(self): - import iris.common.metadata + self._import("iris.common.metadata") def time_common_mixin(self): - import iris.common.mixin + self._import("iris.common.mixin") def time_common_resolve(self): - import iris.common.resolve + self._import("iris.common.resolve") def time_config(self): - import iris.config + self._import("iris.config") def time_coord_categorisation(self): - import iris.coord_categorisation + self._import("iris.coord_categorisation") def time_coord_systems(self): - import iris.coord_systems + self._import("iris.coord_systems") def time_coords(self): - import iris.coords + self._import("iris.coords") def time_cube(self): - import iris.cube + self._import("iris.cube") def time_exceptions(self): - import iris.exceptions + self._import("iris.exceptions") def time_experimental(self): - import iris.experimental + self._import("iris.experimental") def time_fileformats(self): - import iris.fileformats + self._import("iris.fileformats") def time_fileformats__ff(self): - import iris.fileformats._ff + self._import("iris.fileformats._ff") def time_fileformats__ff_cross_references(self): - import iris.fileformats._ff_cross_references + self._import("iris.fileformats._ff_cross_references") def time_fileformats__pp_lbproc_pairs(self): - import iris.fileformats._pp_lbproc_pairs + self._import("iris.fileformats._pp_lbproc_pairs") def time_fileformats_structured_array_identification(self): - import iris.fileformats._structured_array_identification + self._import("iris.fileformats._structured_array_identification") def time_fileformats_abf(self): - import iris.fileformats.abf + self._import("iris.fileformats.abf") def time_fileformats_cf(self): - import iris.fileformats.cf + self._import("iris.fileformats.cf") def time_fileformats_dot(self): - import iris.fileformats.dot + self._import("iris.fileformats.dot") def time_fileformats_name(self): - import iris.fileformats.name + self._import("iris.fileformats.name") def time_fileformats_name_loaders(self): - import iris.fileformats.name_loaders + self._import("iris.fileformats.name_loaders") def time_fileformats_netcdf(self): - import iris.fileformats.netcdf + self._import("iris.fileformats.netcdf") def time_fileformats_nimrod(self): - import iris.fileformats.nimrod + self._import("iris.fileformats.nimrod") def time_fileformats_nimrod_load_rules(self): - import iris.fileformats.nimrod_load_rules + self._import("iris.fileformats.nimrod_load_rules") def time_fileformats_pp(self): - import iris.fileformats.pp + self._import("iris.fileformats.pp") def time_fileformats_pp_load_rules(self): - import iris.fileformats.pp_load_rules + self._import("iris.fileformats.pp_load_rules") def time_fileformats_pp_save_rules(self): - import iris.fileformats.pp_save_rules + self._import("iris.fileformats.pp_save_rules") def time_fileformats_rules(self): - import iris.fileformats.rules + self._import("iris.fileformats.rules") def time_fileformats_um(self): - import iris.fileformats.um + self._import("iris.fileformats.um") def time_fileformats_um__fast_load(self): - import iris.fileformats.um._fast_load + self._import("iris.fileformats.um._fast_load") def time_fileformats_um__fast_load_structured_fields(self): - import iris.fileformats.um._fast_load_structured_fields + self._import("iris.fileformats.um._fast_load_structured_fields") def time_fileformats_um__ff_replacement(self): - import iris.fileformats.um._ff_replacement + self._import("iris.fileformats.um._ff_replacement") def time_fileformats_um__optimal_array_structuring(self): - import iris.fileformats.um._optimal_array_structuring + self._import("iris.fileformats.um._optimal_array_structuring") def time_fileformats_um_cf_map(self): - import iris.fileformats.um_cf_map + self._import("iris.fileformats.um_cf_map") def time_io(self): - import iris.io + self._import("iris.io") def time_io_format_picker(self): - import iris.io.format_picker + self._import("iris.io.format_picker") def time_iterate(self): - import iris.iterate + self._import("iris.iterate") def time_palette(self): - import iris.palette + self._import("iris.palette") def time_plot(self): - import iris.plot + self._import("iris.plot") def time_quickplot(self): - import iris.quickplot + self._import("iris.quickplot") def time_std_names(self): - import iris.std_names + self._import("iris.std_names") def time_symbols(self): - import iris.symbols + self._import("iris.symbols") def time_tests(self): - import iris.tests + self._import("iris.tests") def time_time(self): - import iris.time + self._import("iris.time") def time_util(self): - import iris.util + self._import("iris.util") # third-party imports def time_third_party_cartopy(self): - import cartopy + self._import("cartopy") def time_third_party_cf_units(self): - import cf_units + self._import("cf_units") def time_third_party_cftime(self): - import cftime + self._import("cftime") def time_third_party_matplotlib(self): - import matplotlib + self._import("matplotlib") def time_third_party_numpy(self): - import numpy + self._import("numpy") def time_third_party_scipy(self): - import scipy + self._import("scipy") diff --git a/benchmarks/benchmarks/save.py b/benchmarks/benchmarks/save.py index d4c36ef983..730b63294d 100644 --- a/benchmarks/benchmarks/save.py +++ b/benchmarks/benchmarks/save.py @@ -25,8 +25,7 @@ class NetcdfSave: params = [[1, 600], [False, True]] param_names = ["cubesphere-N", "is_unstructured"] # For use on 'track_addedmem_..' type benchmarks - result is too noisy. - no_small_params = params - no_small_params[0] = params[0][1:] + no_small_params = [[600], [True]] def setup(self, n_cubesphere, is_unstructured): self.cube = make_cube_like_2d_cubesphere( From b141b01b754fb0f012a0b4ffc812d66a3ca4c271 Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Thu, 17 Mar 2022 08:42:55 +0000 Subject: [PATCH 048/319] Updated GHA frequency and table name for Voted Issues (#4641) * updated gha frequency and table name * added new ignore for linkcheck --- docs/src/conf.py | 1 + docs/src/voted_issues.rst | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 9c379ea730..3b7731ab1b 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -314,6 +314,7 @@ def _dotv(version): # url link checker. Some links work but report as broken, lets ignore them. # See https://www.sphinx-doc.org/en/1.2/config.html#options-for-the-linkcheck-builder linkcheck_ignore = [ + "http://catalogue.ceda.ac.uk/uuid/82adec1f896af6169112d09cc1174499", "http://cfconventions.org", "http://code.google.com/p/msysgit/downloads/list", "http://effbot.org", diff --git a/docs/src/voted_issues.rst b/docs/src/voted_issues.rst index edc1c860a2..473af3351e 100644 --- a/docs/src/voted_issues.rst +++ b/docs/src/voted_issues.rst @@ -20,7 +20,7 @@ the below table. .. raw:: html - +
@@ -37,7 +37,7 @@ the below table.

-.. note:: The data in this table is updated daily and is sourced from - `voted-issues.json`_. + +.. note:: The data in this table is updated every 30 minutes and is sourced + from `voted-issues.json`_. For the latest data please see the `issues on GitHub`_. Note that the list on Github does not show the number of votes 👍 only the total number of comments for the whole issue. \ No newline at end of file From 0852e1ebe7481f0e2a8bcf7665d5179d3496dce7 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 18 Mar 2022 10:25:22 +0000 Subject: [PATCH 049/319] use conda-lock -k explicit (#4644) --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 614bd7bb65..1f2421d65f 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -44,7 +44,7 @@ jobs: conda install -y -c conda-forge conda-lock - name: generate lockfile run: | - $CONDA/bin/conda-lock lock -p linux-64 -f requirements/ci/py${{matrix.python}}.yml + $CONDA/bin/conda-lock lock -k explicit -p linux-64 -f requirements/ci/py${{matrix.python}}.yml mv conda-linux-64.lock py${{matrix.python}}-linux-64.lock - name: output lockfile uses: actions/upload-artifact@v2 From 1837272d3f1c211806e3d9e71f96a5ed342de58b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Mar 2022 11:45:50 +0000 Subject: [PATCH 050/319] Bump actions/cache from 2 to 3 (#4650) Bumps [actions/cache](https://github.com/actions/cache) from 2 to 3. - [Release notes](https://github.com/actions/cache/releases) - [Commits](https://github.com/actions/cache/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 04e26686ea..2fbfbd461e 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -33,7 +33,7 @@ jobs: - name: Cache environment directories id: cache-env-dir - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | .nox @@ -43,7 +43,7 @@ jobs: - name: Cache test data directory id: cache-test-data - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: | ${{ env.IRIS_TEST_DATA_PATH }} From 5420c5fd6a211f2d60ccddb253a5cd2e8580872e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Mar 2022 13:56:32 +0000 Subject: [PATCH 051/319] Bump peter-evans/create-pull-request from 3.14.0 to 4 (#4652) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3.14.0 to 4. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/18f7dc018cc2cd597073088f7c7591b9d1c02672...d6d5519d05f5814158ef015b8448f2f74648c421) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 1f2421d65f..d191910b44 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -72,7 +72,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@18f7dc018cc2cd597073088f7c7591b9d1c02672 + uses: peter-evans/create-pull-request@d6d5519d05f5814158ef015b8448f2f74648c421 with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From c2135160627aba232f3bedd44765eea17a51b8f4 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:36:53 +0000 Subject: [PATCH 052/319] Fix aggregated_by mdtol/masked constant problem (#4246) * add failing test * pass test * whatsnew * remove redundant copy call --- docs/src/whatsnew/dev.rst | 4 ++++ lib/iris/analysis/__init__.py | 6 +++++- lib/iris/tests/unit/analysis/test_Aggregator.py | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index bb37e39c45..6f1e28e976 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -45,6 +45,10 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ enabled passing of scalar objects to :func:`~iris.plot.plot` and :func:`~iris.plot.scatter`. (:pull:`4616`) +#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.aggregated_by` with `mdtol` for 1D + cubes where an aggregated section is entirely masked, reported at + :issue:`3190`. (:pull:`4246`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index b1a9e1d259..69f76e68eb 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -582,7 +582,11 @@ def aggregate(self, data, axis, **kwargs): mdtol = kwargs.pop("mdtol", None) result = self.call_func(data, axis=axis, **kwargs) - if mdtol is not None and ma.isMaskedArray(data): + if ( + mdtol is not None + and ma.is_masked(data) + and result is not ma.masked + ): fraction_not_missing = data.count(axis=axis) / data.shape[axis] mask_update = 1 - mdtol > fraction_not_missing if ma.isMaskedArray(result): diff --git a/lib/iris/tests/unit/analysis/test_Aggregator.py b/lib/iris/tests/unit/analysis/test_Aggregator.py index 08180e61d0..ec837ea49a 100644 --- a/lib/iris/tests/unit/analysis/test_Aggregator.py +++ b/lib/iris/tests/unit/analysis/test_Aggregator.py @@ -156,6 +156,19 @@ def test_unmasked(self): self.assertArrayAlmostEqual(result, mock_return.copy()) mock_method.assert_called_once_with(data, axis=axis) + def test_allmasked_1D_with_mdtol(self): + data = ma.masked_all((3,)) + axis = 0 + mdtol = 0.5 + mock_return = ma.masked + with mock.patch.object( + self.TEST, "call_func", return_value=mock_return + ) as mock_method: + result = self.TEST.aggregate(data, axis, mdtol=mdtol) + + self.assertIs(result, mock_return) + mock_method.assert_called_once_with(data, axis=axis) + def test_returning_scalar_mdtol(self): # Test the case when the data aggregation function returns a scalar and # turns it into a masked array. From 89ed9eec2966d463d58451deeab361c7a5d8414f Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Thu, 24 Mar 2022 13:38:38 +0000 Subject: [PATCH 053/319] Make iris.tests.stock.simple_1d respect bounds arg (#4658) * Make iris.tests.stock.simple_1d respect bounds arg * What's new * What's new --- docs/src/whatsnew/dev.rst | 2 ++ lib/iris/tests/stock/__init__.py | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 6f1e28e976..63839825e1 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -90,6 +90,8 @@ This document explains the changes made to Iris for this release infrastructure (see :ref:`contributing.benchmarks`), building on 2 hard years of lessons learned 🎉. (:pull:`4477`, :pull:`4562`, :pull:`4571`, :pull:`4583`, :pull:`4621`) +#. `@wjbenfold`_ made :func:`iris.tests.stock.simple_1d` respect the + ``with_bounds`` argument. (:pull:`4658`) .. comment diff --git a/lib/iris/tests/stock/__init__.py b/lib/iris/tests/stock/__init__.py index a46a5510f6..4310906c79 100644 --- a/lib/iris/tests/stock/__init__.py +++ b/lib/iris/tests/stock/__init__.py @@ -99,7 +99,12 @@ def simple_1d(with_bounds=True): bounds = np.column_stack( [np.arange(11, dtype=np.int32), np.arange(11, dtype=np.int32) + 1] ) - coord = DimCoord(points, long_name="foo", units="1", bounds=bounds) + coord = DimCoord( + points, + long_name="foo", + units="1", + bounds=bounds if with_bounds else None, + ) cube.add_dim_coord(coord, 0) return cube From 873b3e6d91b150ab128c21f44e8d5700319e64de Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 24 Mar 2022 14:11:29 +0000 Subject: [PATCH 054/319] Improved check for valid user in overnight BM run. (#4659) --- .github/workflows/benchmark.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 2fbfbd461e..fddeffc26d 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -84,9 +84,9 @@ jobs: author=$(gh pr view $pr_number --json author -q '.["author"]["login"]' --repo $GITHUB_REPOSITORY) merger=$(gh pr view $pr_number --json mergedBy -q '.["mergedBy"]["login"]' --repo $GITHUB_REPOSITORY) # Find a valid assignee from author/merger/nothing. - if curl -s https://api.github.com/users/$author | grep -q "login"; then + if curl -s https://api.github.com/users/$author | grep -q '"type": "User"'; then assignee=$author - elif curl -s https://api.github.com/users/$merger | grep -q "login"; then + elif curl -s https://api.github.com/users/$merger | grep -q '"type": "User"'; then assignee=$merger else assignee="" From f41fbbb37c54a093b8c7ca8ad698fb7a58ac3a26 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 25 Mar 2022 09:36:26 +0000 Subject: [PATCH 055/319] Preserve position when replacing Axes with GeoAxes (#4273) * preserve position * add test * comment to clarify test * whatsnew * use original position --- docs/src/whatsnew/dev.rst | 8 +++- lib/iris/plot.py | 2 + .../test__replace_axes_with_cartopy_axes.py | 45 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 63839825e1..8821c6c676 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -41,14 +41,18 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ reverted part of the change from :pull:`3906` so that :func:`iris.plot.plot` no longer defaults to placing a "Y" coordinate (e.g. latitude) on the y-axis of the plot. (:issue:`4493`, :pull:`4601`) - -#. `@rcomer`_ enabled passing of scalar objects to :func:`~iris.plot.plot` and + +#. `@rcomer`_ enabled passing of scalar objects to :func:`~iris.plot.plot` and :func:`~iris.plot.scatter`. (:pull:`4616`) #. `@rcomer`_ fixed :meth:`~iris.cube.Cube.aggregated_by` with `mdtol` for 1D cubes where an aggregated section is entirely masked, reported at :issue:`3190`. (:pull:`4246`) +#. `@rcomer`_ ensured that a :class:`matplotlib.axes.Axes`'s position is preserved + when Iris replaces it with a :class:`cartopy.mpl.geoaxes.GeoAxes`, fixing + :issue:`1157`. (:pull:`4273`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/plot.py b/lib/iris/plot.py index d886ac1cf9..10bd740306 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -843,7 +843,9 @@ def _replace_axes_with_cartopy_axes(cartopy_proj): ylabel=ax.get_ylabel(), ) else: + position = ax.get_position(original=True) _ = fig.add_axes( + position, projection=cartopy_proj, title=ax.get_title(), xlabel=ax.get_xlabel(), diff --git a/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py b/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py new file mode 100644 index 0000000000..c4416c587d --- /dev/null +++ b/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py @@ -0,0 +1,45 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the `iris.plot.__replace_axes_with_cartopy_axes` function.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import cartopy.crs as ccrs +import matplotlib.pyplot as plt + +from iris.plot import _replace_axes_with_cartopy_axes + + +@tests.skip_plot +class Test_replace_axes_with_cartopy_axes(tests.IrisTest): + def setUp(self): + self.fig = plt.figure() + + def test_preserve_position(self): + position = [0.17, 0.65, 0.2, 0.2] + projection = ccrs.PlateCarree() + + plt.axes(position) + _replace_axes_with_cartopy_axes(projection) + result = plt.gca() + + # result should be the same as an axes created directly with the projection. + expected = plt.axes(position, projection=projection) + + # get_position returns mpl.transforms.Bbox object, for which equality does + # not appear to be implemented. Compare the bounds (tuple) instead. + self.assertEqual( + expected.get_position().bounds, result.get_position().bounds + ) + + def tearDown(self): + plt.close(self.fig) + + +if __name__ == "__main__": + tests.main() From 4cc233682cd044a247bd6487446eaff4f4789537 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 25 Mar 2022 10:10:40 +0000 Subject: [PATCH 056/319] Lazy Percentile Aggregator (#3901) * implement lazy percentile * fix map_complete_blocks tests * fix legacy masked type no mask tests * add test for map_complete_blocks on dask array * handle axis expressed as sequence * fix docstring * add lazy_aggregate method and mdtol handling * reshape for multi-dim collapse * mdtol handling for added trailing dimension * update aggregator instance docstring * simplify array reshape * factor out array reshaping as decorator * reduce repetition in tests * PERCENTILE multi-axis tests * add tests for array wrangling * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * py3.7 * test_PERCENTILE.py tweaks * cut down post_process tests * clarify wrapper tests * whatsnew * revert py3.7 change * Update lib/iris/analysis/__init__.py Co-authored-by: Will Benfold <69585101+wjbenfold@users.noreply.github.com> * review: remove docstring kwarg; define _real_percentile * review: blank line for note * review: remove scalar_percent check * review: test__axis_to_single_trailing.py * review: simplify structure * review: _percentile docstring args * review: remove axis description from _percentile docstring * review: don't use rollaxis * review: generalize LazyMixin to also work for non-lazy. Rename * review: move Test_lazy_aggregate tests into MaskedCalcMixin * review: generalise MaskedCalcMixin * review: remove stray as_lazy_data calls * review: use mixins for Test_aggregate * review: move multi axis tests to their own mixin * review: use MultiAxisMixin in both lazy classes * review: add tests for fast real aggregation * docstrings * review: test clarity tweaks * review: test alphap, betap passed to scipy * review: use assertEqual Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Will Benfold <69585101+wjbenfold@users.noreply.github.com> --- docs/src/whatsnew/dev.rst | 2 + lib/iris/_lazy_data.py | 13 +- lib/iris/analysis/__init__.py | 195 +++++++++--- .../tests/unit/analysis/test_PERCENTILE.py | 284 +++++++++++++++--- .../analysis/test_PercentileAggregator.py | 32 +- .../analysis/test__axis_to_single_trailing.py | 150 +++++++++ .../lazy_data/test_map_complete_blocks.py | 10 + 7 files changed, 579 insertions(+), 107 deletions(-) create mode 100644 lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 8821c6c676..9ed3cb23c2 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -34,6 +34,8 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`) +#. `@rcomer`_ implemented lazy aggregation for the + :obj:`iris.analysis.PERCENTILE` aggregator. (:pull:`3901`) 🐛 Bugs Fixed ============= diff --git a/lib/iris/_lazy_data.py b/lib/iris/_lazy_data.py index 038f9d9337..27f09b2a35 100644 --- a/lib/iris/_lazy_data.py +++ b/lib/iris/_lazy_data.py @@ -359,7 +359,7 @@ def map_complete_blocks(src, func, dims, out_sizes): Args: - * src (:class:`~iris.cube.Cube`): + * src (:class:`~iris.cube.Cube` or array-like): Source cube that function is applied to. * func: Function to apply. @@ -369,10 +369,15 @@ def map_complete_blocks(src, func, dims, out_sizes): Output size of dimensions that cannot be chunked. """ - if not src.has_lazy_data(): + if is_lazy_data(src): + data = src + elif not hasattr(src, "has_lazy_data"): + # Not a lazy array and not a cube. So treat as ordinary numpy array. + return func(src) + elif not src.has_lazy_data(): return func(src.data) - - data = src.lazy_data() + else: + data = src.lazy_data() # Ensure dims are not chunked in_chunks = list(data.chunks) diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 69f76e68eb..f9eb1a412a 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -37,6 +37,7 @@ from collections import OrderedDict from collections.abc import Iterable +import functools from functools import wraps import dask.array as da @@ -683,7 +684,7 @@ class PercentileAggregator(_Aggregator): """ - def __init__(self, units_func=None, lazy_func=None, **kwargs): + def __init__(self, units_func=None, **kwargs): """ Create a percentile aggregator. @@ -696,11 +697,6 @@ def __init__(self, units_func=None, lazy_func=None, **kwargs): Returns an :class:`cf_units.Unit`, or a value that can be made into one. - * lazy_func (callable or None): - An alternative to :data:`call_func` implementing a lazy - aggregation. Note that, it need not support all features of the - main operation, but should raise an error in unhandled cases. - Additional kwargs:: Passed through to :data:`call_func` and :data:`lazy_func`. @@ -718,7 +714,7 @@ def __init__(self, units_func=None, lazy_func=None, **kwargs): None, _percentile, units_func=units_func, - lazy_func=lazy_func, + lazy_func=_build_dask_mdtol_function(_percentile), **kwargs, ) @@ -764,6 +760,45 @@ def aggregate(self, data, axis, **kwargs): return _Aggregator.aggregate(self, data, axis, **kwargs) + def lazy_aggregate(self, data, axis, **kwargs): + """ + Perform aggregation over the data with a lazy operation, analogous to + the 'aggregate' result. + + Keyword arguments are passed through to the data aggregation function + (for example, the "percent" keyword for a percentile aggregator). + This function is usually used in conjunction with update_metadata(), + which should be passed the same keyword arguments. + + Args: + + * data (array): + A lazy array (:class:`dask.array.Array`). + + * axis (int or list of int): + The dimensions to aggregate over -- note that this is defined + differently to the 'aggregate' method 'axis' argument, which only + accepts a single dimension index. + + Kwargs: + + * kwargs: + All keyword arguments are passed through to the data aggregation + function. + + Returns: + A lazy array representing the result of the aggregation operation + (:class:`dask.array.Array`). + + """ + + msg = "{} aggregator requires the mandatory keyword argument {!r}." + for arg in self._args: + if arg not in kwargs: + raise ValueError(msg.format(self.name(), arg)) + + return _Aggregator.lazy_aggregate(self, data, axis, **kwargs) + def post_process(self, collapsed_cube, data_result, coords, **kwargs): """ Process the result from :func:`iris.analysis.Aggregator.aggregate`. @@ -1127,7 +1162,9 @@ def _build_dask_mdtol_function(dask_stats_function): call signature : "dask_stats_function(data, axis=axis, **kwargs)". It must be masked-data tolerant, i.e. it ignores masked input points and performs a calculation on only the unmasked points. - For example, mean([1, --, 2]) = (1 + 2) / 2 = 1.5. + For example, mean([1, --, 2]) = (1 + 2) / 2 = 1.5. If an additional + dimension is created by 'dask_function', it is assumed to be the trailing + one (as for '_percentile'). The returned value is a new function operating on dask arrays. It has the call signature `stat(data, axis=-1, mdtol=None, **kwargs)`. @@ -1147,6 +1184,12 @@ def inner_stat(array, axis=-1, mdtol=None, **kwargs): points_per_calc = array.size / dask_result.size masked_point_fractions = point_mask_counts / points_per_calc boolean_mask = masked_point_fractions > mdtol + if dask_result.ndim > boolean_mask.ndim: + # dask_stats_function created trailing dimension. + boolean_mask = da.broadcast_to( + boolean_mask.reshape(boolean_mask.shape + (1,)), + dask_result.shape, + ) # Return an mdtol-masked version of the basic result. result = da.ma.masked_array( da.ma.getdata(dask_result), boolean_mask @@ -1156,30 +1199,47 @@ def inner_stat(array, axis=-1, mdtol=None, **kwargs): return inner_stat -def _percentile(data, axis, percent, fast_percentile_method=False, **kwargs): +def _axis_to_single_trailing(stats_function): """ - The percentile aggregator is an additive operation. This means that - it *may* introduce a new dimension to the data for the statistic being - calculated, but only if more than one percentile point is requested. + Given a statistical function that acts on the trailing axis of a 1D or 2D + array, wrap it so that higher dimension arrays can be passed, as well as any + axis as int or tuple. - If a new additive dimension is formed, then it will always be the last - dimension of the resulting percentile data payload. + """ - Kwargs: + @wraps(stats_function) + def inner_stat(data, axis, *args, **kwargs): + # Get data as a 1D or 2D view with the target axis as the trailing one. + if not isinstance(axis, Iterable): + axis = (axis,) + end = range(-len(axis), 0) + + data = np.moveaxis(data, axis, end) + shape = data.shape[: -len(axis)] # Shape of dims we won't collapse. + if shape: + data = data.reshape(np.prod(shape), -1) + else: + data = data.flatten() - * fast_percentile_method (boolean) : - When set to True, uses the numpy.percentiles method as a faster - alternative to the scipy.mstats.mquantiles method. Does not handle - masked arrays. + result = stats_function(data, *args, **kwargs) + + # Ensure to unflatten any leading dimensions. + if shape: + # Account for the additive dimension if necessary. + if result.size > np.prod(shape): + shape += (-1,) + result = result.reshape(shape) + + return result + + return inner_stat + + +def _calc_percentile(data, percent, fast_percentile_method=False, **kwargs): + """ + Calculate percentiles along the trailing axis of a 1D or 2D array. """ - # Ensure that the target axis is the last dimension. - data = np.rollaxis(data, axis, start=data.ndim) - shape = data.shape[:-1] - # Flatten any leading dimensions. - if shape: - data = data.reshape([np.prod(shape), data.shape[-1]]) - # Perform the percentile calculation. if fast_percentile_method: msg = "Cannot use fast np.percentile method with masked array." if ma.is_masked(data): @@ -1187,28 +1247,63 @@ def _percentile(data, axis, percent, fast_percentile_method=False, **kwargs): result = np.percentile(data, percent, axis=-1) result = result.T else: - quantiles = np.array(percent) / 100.0 + quantiles = percent / 100.0 result = scipy.stats.mstats.mquantiles( data, quantiles, axis=-1, **kwargs ) if not ma.isMaskedArray(data) and not ma.is_masked(result): - result = np.asarray(result) + return np.asarray(result) else: - result = ma.MaskedArray(result) + return ma.MaskedArray(result) + + +@_axis_to_single_trailing +def _percentile(data, percent, fast_percentile_method=False, **kwargs): + """ + The percentile aggregator is an additive operation. This means that + it *may* introduce a new dimension to the data for the statistic being + calculated, but only if more than one percentile point is requested. + + If a new additive dimension is formed, then it will always be the last + dimension of the resulting percentile data payload. + + Args: + + * data (array-like) + array from which percentiles are to be calculated + + Kwargs: + + * fast_percentile_method (boolean) + When set to True, uses the numpy.percentiles method as a faster + alternative to the scipy.mstats.mquantiles method. Does not handle + masked arrays. + + **kwargs + passed to scipy.stats.mstats.mquantiles if fast_percentile_method is + False + + """ + if not isinstance(percent, Iterable): + percent = [percent] + percent = np.array(percent) + + # Perform the percentile calculation. + _partial_percentile = functools.partial( + _calc_percentile, + percent=percent, + fast_percentile_method=fast_percentile_method, + **kwargs, + ) + + result = iris._lazy_data.map_complete_blocks( + data, _partial_percentile, (-1,), percent.shape + ) - # Ensure to unflatten any leading dimensions. - if shape: - if not isinstance(percent, Iterable): - percent = [percent] - percent = np.array(percent) - # Account for the additive dimension. - if percent.shape > (1,): - shape += percent.shape - result = result.reshape(shape) # Check whether to reduce to a scalar result, as per the behaviour # of other aggregators. - if result.shape == (1,) and quantiles.ndim == 0: - result = result[0] + if result.shape == (1,): + result = np.squeeze(result) return result @@ -1738,9 +1833,10 @@ def interp_order(length): PERCENTILE = PercentileAggregator(alphap=1, betap=1) """ -An :class:`~iris.analysis.PercentileAggregator` instance that calculates the +A :class:`~iris.analysis.PercentileAggregator` instance that calculates the percentile over a :class:`~iris.cube.Cube`, as computed by -:func:`scipy.stats.mstats.mquantiles`. +:func:`scipy.stats.mstats.mquantiles` (default) or :func:`numpy.percentile` (if +fast_percentile_method is True). **Required** kwargs associated with the use of this aggregator: @@ -1755,6 +1851,11 @@ def interp_order(length): * betap (float): Plotting positions parameter, see :func:`scipy.stats.mstats.mquantiles`. Defaults to 1. +* fast_percentile_method (boolean): + When set to True, uses :func:`numpy.percentile` method as a faster + alternative to the :func:`scipy.stats.mstats.mquantiles` method. alphap and + betap are ignored. An exception is raised if the data are masked. + Defaults to False. **For example**: @@ -1762,7 +1863,15 @@ def interp_order(length): result = cube.collapsed('time', iris.analysis.PERCENTILE, percent=[10, 90]) -This aggregator handles masked data. +This aggregator handles masked data and lazy data. + +.. note:: + + Performance of this aggregator on lazy data is particularly sensitive to + the dask array chunking, so it may be useful to test with various chunk + sizes for a given application. Any chunking along the dimensions to be + aggregated is removed by the aggregator prior to calculating the + percentiles. """ diff --git a/lib/iris/tests/unit/analysis/test_PERCENTILE.py b/lib/iris/tests/unit/analysis/test_PERCENTILE.py index 52648f6fb8..1e4ba3af0f 100644 --- a/lib/iris/tests/unit/analysis/test_PERCENTILE.py +++ b/lib/iris/tests/unit/analysis/test_PERCENTILE.py @@ -9,92 +9,276 @@ # importing anything else. import iris.tests as tests # isort:skip +from unittest import mock + import numpy as np import numpy.ma as ma +from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data from iris.analysis import PERCENTILE -class Test_aggregate(tests.IrisTest): - def test_missing_mandatory_kwarg(self): - emsg = "percentile aggregator requires .* keyword argument 'percent'" - with self.assertRaisesRegex(ValueError, emsg): - PERCENTILE.aggregate("dummy", axis=0) +class AggregateMixin: + """ + Percentile aggregation tests for both numpy and scipy methods within lazy + and real percentile aggregation. + + """ + + def check_percentile_calc( + self, data, axis, percent, expected, approx=False, **kwargs + ): + if self.lazy: + data = as_lazy_data(data) + + expected = np.array(expected) + + actual = self.agg_method( + data, + axis=axis, + percent=percent, + fast_percentile_method=self.fast, + **kwargs, + ) + + self.assertTupleEqual(actual.shape, expected.shape) + is_lazy = is_lazy_data(actual) + + if self.lazy: + self.assertTrue(is_lazy) + actual = as_concrete_data(actual) + else: + self.assertFalse(is_lazy) + + if approx: + self.assertArrayAlmostEqual(actual, expected) + else: + self.assertArrayEqual(actual, expected) def test_1d_single(self): data = np.arange(11) - actual = PERCENTILE.aggregate(data, axis=0, percent=50) + axis = 0 + percent = 50 expected = 5 - self.assertTupleEqual(actual.shape, ()) - self.assertEqual(actual, expected) - - def test_masked_1d_single(self): - data = ma.arange(11) - data[3:7] = ma.masked - actual = PERCENTILE.aggregate(data, axis=0, percent=50) - expected = 7 - self.assertTupleEqual(actual.shape, ()) - self.assertEqual(actual, expected) + self.check_percentile_calc(data, axis, percent, expected) def test_1d_multi(self): data = np.arange(11) percent = np.array([20, 50, 90]) - actual = PERCENTILE.aggregate(data, axis=0, percent=percent) + axis = 0 expected = [2, 5, 9] - self.assertTupleEqual(actual.shape, percent.shape) - self.assertArrayEqual(actual, expected) + self.check_percentile_calc(data, axis, percent, expected) + + def test_2d_single(self): + shape = (2, 11) + data = np.arange(np.prod(shape)).reshape(shape) + axis = 0 + percent = 50 + expected = np.arange(shape[-1]) + 5.5 + self.check_percentile_calc(data, axis, percent, expected) + + def test_2d_multi(self): + shape = (2, 10) + data = np.arange(np.prod(shape)).reshape(shape) + axis = 0 + percent = np.array([10, 50, 90, 100]) + expected = np.tile(np.arange(shape[-1]), percent.size) + expected = expected.reshape(percent.size, shape[-1]).T + 1 + expected = expected + (percent / 10 - 1) + self.check_percentile_calc(data, axis, percent, expected, approx=True) + + +class MaskedAggregateMixin: + """ + Tests for calculations on masked data. Will only work if using the standard + (scipy) method. Needs to be used with AggregateMixin, as these tests re-use its + method. + + """ + + def test_masked_1d_single(self): + data = ma.arange(11) + data[3:7] = ma.masked + axis = 0 + percent = 50 + expected = 7 + self.check_percentile_calc(data, axis, percent, expected) def test_masked_1d_multi(self): data = ma.arange(11) data[3:9] = ma.masked percent = np.array([25, 50, 75]) - actual = PERCENTILE.aggregate(data, axis=0, percent=percent) + axis = 0 expected = [1, 2, 9] - self.assertTupleEqual(actual.shape, percent.shape) - self.assertArrayEqual(actual, expected) - - def test_2d_single(self): - shape = (2, 11) - data = np.arange(np.prod(shape)).reshape(shape) - actual = PERCENTILE.aggregate(data, axis=0, percent=50) - self.assertTupleEqual(actual.shape, shape[-1:]) - expected = np.arange(shape[-1]) + 5.5 - self.assertArrayEqual(actual, expected) + self.check_percentile_calc(data, axis, percent, expected) def test_masked_2d_single(self): shape = (2, 11) data = ma.arange(np.prod(shape)).reshape(shape) data[0, ::2] = ma.masked data[1, 1::2] = ma.masked - actual = PERCENTILE.aggregate(data, axis=0, percent=50) - self.assertTupleEqual(actual.shape, shape[-1:]) + axis = 0 + percent = 50 + # data has only one value for each column being aggregated, so result + # should be that value. expected = np.empty(shape[-1:]) expected[1::2] = data[0, 1::2] expected[::2] = data[1, ::2] - self.assertArrayEqual(actual, expected) - - def test_2d_multi(self): - shape = (2, 10) - data = np.arange(np.prod(shape)).reshape(shape) - percent = np.array([10, 50, 90, 100]) - actual = PERCENTILE.aggregate(data, axis=0, percent=percent) - self.assertTupleEqual(actual.shape, (shape[-1], percent.size)) - expected = np.tile(np.arange(shape[-1]), percent.size) - expected = expected.reshape(percent.size, shape[-1]).T + 1 - expected = expected + (percent / 10 - 1) - self.assertArrayAlmostEqual(actual, expected) + self.check_percentile_calc(data, axis, percent, expected) def test_masked_2d_multi(self): shape = (3, 10) data = ma.arange(np.prod(shape)).reshape(shape) data[1] = ma.masked percent = np.array([10, 50, 70, 80]) - actual = PERCENTILE.aggregate(data, axis=0, percent=percent) - self.assertTupleEqual(actual.shape, (shape[-1], percent.size)) - expected = np.tile(np.arange(shape[-1]), percent.size) - expected = expected.reshape(percent.size, shape[-1]).T - expected = expected + (percent / 10 * 2) - self.assertArrayAlmostEqual(actual, expected) + axis = 0 + mdtol = 0.1 + + # First column is just 0 and 20. Percentiles of these can be calculated as + # linear interpolation. + expected = percent / 100 * 20 + # Other columns are first column plus column number. + expected = ( + np.broadcast_to(expected, (shape[-1], percent.size)) + + np.arange(shape[-1])[:, np.newaxis] + ) + + self.check_percentile_calc( + data, axis, percent, expected, mdtol=mdtol, approx=True + ) + + @mock.patch("scipy.stats.mstats.mquantiles") + def test_default_kwargs_passed(self, mocked_mquantiles): + data = np.arange(5) + percent = 50 + axis = 0 + self.agg_method(data, axis=axis, percent=percent) + for key in ["alphap", "betap"]: + self.assertEqual(mocked_mquantiles.call_args.kwargs[key], 1) + + @mock.patch("scipy.stats.mstats.mquantiles") + def test_chosen_kwargs_passed(self, mocked_mquantiles): + data = np.arange(5) + percent = 50 + axis = 0 + self.agg_method( + data, axis=axis, percent=percent, alphap=0.6, betap=0.5 + ) + for key, val in zip(["alphap", "betap"], [0.6, 0.5]): + self.assertEqual(mocked_mquantiles.call_args.kwargs[key], val) + + +class Test_aggregate(tests.IrisTest, AggregateMixin, MaskedAggregateMixin): + """Tests for standard aggregation method on real data.""" + + def setUp(self): + self.fast = False + self.lazy = False + self.agg_method = PERCENTILE.aggregate + + def test_missing_mandatory_kwarg(self): + emsg = "percentile aggregator requires .* keyword argument 'percent'" + with self.assertRaisesRegex(ValueError, emsg): + PERCENTILE.aggregate("dummy", axis=0) + + +class Test_fast_aggregate(tests.IrisTest, AggregateMixin): + """Tests for fast percentile method on real data.""" + + def setUp(self): + self.fast = True + self.lazy = False + self.agg_method = PERCENTILE.aggregate + + def test_masked(self): + shape = (2, 11) + data = ma.arange(np.prod(shape)).reshape(shape) + data[0, ::2] = ma.masked + emsg = "Cannot use fast np.percentile method with masked array." + with self.assertRaisesRegex(TypeError, emsg): + PERCENTILE.aggregate( + data, axis=0, percent=50, fast_percentile_method=True + ) + + +class MultiAxisMixin: + """ + Tests for axis passed as a tuple. Only relevant for lazy aggregation since + axis is always specified as int for real aggregation. + + """ + + def test_multi_axis(self): + data = np.arange(24).reshape((2, 3, 4)) + collapse_axes = (0, 2) + lazy_data = as_lazy_data(data) + percent = 30 + actual = PERCENTILE.lazy_aggregate( + lazy_data, + axis=collapse_axes, + percent=percent, + fast_percentile_method=self.fast, + ) + self.assertTrue(is_lazy_data(actual)) + result = as_concrete_data(actual) + self.assertTupleEqual(result.shape, (3,)) + for num, sub_result in enumerate(result): + # results should be the same as percentiles calculated from slices. + self.assertArrayAlmostEqual( + sub_result, np.percentile(data[:, num, :], percent) + ) + + def test_multi_axis_multi_percent(self): + data = np.arange(24).reshape((2, 3, 4)) + collapse_axes = (0, 2) + lazy_data = as_lazy_data(data) + percent = [20, 30, 50, 70, 80] + actual = PERCENTILE.lazy_aggregate( + lazy_data, + axis=collapse_axes, + percent=percent, + fast_percentile_method=self.fast, + ) + self.assertTrue(is_lazy_data(actual)) + result = as_concrete_data(actual) + self.assertTupleEqual(result.shape, (3, 5)) + for num, sub_result in enumerate(result): + # results should be the same as percentiles calculated from slices. + self.assertArrayAlmostEqual( + sub_result, np.percentile(data[:, num, :], percent) + ) + + +class Test_lazy_fast_aggregate(tests.IrisTest, AggregateMixin, MultiAxisMixin): + """Tests for fast aggregation on lazy data.""" + + def setUp(self): + self.fast = True + self.lazy = True + self.agg_method = PERCENTILE.lazy_aggregate + + def test_masked(self): + shape = (2, 11) + data = ma.arange(np.prod(shape)).reshape(shape) + data[0, ::2] = ma.masked + data = as_lazy_data(data) + actual = PERCENTILE.lazy_aggregate( + data, axis=0, percent=50, fast_percentile_method=True + ) + emsg = "Cannot use fast np.percentile method with masked array." + with self.assertRaisesRegex(TypeError, emsg): + as_concrete_data(actual) + + +class Test_lazy_aggregate( + tests.IrisTest, AggregateMixin, MaskedAggregateMixin, MultiAxisMixin +): + """Tests for standard aggregation on lazy data.""" + + def setUp(self): + self.fast = False + self.lazy = True + self.agg_method = PERCENTILE.lazy_aggregate class Test_name(tests.IrisTest): diff --git a/lib/iris/tests/unit/analysis/test_PercentileAggregator.py b/lib/iris/tests/unit/analysis/test_PercentileAggregator.py index a8e6ed28ed..f11cd7a8d3 100644 --- a/lib/iris/tests/unit/analysis/test_PercentileAggregator.py +++ b/lib/iris/tests/unit/analysis/test_PercentileAggregator.py @@ -14,9 +14,11 @@ from unittest import mock +import dask.array as da import numpy as np -from iris.analysis import PercentileAggregator, _percentile +from iris._lazy_data import as_concrete_data +from iris.analysis import PercentileAggregator from iris.coords import AuxCoord, DimCoord from iris.cube import Cube @@ -24,16 +26,10 @@ class Test(tests.IrisTest): def test_init(self): name = "percentile" - call_func = _percentile units_func = mock.sentinel.units_func - lazy_func = mock.sentinel.lazy_func - aggregator = PercentileAggregator( - units_func=units_func, lazy_func=lazy_func - ) + aggregator = PercentileAggregator(units_func=units_func) self.assertEqual(aggregator.name(), name) - self.assertIs(aggregator.call_func, call_func) self.assertIs(aggregator.units_func, units_func) - self.assertIs(aggregator.lazy_func, lazy_func) self.assertIsNone(aggregator.cell_method) @@ -85,7 +81,7 @@ def test_simple_multiple_points(self): self.cube_simple, data, coords, **kwargs ) self.assertEqual(actual.shape, percent.shape + self.cube_simple.shape) - expected = np.rollaxis(data, -1) + expected = data.T self.assertArrayEqual(actual.data, expected) name = "percentile_over_time" coord = actual.coord(name) @@ -119,13 +115,29 @@ def test_multi_multiple_points(self): self.cube_multi, data, coords, **kwargs ) self.assertEqual(actual.shape, percent.shape + self.cube_multi.shape) - expected = np.rollaxis(data, -1) + expected = np.moveaxis(data, -1, 0) self.assertArrayEqual(actual.data, expected) name = "percentile_over_time" coord = actual.coord(name) expected = AuxCoord(percent, long_name=name, units="percent") self.assertEqual(coord, expected) + def test_multi_multiple_points_lazy(self): + # Check that lazy data is preserved. + aggregator = PercentileAggregator() + percent = np.array([17, 29, 81]) + kwargs = dict(percent=percent) + shape = self.cube_multi.shape + percent.shape + data = da.arange(np.prod(shape)).reshape(shape) + coords = [self.coord_multi_0] + actual = aggregator.post_process( + self.cube_multi, data, coords, **kwargs + ) + self.assertEqual(actual.shape, percent.shape + self.cube_multi.shape) + self.assertTrue(actual.has_lazy_data()) + expected = np.moveaxis(as_concrete_data(data), -1, 0) + self.assertArrayEqual(actual.data, expected) + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py b/lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py new file mode 100644 index 0000000000..505a00df78 --- /dev/null +++ b/lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py @@ -0,0 +1,150 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :data:`iris.analysis._axis_to_single_trailing` function.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +from unittest import mock + +import dask.array as da +import numpy as np + +from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data +from iris.analysis import _axis_to_single_trailing + + +class TestInputReshape(tests.IrisTest): + """Tests to make sure correct array is passed into stat function.""" + + def setUp(self): + self.stat_func = mock.Mock() + + def check_input(self, data, axis, expected): + """ + Given data and axis passed to the wrapped function, check that expected + array is passed to the inner function. + + """ + wrapped_stat_func = _axis_to_single_trailing(self.stat_func) + wrapped_stat_func(data, axis=axis) + # Can't use Mock.assert_called_with because array equality is ambiguous + # get hold of the first arg instead. + self.assertArrayEqual(self.stat_func.call_args.args[0], expected) + + def test_1d_input(self): + # Trailing axis chosen, so array should be unchanged. + data = np.arange(5) + axis = 0 + self.check_input(data, axis, data) + + def test_2d_input_trailing(self): + # Trailing axis chosen, so array should be unchanged. + data = np.arange(6).reshape(2, 3) + axis = 1 + self.stat_func.return_value = np.empty(2) + self.check_input(data, axis, data) + + def test_2d_input_transpose(self): + # Leading axis chosen, so array should be transposed. + data = np.arange(6).reshape(2, 3) + axis = 0 + self.stat_func.return_value = np.empty(3) + self.check_input(data, axis, data.T) + + def test_3d_input_middle(self): + # Middle axis is chosen, should be moved to end. Other dims should be + # flattened. + data = np.arange(24).reshape(2, 3, 4) + axis = 1 + self.stat_func.return_value = np.empty(8) + expected = np.moveaxis(data, 1, 2).reshape(8, 3) + self.check_input(data, axis, expected) + + def test_3d_input_leading_multiple(self): + # First 2 axis chosen, should be flattened and moved to end. + data = np.arange(24).reshape(2, 3, 4) + axis = (0, 1) + self.stat_func.return_value = np.empty(4) + expected = np.moveaxis(data, 2, 0).reshape(4, 6) + self.check_input(data, axis, expected) + + def test_4d_first_and_last(self): + data = np.arange(120).reshape(2, 3, 4, 5) + axis = (0, -1) + self.stat_func.return_value = np.empty(12) + expected = np.moveaxis(data, 0, 2).reshape(12, 10) + self.check_input(data, axis, expected) + + def test_3d_input_leading_multiple_lazy(self): + # First 2 axis chosen, should be flattened and moved to end. Lazy data + # should be preserved. + data = np.arange(24).reshape(2, 3, 4) + lazy_data = as_lazy_data(data) + axis = (0, 1) + self.stat_func.return_value = np.empty(4) + expected = np.moveaxis(data, 2, 0).reshape(4, 6) + + wrapped_stat_func = _axis_to_single_trailing(self.stat_func) + wrapped_stat_func(lazy_data, axis=axis) + self.assertTrue(is_lazy_data(self.stat_func.call_args.args[0])) + self.assertArrayEqual( + as_concrete_data(self.stat_func.call_args.args[0]), expected + ) + + +class TestOutputReshape(tests.IrisTest): + """Tests to make sure array from stat function is handled correctly.""" + + def setUp(self): + self.stat_func = mock.Mock() + + def test_1d_input_1d_output(self): + # If array is fully aggregated, result should be same as returned by stat + # function. + data = np.arange(3) + self.stat_func.return_value = np.arange(2) + wrapped_stat_func = _axis_to_single_trailing(self.stat_func) + result = wrapped_stat_func(data, axis=0) + self.assertArrayEqual(result, self.stat_func.return_value) + + def test_3d_input_middle_single_stat(self): + # result shape should match non-aggregated input dims. + data = np.empty((2, 3, 4)) + axis = 1 + self.stat_func.return_value = np.arange(8) + expected = np.arange(8).reshape(2, 4) + wrapped_stat_func = _axis_to_single_trailing(self.stat_func) + result = wrapped_stat_func(data, axis=axis) + self.assertArrayEqual(result, expected) + + def test_3d_input_middle_single_stat_lazy(self): + # result shape should match non-aggregated input dims. Lazy data should + # be preserved. + data = np.empty((2, 3, 4)) + axis = 1 + self.stat_func.return_value = da.arange(8) + expected = np.arange(8).reshape(2, 4) + wrapped_stat_func = _axis_to_single_trailing(self.stat_func) + result = wrapped_stat_func(data, axis=axis) + self.assertTrue(is_lazy_data(result)) + self.assertArrayEqual(as_concrete_data(result), expected) + + def test_3d_input_middle_multiple_stat(self): + # result shape should match non-aggregated input dims, plus trailing dim + # with size determined by the stat function. + data = np.empty((2, 3, 4)) + axis = 1 + self.stat_func.return_value = np.arange(8 * 5).reshape(8, 5) + expected = np.arange(40).reshape(2, 4, 5) + wrapped_stat_func = _axis_to_single_trailing(self.stat_func) + result = wrapped_stat_func(data, axis=axis) + self.assertArrayEqual(result, expected) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py b/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py index e7f3adad76..66c03d04c8 100644 --- a/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py +++ b/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py @@ -25,6 +25,8 @@ def create_mock_cube(array): cube.has_lazy_data = unittest.mock.Mock(return_value=is_lazy_data(array)) cube.lazy_data = unittest.mock.Mock(return_value=array) cube.shape = array.shape + # Remove compute so cube is not interpreted as dask array. + del cube.compute return cube, cube_data @@ -58,6 +60,14 @@ def test_lazy_input(self): cube.lazy_data.assert_called_once() cube_data.assert_not_called() + def test_dask_array_input(self): + lazy_array = da.asarray(self.array, chunks=((1, 1), (4,))) + result = map_complete_blocks( + lazy_array, self.func, dims=(1,), out_sizes=(4,) + ) + self.assertTrue(is_lazy_data(result)) + self.assertArrayEqual(result.compute(), self.func_result) + def test_rechunk(self): lazy_array = da.asarray(self.array, chunks=((1, 1), (2, 2))) cube, _ = create_mock_cube(lazy_array) From 7be9dcad065a106e35995fb9e4baeebea81a55d4 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 25 Mar 2022 12:19:49 +0000 Subject: [PATCH 057/319] Fix nearest_neighbour_index for edge case where requested point is float and bounds are int (#4245) * add failing test * pass test * add whatsnew --- docs/src/whatsnew/dev.rst | 4 ++++ lib/iris/coords.py | 4 +++- lib/iris/tests/unit/coords/test_Coord.py | 5 +++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 9ed3cb23c2..0ef6c7f65e 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -54,6 +54,10 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ ensured that a :class:`matplotlib.axes.Axes`'s position is preserved when Iris replaces it with a :class:`cartopy.mpl.geoaxes.GeoAxes`, fixing :issue:`1157`. (:pull:`4273`) + +#. `@rcomer`_ fixed :meth:`~iris.coords.Coord.nearest_neighbour_index` for edge + cases where the requested point is float and the coordinate has integer + bounds, reported at :issue:`2969`. (:pull:`4245`) 💣 Incompatible Changes diff --git a/lib/iris/coords.py b/lib/iris/coords.py index b236d407da..8bcba776fd 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -2440,7 +2440,9 @@ def nearest_neighbour_index(self, point): if self.has_bounds(): # make bounds ranges complete+separate, so point is in at least one increasing = self.bounds[0, 1] > self.bounds[0, 0] - bounds = bounds.copy() + # identify data type that bounds and point can safely cast to + dtype = np.result_type(bounds, point) + bounds = bounds.astype(dtype) # sort the bounds cells by their centre values sort_inds = np.argsort(np.mean(bounds, axis=1)) bounds = bounds[sort_inds] diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index 43170b6c4e..5f707f91db 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -74,6 +74,11 @@ def test_scalar(self): target = [0, 0, 0, 0, 0] self._test_nearest_neighbour_index(target) + def test_bounded_float_point(self): + coord = DimCoord(1, bounds=[0, 2]) + result = coord.nearest_neighbour_index(2.5) + self.assertEqual(result, 0) + class Test_nearest_neighbour_index__descending(tests.IrisTest): def setUp(self): From 57c0bff107921ea36bd564aaaa71c91ca36bafce Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Tue, 29 Mar 2022 15:25:17 +0100 Subject: [PATCH 058/319] Include note to strip out Artifactory (#4666) * Include note to strip out Artifactory * What's new --- docs/src/developers_guide/contributing_ci_tests.rst | 7 +++++++ docs/src/whatsnew/dev.rst | 2 ++ 2 files changed, 9 insertions(+) diff --git a/docs/src/developers_guide/contributing_ci_tests.rst b/docs/src/developers_guide/contributing_ci_tests.rst index 46848166b3..87a3054605 100644 --- a/docs/src/developers_guide/contributing_ci_tests.rst +++ b/docs/src/developers_guide/contributing_ci_tests.rst @@ -68,6 +68,13 @@ or simply:: and add the changed lockfiles to your pull request. +.. note:: + + If your installation of conda runs through Artifactory or another similar + proxy then you will need to amend that lockfile to use URLs that Github + Actions can access. A utility to strip out Artifactory exists in the + ``ssstack`` tool. + New lockfiles are generated automatically each week to ensure that Iris continues to be tested against the latest available version of its dependencies. Each week the yaml files in ``requirements/ci`` are resolved by a GitHub Action. diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 0ef6c7f65e..85b997125a 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -91,6 +91,8 @@ This document explains the changes made to Iris for this release #. `@tkknight`_ added a page to show the issues that have been voted for. See :ref:`voted_issues`. (:issue:`3307`, :pull:`4617`) +#. `@wjbenfold`_ added a note about fixing proxy URLs in lockfiles generated + because dependencies have changed. (:pull:`4666`) 💼 Internal From 62047f7fa9df9cf817067cb595127f0fc3c2b421 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 29 Mar 2022 16:39:30 +0100 Subject: [PATCH 059/319] update black (#4668) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee036038e4..8ce1a3cd28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: no-commit-to-branch - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black pass_filenames: false From e4246e1d1934f21e257258d868cc2936b465b0ce Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 30 Mar 2022 18:07:20 +0100 Subject: [PATCH 060/319] Fixed cube arithmetic for cubes with meshes. (#4651) * Use separate cml files for tests/unit/analysis/maths/test_slice multiple passes. * Roughly functional, but lots of tests to fix. * Fix * Fix mockist test for new-style Resolve. Mesh support not yet tested. * Fix _create_prepared_item when points/bounds passed in. * --no-edit * Simplify prepared-item usage. * Added tests * Added whatsnew. * Reference Iris issues for outstanding problems. * Small simplifications. * Updated statement on cube arithmetic with meshes. * Fix use of 'MathsAddOperationMixin'; add coord-mismatch tests on meshcubes. * Review changes. * Hack test which was failing. --- docs/src/further_topics/ugrid/operations.rst | 21 +- docs/src/whatsnew/dev.rst | 4 + lib/iris/common/resolve.py | 231 ++++++-- .../collapse_all_dims.cml | 507 ++++++++++++++++++ .../collapse_last_dims.cml | 507 ++++++++++++++++++ .../collapse_middle_dim.cml | 507 ++++++++++++++++++ .../collapse_zeroth_dim.cml | 507 ++++++++++++++++++ .../TestBroadcastingDerived/slice.cml | 507 ++++++++++++++++++ .../TestBroadcastingDerived/transposed.cml | 507 ++++++++++++++++++ .../collapse_all_dims.cml | 122 +++++ .../collapse_last_dims.cml | 122 +++++ .../collapse_middle_dim.cml | 111 ++++ .../collapse_zeroth_dim.cml | 111 ++++ .../TestBroadcastingWithMesh/slice.cml | 111 ++++ .../TestBroadcastingWithMesh/transposed.cml | 111 ++++ .../collapse_all_dims.cml | 122 +++++ .../collapse_last_dims.cml | 122 +++++ .../collapse_middle_dim.cml | 111 ++++ .../collapse_zeroth_dim.cml | 111 ++++ .../slice.cml | 111 ++++ .../transposed.cml | 111 ++++ .../tests/unit/analysis/maths/__init__.py | 94 +++- .../maths/test__arith__derived_coords.py | 40 ++ .../analysis/maths/test__arith__meshcoords.py | 186 +++++++ .../tests/unit/common/resolve/test_Resolve.py | 67 ++- 25 files changed, 4979 insertions(+), 82 deletions(-) create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml create mode 100644 lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml create mode 100644 lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py create mode 100644 lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py diff --git a/docs/src/further_topics/ugrid/operations.rst b/docs/src/further_topics/ugrid/operations.rst index f96e3e406c..c636043640 100644 --- a/docs/src/further_topics/ugrid/operations.rst +++ b/docs/src/further_topics/ugrid/operations.rst @@ -976,13 +976,26 @@ on dimensions other than the :meth:`~iris.cube.Cube.mesh_dim`, since such Arithmetic ---------- -.. |tagline: arithmetic| replace:: |pending| +.. |tagline: arithmetic| replace:: |unchanged| .. rubric:: |tagline: arithmetic| -:class:`~iris.cube.Cube` Arithmetic (described in :doc:`/userguide/cube_maths`) -has not yet been adapted to handle :class:`~iris.cube.Cube`\s that include -:class:`~iris.experimental.ugrid.MeshCoord`\s. +Cube Arithmetic (described in :doc:`/userguide/cube_maths`) +has been extended to handle :class:`~iris.cube.Cube`\s that include +:class:`~iris.experimental.ugrid.MeshCoord`\s, and hence have a ``cube.mesh``. + +Cubes with meshes can be combined in arithmetic operations like +"ordinary" cubes. They can combine with other cubes without that mesh +(and its dimension); or with a matching mesh, which may be on a different +dimension. +Arithmetic can also be performed between a cube with a mesh and a mesh +coordinate with a matching mesh. + +In all cases, the result will have the same mesh as the input cubes. + +Meshes only match if they are fully equal -- i.e. they contain all the same +coordinates and connectivities, with identical names, units, attributes and +data content. .. todo: diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 85b997125a..7d4a190ac9 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -37,6 +37,10 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ implemented lazy aggregation for the :obj:`iris.analysis.PERCENTILE` aggregator. (:pull:`3901`) +#. `@pp-mo`_ fixed cube arithmetic operation for cubes with meshes. + (:issue:`4454`, :pull:`4651`) + + 🐛 Bugs Fixed ============= diff --git a/lib/iris/common/resolve.py b/lib/iris/common/resolve.py index 12db64cafe..a0c97dfc00 100644 --- a/lib/iris/common/resolve.py +++ b/lib/iris/common/resolve.py @@ -13,7 +13,9 @@ from collections import namedtuple from collections.abc import Iterable +from dataclasses import dataclass import logging +from typing import Any from dask.array.core import broadcast_shapes import numpy as np @@ -56,10 +58,42 @@ _PreparedFactory = namedtuple("PreparedFactory", ["container", "dependencies"]) -_PreparedItem = namedtuple( - "PreparedItem", - ["metadata", "points", "bounds", "dims", "container"], -) + +@dataclass +class _PreparedItem: + metadata: Any + points: Any + bounds: Any + dims: Any + container: Any + mesh: Any = None + location: Any = None + axis: Any = None + + def create_coord(self, metadata): + from iris.experimental.ugrid.mesh import MeshCoord + + if issubclass(self.container, MeshCoord): + # Make a MeshCoord, for which we have mesh/location/axis. + result = MeshCoord( + mesh=self.mesh, + location=self.location, + axis=self.axis, + ) + # Note: in this case we do also have "prepared metadata", but we + # do *not* assign it as we do for an 'ordinary' Coord. + # Instead, MeshCoord name/units/attributes are immutable, and set at + # create time to those of the underlying mesh node coordinate. + # cf https://github.com/SciTools/iris/issues/4670 + + else: + # make a regular coord, for which we have points/bounds/metadata. + result = self.container(self.points, bounds=self.bounds) + # Also assign prepared metadata. + result.metadata = metadata + + return result + _PreparedMetadata = namedtuple("PreparedMetadata", ["combined", "src", "tgt"]) @@ -646,7 +680,13 @@ def _categorise_items(cube): @staticmethod def _create_prepared_item( - coord, dims, src_metadata=None, tgt_metadata=None + coord, + dims, + src_metadata=None, + tgt_metadata=None, + points=None, + bounds=None, + container=None, ): """ Convenience method that creates a :class:`~iris.common.resolve._PreparedItem` @@ -658,8 +698,10 @@ def _create_prepared_item( * coord: The coordinate with the ``points`` and ``bounds`` to be extracted. - * dims: - The dimensions that the ``coord`` spans on the resulting resolved :class:`~iris.cube.Cube`. + * dims (int or tuple): + The dimensions that the ``coord`` spans on the resulting resolved + :class:`~iris.cube.Cube`. + (Can also be a single dimension number). * src_metadata: The coordinate metadata from the ``src`` :class:`~iris.cube.Cube`. @@ -667,26 +709,85 @@ def _create_prepared_item( * tgt_metadata: The coordinate metadata from the ``tgt`` :class:`~iris.cube.Cube`. + * points: + Override points array. When not given, use coord.points. + + * bounds: + Override bounds array. When not given, use coord.bounds. + + * container: + Override coord type (class constructor). + When not given, use type(coord). + Returns: The :class:`~iris.common.resolve._PreparedItem`. + .. note:: + + If container or type(coord) is DimCoord/AuxCoord (i.e. not + MeshCoord), then points+bounds define the built AuxCoord/DimCoord. + Theses points+bounds come either from those args, or the 'coord'. + Alternatively, when container or type(coord) is MeshCoord, then + points==bounds==None and the preparted item contains + mesh/location/axis properties for the resulting MeshCoord. + These don't have override args: they *always* come from 'coord'. + """ + if not isinstance(dims, Iterable): + dims = (dims,) + if src_metadata is not None and tgt_metadata is not None: combined = src_metadata.combine(tgt_metadata) else: combined = src_metadata or tgt_metadata - if not isinstance(dims, Iterable): - dims = (dims,) prepared_metadata = _PreparedMetadata( combined=combined, src=src_metadata, tgt=tgt_metadata ) - bounds = coord.bounds + + if container is None: + container = type(coord) + + from iris.experimental.ugrid.mesh import MeshCoord + + if issubclass(container, MeshCoord): + # Build a prepared-item to make a MeshCoord. + # This case does *NOT* use points + bounds, so alternatives to the + # coord content should not have been specified by the caller. + assert points is None and bounds is None + mesh = coord.mesh + location = coord.location + axis = coord.axis + + else: + # Build a prepared-item to make a DimCoord or AuxCoord. + + # mesh/location/axis are not used. + mesh = None + location = None + axis = None + + # points + bounds default to those from the coordinate, but + # alternative values may be specified. + if points is None: + points = coord.points + bounds = coord.bounds + # 'ELSE' points was passed: both points+bounds come from the args + + # Always *copy* points+bounds, to avoid any possible direct (shared) + # references to existing coord arrays. + points = points.copy() + if bounds is not None: + bounds = bounds.copy() + result = _PreparedItem( metadata=prepared_metadata, - points=coord.points.copy(), - bounds=bounds if bounds is None else bounds.copy(), dims=dims, - container=type(coord), + points=points, + bounds=bounds, + mesh=mesh, + location=location, + axis=axis, + container=container, ) return result @@ -1422,30 +1523,64 @@ def _prepare_common_aux_payload( (tgt_item,) = tgt_items src_coord = src_item.coord tgt_coord = tgt_item.coord - points, bounds = self._prepare_points_and_bounds( - src_coord, - tgt_coord, - src_item.dims, - tgt_item.dims, - ignore_mismatch=ignore_mismatch, - ) - if points is not None: - src_type = type(src_coord) - tgt_type = type(tgt_coord) - # Downcast to aux if there are mixed container types. - container = src_type if src_type is tgt_type else AuxCoord - prepared_metadata = _PreparedMetadata( - combined=src_metadata.combine(tgt_item.metadata), - src=src_metadata, - tgt=tgt_item.metadata, - ) - prepared_item = _PreparedItem( - metadata=prepared_metadata, - points=points.copy(), - bounds=bounds if bounds is None else bounds.copy(), - dims=tgt_item.dims, - container=container, + + prepared_item = None + src_is_mesh, tgt_is_mesh = [ + hasattr(coord, "mesh") for coord in (src_coord, tgt_coord) + ] + if src_is_mesh and tgt_is_mesh: + # MeshCoords are a bit "special" ... + # In this case, we may need to produce an alternative form + # to the 'ordinary' _PreparedItem + # However, this only works if they have identical meshes.. + if src_coord == tgt_coord: + prepared_item = self._create_prepared_item( + src_coord, + tgt_item.dims, + src_metadata=src_metadata, + tgt_metadata=tgt_item.metadata, + ) + else: + emsg = ( + f"Mesh coordinate {src_coord.name()!r} does not match between the " + f"LHS cube {self.lhs_cube.name()!r} and " + f"RHS cube {self.rhs_cube.name()!r}." + ) + raise ValueError(emsg) + + if prepared_item is None: + # Make a "normal" _PreparedItem, which is specified using + # points + bounds arrays. + # First, convert any un-matching MeshCoords to AuxCoord + if src_is_mesh: + src_coord = AuxCoord.from_coord(src_coord) + if tgt_is_mesh: + tgt_coord = AuxCoord.from_coord(tgt_coord) + points, bounds = self._prepare_points_and_bounds( + src_coord, + tgt_coord, + src_item.dims, + tgt_item.dims, + ignore_mismatch=ignore_mismatch, ) + if points is not None: + src_type = type(src_coord) + tgt_type = type(tgt_coord) + # Downcast to aux if there are mixed container types. + container = ( + src_type if src_type is tgt_type else AuxCoord + ) + prepared_item = self._create_prepared_item( + src_coord, + tgt_item.dims, + src_metadata=src_metadata, + tgt_metadata=tgt_item.metadata, + points=points, + bounds=bounds, + container=container, + ) + + if prepared_item is not None: prepared_items.append(prepared_item) def _prepare_common_dim_payload( @@ -1499,16 +1634,13 @@ def _prepare_common_dim_payload( ) if points is not None: - prepared_metadata = _PreparedMetadata( - combined=src_metadata.combine(tgt_metadata), - src=src_metadata, - tgt=tgt_metadata, - ) - prepared_item = _PreparedItem( - metadata=prepared_metadata, - points=points.copy(), - bounds=bounds if bounds is None else bounds.copy(), - dims=(tgt_dim,), + prepared_item = self._create_prepared_item( + src_coord, + tgt_dim, + src_metadata=src_metadata, + tgt_metadata=tgt_metadata, + points=points, + bounds=bounds, container=DimCoord, ) self.prepared_category.items_dim.append(prepared_item) @@ -2333,8 +2465,7 @@ def cube(self, data, in_place=False): # Add the prepared dim coordinates. for item in self.prepared_category.items_dim: - coord = item.container(item.points, bounds=item.bounds) - coord.metadata = item.metadata.combined + coord = item.create_coord(metadata=item.metadata.combined) result.add_dim_coord(coord, item.dims) # Add the prepared aux and scalar coordinates. @@ -2343,8 +2474,8 @@ def cube(self, data, in_place=False): + self.prepared_category.items_scalar ) for item in prepared_aux_coords: - coord = item.container(item.points, bounds=item.bounds) - coord.metadata = item.metadata.combined + # These items are "special" + coord = item.create_coord(metadata=item.metadata.combined) try: result.add_aux_coord(coord, item.dims) except ValueError as err: diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml new file mode 100644 index 0000000000..9a522e5167 --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml new file mode 100644 index 0000000000..9a522e5167 --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml new file mode 100644 index 0000000000..9a522e5167 --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml new file mode 100644 index 0000000000..9a522e5167 --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml new file mode 100644 index 0000000000..9a522e5167 --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml new file mode 100644 index 0000000000..9a522e5167 --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml @@ -0,0 +1,507 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml new file mode 100644 index 0000000000..2db0cc598f --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml new file mode 100644 index 0000000000..2db0cc598f --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml new file mode 100644 index 0000000000..2db0cc598f --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml new file mode 100644 index 0000000000..2db0cc598f --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml new file mode 100644 index 0000000000..359656f25a --- /dev/null +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/unit/analysis/maths/__init__.py b/lib/iris/tests/unit/analysis/maths/__init__.py index 7d11c54660..521c65a7eb 100644 --- a/lib/iris/tests/unit/analysis/maths/__init__.py +++ b/lib/iris/tests/unit/analysis/maths/__init__.py @@ -10,11 +10,13 @@ import iris.tests as tests # isort:skip from abc import ABCMeta, abstractmethod +import operator import numpy as np from numpy import ma from iris.analysis import MEAN +from iris.analysis.maths import add from iris.coords import DimCoord from iris.cube import Cube import iris.tests.stock as stock @@ -36,8 +38,46 @@ def cube_func(self): # I.E. 'iris.analysis.maths.xx'. pass + def _base_testcube(self, include_derived=False): + if include_derived: + self.cube = stock.realistic_4d() + else: + self.cube = stock.realistic_4d_no_derived() + self.cube_xy_dimcoords = ["grid_latitude", "grid_longitude"] + return self.cube + + def _meshcube_collapsesafe(self, cube, coords): + # Return the cube, or if need be a modified copy, which can be safely + # collapsed over the given coords. + # This is needed for mesh-cubes, because the mesh coords have + # bounds which are not understood by the standard 'collapse' operation. + # TODO: possibly replace with a future 'safe mesh collapse' operation. + # cf. https://github.com/SciTools/iris/issues/4672 + result = cube + if cube.mesh is not None: + collapse_dims = set() + for co in coords: + # Each must produce a single coord, with a single dim + (dim,) = cube.coord_dims(co) + collapse_dims.add(dim) + i_meshdim = cube.mesh_dim() + if i_meshdim in collapse_dims: + # Make a copy with all mesh coords replaced by their AuxCoord + # equivalents. A simple slicing will do that. + slices = [slice(None)] * cube.ndim + slices[i_meshdim] = slice(0, None) + result = cube[tuple(slices)] + # Finally, **remove bounds** from all the former AuxCoords. + # This is what enables them to be successfully collapsed. + for meshco in cube.coords(mesh_coords=True): + # Note: select new coord by name, as getting the AuxCoord + # which "matches" a MeshCoord is not possible. + result.coord(meshco.name()).bounds = None + + return result + def test_transposed(self): - cube = stock.realistic_4d_no_derived() + cube = self._base_testcube() other = cube.copy() other.transpose() res = self.cube_func(cube, other) @@ -46,7 +86,7 @@ def test_transposed(self): self.assertArrayEqual(res.data, expected_data) def test_collapse_zeroth_dim(self): - cube = stock.realistic_4d_no_derived() + cube = self._base_testcube() other = cube.collapsed("time", MEAN) res = self.cube_func(cube, other) self.assertCML(res, checksum=False) @@ -58,8 +98,10 @@ def test_collapse_zeroth_dim(self): self.assertMaskedArrayEqual(res.data, expected_data) def test_collapse_all_dims(self): - cube = stock.realistic_4d_no_derived() - other = cube.collapsed(cube.coords(dim_coords=True), MEAN) + cube = self._base_testcube() + collapse_coords = cube.coords(dim_coords=True) + other = self._meshcube_collapsesafe(cube, collapse_coords) + other = other.collapsed(collapse_coords, MEAN) res = self.cube_func(cube, other) self.assertCML(res, checksum=False) # No modification to other.data is needed as numpy broadcasting @@ -70,21 +112,28 @@ def test_collapse_all_dims(self): self.assertArrayEqual(res.data, expected_data) def test_collapse_last_dims(self): - cube = stock.realistic_4d_no_derived() - other = cube.collapsed(["grid_latitude", "grid_longitude"], MEAN) + cube = self._base_testcube() + # Collapse : by 'last' we mean the X+Y ones... + other = self._meshcube_collapsesafe(cube, self.cube_xy_dimcoords) + other = other.collapsed(self.cube_xy_dimcoords, MEAN) res = self.cube_func(cube, other) self.assertCML(res, checksum=False) # Transpose the dimensions in self.cube that have been collapsed in # other to lie at the front, thereby enabling numpy broadcasting to # function when applying data operator. Finish by transposing back # again to restore order. + n_xydims = len(self.cube_xy_dimcoords) + cube_dims = tuple(np.arange(cube.ndim)) + transpose_xy_back2front = cube_dims[-n_xydims:] + cube_dims[:-n_xydims] + transpose_xy_front2back = cube_dims[n_xydims:] + cube_dims[:n_xydims] expected_data = self.data_op( - cube.data.transpose((2, 3, 0, 1)), other.data - ).transpose(2, 3, 0, 1) + cube.data.transpose(transpose_xy_back2front), other.data + ).transpose(transpose_xy_front2back) + # Confirm result content is as expected self.assertMaskedArrayEqual(res.data, expected_data) def test_collapse_middle_dim(self): - cube = stock.realistic_4d_no_derived() + cube = self._base_testcube() other = cube.collapsed(["model_level_number"], MEAN) res = self.cube_func(cube, other) self.assertCML(res, checksum=False) @@ -94,12 +143,26 @@ def test_collapse_middle_dim(self): self.assertMaskedArrayEqual(res.data, expected_data) def test_slice(self): - cube = stock.realistic_4d_no_derived() + cube = self._base_testcube() for dim in range(cube.ndim): keys = [slice(None)] * cube.ndim keys[dim] = 3 other = cube[tuple(keys)] + + # A special "cheat" for mesh cases... + # When a mesh dimension is indexed, this produces scalar versions + # of the mesh-coords, which don't match to the originals. + # FOR NOW: remove those, for a result matching the other ones. + # TODO: coord equivalence may need reviewing, either for cube + # maths or for coord equivalance generally. + # cf. https://github.com/SciTools/iris/issues/4671 + if cube.mesh and dim == cube.mesh_dim(): + for co in cube.coords(mesh_coords=True): + other.remove_coord(co.name()) + res = self.cube_func(cube, other) + + # NOTE: only one testfile : any dim collapsed gives SAME result self.assertCML(res, checksum=False) # Add the collapsed dimension back in via np.newaxis to enable # numpy broadcasting to function. @@ -111,6 +174,17 @@ def test_slice(self): ) +class MathsAddOperationMixin: + # Test everything with the 'add' operation. + @property + def data_op(self): + return operator.add + + @property + def cube_func(self): + return add + + class CubeArithmeticMaskingTestMixin(metaclass=ABCMeta): # A framework for testing the mask handling behaviour of the various cube # arithmetic operations. (A test for each operation inherits this). diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py b/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py new file mode 100644 index 0000000000..51f71affb0 --- /dev/null +++ b/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py @@ -0,0 +1,40 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for cube arithmetic involving derived (i.e. factory) coords.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +from iris.tests.unit.analysis.maths import ( + CubeArithmeticBroadcastingTestMixin, + MathsAddOperationMixin, +) + + +@tests.skip_data +@tests.iristest_timing_decorator +class TestBroadcastingDerived( + tests.IrisTest_nometa, + MathsAddOperationMixin, + CubeArithmeticBroadcastingTestMixin, +): + """ + Repeat the broadcasting tests while retaining derived coordinates. + + NOTE: apart from showing that these operations do succeed, this mostly + produces a new set of CML result files, + in "lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords" . + See there to confirm that the results preserve the derived coordinates. + + """ + + def _base_testcube(self): + return super()._base_testcube(include_derived=True) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py b/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py new file mode 100644 index 0000000000..1d81e7b480 --- /dev/null +++ b/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py @@ -0,0 +1,186 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for cube arithmetic involving MeshCoords.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import numpy as np + +from iris.analysis.maths import add +from iris.coords import AuxCoord, DimCoord +from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube +from iris.tests.unit.analysis.maths import ( + CubeArithmeticBroadcastingTestMixin, + CubeArithmeticCoordsTest, + MathsAddOperationMixin, +) + + +def _convert_to_meshcube(cube): + """Convert a cube based on stock.realistic_4d into a "meshcube".""" + # Replace lat+lon with a small mesh + cube = cube[..., -1] # remove final (X) dim + for name in ("grid_longitude", "grid_latitude"): + cube.remove_coord(name) + i_meshdim = len(cube.shape) - 1 + n_meshpoints = cube.shape[i_meshdim] + mesh = sample_mesh(n_nodes=n_meshpoints, n_faces=n_meshpoints, n_edges=0) + for co in mesh.to_MeshCoords(location="face"): + cube.add_aux_coord(co, i_meshdim) + # also add a dim-coord for the mesh dim, mainly so that + # the 'xxBroadcastingxx.test_collapse_all_dims' tests can do what they say. + mesh_dimcoord = DimCoord(np.arange(n_meshpoints), long_name="i_mesh_face") + cube.add_dim_coord(mesh_dimcoord, i_meshdim) + return cube + + +class MeshLocationsMixin: + # Control allowing us to also include test with derived coordinates. + use_derived_coords = False + + # Modify the inherited data operation, to test with a mesh-cube. + # Also, optionally, test with derived coordinates. + def _base_testcube(self): + cube = super()._base_testcube(include_derived=self.use_derived_coords) + cube = _convert_to_meshcube(cube) + self.cube_xy_dimcoords = ["i_mesh_face"] + self.cube = cube + return self.cube + + +@tests.skip_data +@tests.iristest_timing_decorator +class TestBroadcastingWithMesh( + tests.IrisTest_nometa, + MeshLocationsMixin, + MathsAddOperationMixin, + CubeArithmeticBroadcastingTestMixin, +): + """ + Run all the broadcasting tests on cubes with meshes. + + NOTE: there is a fair amount of special-case code to support this, built + into the CubeArithmeticBroadcastingTestMixin baseclass. + + """ + + +@tests.skip_data +@tests.iristest_timing_decorator +class TestBroadcastingWithMeshAndDerived( + tests.IrisTest_nometa, + MeshLocationsMixin, + MathsAddOperationMixin, + CubeArithmeticBroadcastingTestMixin, +): + """Run broadcasting tests with meshes *and* derived coords.""" + + use_derived = True + + +class TestCoordMatchWithMesh(CubeArithmeticCoordsTest): + """Run the coordinate-mismatch tests with meshcubes.""" + + def _convert_to_meshcubes(self, cubes, i_dim): + """Add a mesh to one dim of the 'normal case' test-cubes.""" + for cube in cubes: + n_size = cube.shape[i_dim] + mesh = sample_mesh(n_nodes=n_size, n_faces=n_size, n_edges=0) + for co in mesh.to_MeshCoords("face"): + cube.add_aux_coord(co, i_dim) + assert cube.mesh is not None + + def _check_no_match(self, dim): + # Duplicate the basic operation, but convert cubes to meshcubes. + cube1, cube2 = self.SetUpNonMatching() + self._convert_to_meshcubes([cube1, cube2], dim) + with self.assertRaises(ValueError): + add(cube1, cube2) + + def test_no_match_dim0(self): + self._check_no_match(0) + + def test_no_match_dim1(self): + self._check_no_match(1) + + def _check_reversed_points(self, dim): + # Duplicate the basic operation, but convert cubes to meshcubes. + cube1, cube2 = self.SetUpReversed() + self._convert_to_meshcubes([cube1, cube2], dim) + with self.assertRaises(ValueError): + add(cube1, cube2) + + def test_reversed_points_dim0(self): + self._check_reversed_points(0) + + def test_reversed_points_dim1(self): + self._check_reversed_points(1) + + +class TestBasicMeshOperation(tests.IrisTest): + """Some very basic standalone tests, in an easier-to-comprehend form.""" + + def test_meshcube_same_mesh(self): + # Two similar cubes on a common mesh add to a third on the same mesh. + mesh = sample_mesh() + cube1 = sample_mesh_cube(mesh=mesh) + cube2 = sample_mesh_cube(mesh=mesh) + self.assertIs(cube1.mesh, mesh) + self.assertIs(cube2.mesh, mesh) + + result = cube1 + cube2 + self.assertEqual(result.shape, cube1.shape) + self.assertIs(result.mesh, mesh) + + def test_meshcube_different_equal_mesh(self): + # Two similar cubes on identical but different meshes. + cube1 = sample_mesh_cube() + cube2 = sample_mesh_cube() + self.assertEqual(cube1.mesh, cube2.mesh) + self.assertIsNot(cube1.mesh, cube2.mesh) + + result = cube1 + cube2 + self.assertEqual(result.shape, cube1.shape) + self.assertEqual(result.mesh, cube1.mesh) + self.assertTrue(result.mesh is cube1.mesh or result.mesh is cube2.mesh) + + def test_fail_meshcube_nonequal_mesh(self): + # Cubes on similar but different meshes -- should *not* combine. + mesh1 = sample_mesh() + mesh2 = sample_mesh(n_edges=0) + self.assertNotEqual(mesh1, mesh2) + cube1 = sample_mesh_cube(mesh=mesh1) + cube2 = sample_mesh_cube(mesh=mesh2) + + msg = "Mesh coordinate.* does not match" + with self.assertRaisesRegex(ValueError, msg): + cube1 + cube2 + + def test_meshcube_meshcoord(self): + # Combining a meshcube and meshcoord. + cube = sample_mesh_cube() + cube.coord("latitude").units = "s" + cube.units = "m" + + # A separately derived, but matching 'latitude' MeshCoord. + coord = sample_mesh_cube().coord("latitude") + coord.units = "s" # N.B. the units **must also match** + + result = cube / coord + self.assertEqual(result.name(), "unknown") + self.assertEqual(result.units, "m s-1") + + # Moreover : *cannot* do this with the 'equivalent' AuxCoord + # cf. https://github.com/SciTools/iris/issues/4671 + coord = AuxCoord.from_coord(coord) + with self.assertRaises(ValueError): + cube / coord + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/common/resolve/test_Resolve.py b/lib/iris/tests/unit/common/resolve/test_Resolve.py index 98643c8f10..840f65db01 100644 --- a/lib/iris/tests/unit/common/resolve/test_Resolve.py +++ b/lib/iris/tests/unit/common/resolve/test_Resolve.py @@ -15,7 +15,7 @@ from collections import namedtuple from copy import deepcopy import unittest.mock as mock -from unittest.mock import sentinel +from unittest.mock import Mock, sentinel from cf_units import Unit import numpy as np @@ -2086,8 +2086,13 @@ def setUp(self): # # src-to-tgt mapping: # 0->1, 1->2, 2->3 - self.points = (sentinel.points_0, sentinel.points_1, sentinel.points_2) - self.bounds = (sentinel.bounds_0, sentinel.bounds_1, sentinel.bounds_2) + self.points = ( + sentinel.points_0, + sentinel.points_1, + sentinel.points_2, + sentinel.points_3, + ) + self.bounds = sentinel.bounds_0, sentinel.bounds_1, sentinel.bounds_2 self.pb_0 = ( mock.Mock(copy=mock.Mock(return_value=self.points[0])), mock.Mock(copy=mock.Mock(return_value=self.bounds[0])), @@ -2121,9 +2126,13 @@ def setUp(self): ) metadata = [self.src_metadata] * len(self.mapping) self.src_coords = [ - sentinel.src_coord_0, - sentinel.src_coord_1, - sentinel.src_coord_2, + # N.B. these need to mimic a Coord with points and bounds, and + # be of a class which is not-a-MeshCoord. + # NOTE: strictly, bounds should =above values, and support .copy(). + # For these tests, just omitting them works + is simpler. + Mock(spec=DimCoord, points=self.points[0], bounds=None), + Mock(spec=DimCoord, points=self.points[1], bounds=None), + Mock(spec=DimCoord, points=self.points[2], bounds=None), ] self.src_dims_common = [0, 1, 2] self.container = DimCoord @@ -2142,10 +2151,14 @@ def setUp(self): sentinel.tgt_metadata_3, ] self.tgt_coords = [ - sentinel.tgt_coord_0, - sentinel.tgt_coord_1, - sentinel.tgt_coord_2, - sentinel.tgt_coord_3, + # N.B. these need to mimic a Coord with points and bounds, and + # be of a class which is not-a-MeshCoord. + # NOTE: strictly, bounds should =above values, and support .copy(). + # For these tests, just omitting them works + is simpler. + Mock(spec=DimCoord, points=self.points[0], bounds=None), + Mock(spec=DimCoord, points=self.points[1], bounds=None), + Mock(spec=DimCoord, points=self.points[2], bounds=None), + Mock(spec=DimCoord, points=self.points[3], bounds=None), ] self.tgt_dims_common = [1, 2, 3] self.tgt_dim_coverage = _DimCoverage( @@ -2275,7 +2288,12 @@ def setUp(self): # # src-to-tgt mapping: # 0->1, 1->2, 2->3 - self.points = (sentinel.points_0, sentinel.points_1, sentinel.points_2) + self.points = ( + sentinel.points_0, + sentinel.points_1, + sentinel.points_2, + sentinel.points_3, + ) self.bounds = (sentinel.bounds_0, sentinel.bounds_1, sentinel.bounds_2) self.pb_0 = ( mock.Mock(copy=mock.Mock(return_value=self.points[0])), @@ -2318,9 +2336,13 @@ def setUp(self): ), ] self.src_coords = [ - sentinel.src_coord_0, - sentinel.src_coord_1, - sentinel.src_coord_2, + # N.B. these need to mimic a Coord with points and bounds, but also + # the type() defines the 'container' property of a prepared item. + # It seems that 'type()' is not fake-able in Python, so we need to + # provide *real* DimCoords, to match "self.container" below. + DimCoord(points=[0], bounds=None), + DimCoord(points=[1], bounds=None), + DimCoord(points=[2], bounds=None), ] self.src_dims = [(dim,) for dim in self.mapping.keys()] self.src_common_items = [ @@ -2329,10 +2351,14 @@ def setUp(self): ] self.tgt_metadata = [sentinel.tgt_metadata_0] + self.src_metadata self.tgt_coords = [ - sentinel.tgt_coord_0, - sentinel.tgt_coord_1, - sentinel.tgt_coord_2, - sentinel.tgt_coord_3, + # N.B. these need to mimic a Coord with points and bounds, but also + # the type() defines the 'container' property of a prepared item. + # It seems that 'type()' is not fake-able in Python, so we need to + # provide *real* DimCoords, to match "self.container" below. + DimCoord(points=[0], bounds=None), + DimCoord(points=[1], bounds=None), + DimCoord(points=[2], bounds=None), + DimCoord(points=[3], bounds=None), ] self.tgt_dims = [None] + [(dim,) for dim in self.mapping.values()] self.tgt_common_items = [ @@ -4624,6 +4650,11 @@ def setUp(self): self.resolve.prepared_category = prepared_category self.resolve.prepared_factories = prepared_factories + # Required to stop mock 'containers' failing in an 'issubclass' call. + self.patch( + "iris.common.resolve.issubclass", mock.Mock(return_value=False) + ) + def test_no_resolved_shape(self): self.resolve._broadcast_shape = None data = None From c9972cefb7c1e2e2db5c874af0ce2dae958858ae Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 09:36:25 +0100 Subject: [PATCH 061/319] Updated environment lockfiles (#4655) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 396 ++++++++++---------- 1 file changed, 198 insertions(+), 198 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index a612138dfa..ae6c224e8a 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,225 +1,225 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: fc890d56b881193a2422ceb96d07b1b2bb857890e1d48fb24a765ec2f886d4d2 +# input_hash: d7bddc89ba289d4c1b48871b1289eb0bf45715ef2e274de01e245547fb6a8df6 @EXPLICIT -https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h1fd1430_1.tar.bz2#c494f75082f9c052944fda1b22c83336 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.2.0-pyhd8ed1ab_0.tar.bz2#f31e31092035d427b05233ab924c7613 +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-13_linux64_openblas.tar.bz2#018b80e8f21d8560ae4961567e3e00c9 +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.7-h27087fc_0.tar.bz2#b7fcfe2c2e21e5b696a98d774ac3f701 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f +https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h7f98852_6.tar.bz2#612385c4a83edb0619fe911d9da317f4 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_0.tar.bz2#0d1a1c4a830d2bc306af194f4e4896e7 +https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-13_linux64_openblas.tar.bz2#8a4038563ed92dfa622bd72c0d8f31d3 https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 +https://conda.anaconda.org/conda-forge/noarch/identify-2.4.12-pyhd8ed1ab_0.tar.bz2#9f7b07b3f40905fcf600aacf63ae0c41 https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_13.tar.bz2#b62e87134ec17e1180cfcb3951624db4 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_13.tar.bz2#573a74710fad22a27da784cc238150b9 -https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-ha770c72_0.tar.bz2#56594fdd5a80774a80d546fbbccf2c03 -https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_13.tar.bz2#a3a07a89af69d1eada078695b42e4961 -https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_13.tar.bz2#8e91f1f21417c9ab1265240ee4f9db1e -https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2#561e277319a41d4f24f5c05a9ef63c04 -https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_13.tar.bz2#63eaf0f146cc80abd84743d48d667da4 -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b -https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 -https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.6-h27087fc_0.tar.bz2#90dec9e76bc164857cc200f81e981dab -https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#fe9a66a351bfa7a84c3108304c7bcba5 -https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d -https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 -https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa -https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h7f98852_0.tar.bz2#5c214edc675a7fb7cbb34b1d854e5141 -https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 -https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e -https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h7f98852_6.tar.bz2#b0f44f63f7d771d7670747a1dd5d5ac1 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_14.tar.bz2#a77fb1a92411cb8d979de1c2d81dd210 +https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 +https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_0.tar.bz2#00bdc412edc320d1717d1f7156d8edfd +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.1-h4ff8645_0.tar.bz2#8057ac02d6d10a162d7eb4b0ca7ed291 +https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe +https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff +https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.10-h7f98852_0.tar.bz2#ffa3a757a97e851293909b49f49f28fb +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.31.2-py38h0a891b7_0.tar.bz2#381ffd61d2617af9bddb1cee60352480 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.13.4-py38h578d9bd_0.tar.bz2#ff64ee0548fdd8de60b0a6a0356e0f0d https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 -https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 -https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d -https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 -https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.18-pthreads_h8fe5266_0.tar.bz2#41532e4448c0cce086d6570f95e4e12e +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 +https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-13_linux64_openblas.tar.bz2#b17676dbd6688396c3a3076259fb7907 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f -https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h36c2ea0_1013.tar.bz2#dcddf696ff5dfcab567100d691678e18 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.1-h846660c_100.tar.bz2#4b85205b094808088bb0862e08251653 -https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h9c3ff4c_0.tar.bz2#fb31bcb7af058244479ca635d20f0f4a -https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1l-h7f98852_0.tar.bz2#de7b38a1542dbe6f41653a8ae71adc53 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa -https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 -https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 -https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a +https://conda.anaconda.org/conda-forge/linux-64/curl-7.82.0-h7bff187_0.tar.bz2#631777dcd00a2714cf0c415ffe40c480 +https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.17.0-py38h578d9bd_0.tar.bz2#839ac9dba9a6126c9532781a9ea4506b +https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.0.10-h7f98852_0.tar.bz2#d6b0b50b49eccfe0be0373be628be0f3 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908 -https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 -https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 -https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 -https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 -https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 -https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 -https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-13_linux64_openblas.tar.bz2#8a4038563ed92dfa622bd72c0d8f31d3 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h7f98852_6.tar.bz2#c7c03a2592cac92246a13a0732bd1573 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h7f98852_6.tar.bz2#28bfe0a70154e6881da7bae97517c948 -https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 -https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 -https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba -https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 -https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-61.0.0-py38h578d9bd_0.tar.bz2#f22d6162248bb633b199c495268d8fc4 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 -https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 -https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h36c2ea0_1013.tar.bz2#cf7190238072a41e9579e4476a6a60b8 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 -https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h7f98852_6.tar.bz2#9e94bf16f14c78a36561d5019f490d22 -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.2-h3790be6_4.tar.bz2#dbbd32092ee31aab0f2d213e8f9f1b40 -https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-13_linux64_openblas.tar.bz2#b17676dbd6688396c3a3076259fb7907 -https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc -https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-13_linux64_openblas.tar.bz2#018b80e8f21d8560ae4961567e3e00c9 -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-hfa10184_0.tar.bz2#aac17542e50a474e2e632878dc696d50 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.0-h9cd32fc_0.tar.bz2#eb66fc098824d25518a79e83d12a81d6 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h7f98852_6.tar.bz2#612385c4a83edb0619fe911d9da317f4 -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.6-h04a7f16_0.tar.bz2#b24a1e18325a6e8f8b6b4a2ec5860ce2 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.18.5-h9f60fe5_3.tar.bz2#511aa83cdfcc0132380db5daf2f15f27 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.81.0-h2574ce0_0.tar.bz2#1f8655741d0269ca6756f131522da1e8 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/nss-3.74-hb5efdd6_0.tar.bz2#136876ca50177058594f6c2944e95c40 -https://conda.anaconda.org/conda-forge/linux-64/python-3.8.12-ha38a3c6_3_cpython.tar.bz2#bed445cebcd8f97dce76dc06201928ee -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 -https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f -https://conda.anaconda.org/conda-forge/linux-64/curl-7.81.0-h2574ce0_0.tar.bz2#3a95d393b490f82aa406f1892fad84d9 -https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.13.96-ha180cfb_0.tar.bz2#d190a1c55c84ba1c9a33484a38ece029 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.2.0-pyhd8ed1ab_0.tar.bz2#f31e31092035d427b05233ab924c7613 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.18.5-hf529b03_3.tar.bz2#524a9f1718bac53a6cf4906bcc51d044 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd -https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 -https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/noarch/locket-0.2.0-py_2.tar.bz2#709e8671651c7ec3d1ad07800339ff1d -https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/noarch/nose-1.3.7-py_1006.tar.bz2#382019d5f8e9362ef6f60a8d4e7bce8f -https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 -https://conda.anaconda.org/conda-forge/linux-64/proj-8.2.1-h277dcde_0.tar.bz2#f2ceb1be6565c35e2db0ac948754751d -https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff -https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.7-pyhd8ed1ab_0.tar.bz2#727e2216d9c47455d8ddc060eb2caad9 -https://conda.anaconda.org/conda-forge/noarch/pyshp-2.2.0-pyhd8ed1ab_0.tar.bz2#2aa546be05be34b8e1744afd327b623f -https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 -https://conda.anaconda.org/conda-forge/noarch/pytz-2021.3-pyhd8ed1ab_0.tar.bz2#7e4f811bff46a5a6a7e0094921389395 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.82.0-h7bff187_0.tar.bz2#fa26f833ca8796ad44f9561c5402013d +https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h7f98852_6.tar.bz2#b0f44f63f7d771d7670747a1dd5d5ac1 +https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 -https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a -https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 -https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h7f98852_6.tar.bz2#9e94bf16f14c78a36561d5019f490d22 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.0-h93bde94_1.tar.bz2#cf908994f24ea526afc59f295d5b07c1 +https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_4.tar.bz2#9c4bbee6f682f2fc7d7803df3996e77e +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc +https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 +https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba +https://conda.anaconda.org/conda-forge/noarch/sphinx-4.4.0-pyh6c4a22f_1.tar.bz2#a9025d14c2a609e0d895ad3e75b5369c https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.7.0-pyhd8ed1ab_1.tar.bz2#b689b2cbc8481b224777415e1a193170 -https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a -https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha00ac49_1009.tar.bz2#d1dff57b8731c245d3247b46d002e1c9 +https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 +https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_1.tar.bz2#52a6cee65a5d10ed1c3f0af24fb48dd3 -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 -https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.2-py38h578d9bd_0.tar.bz2#0c1ffd6807cbf6c15456c49ca9baa668 -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.3.2-py38h1fd1430_1.tar.bz2#085365abfe53d5d13bb68b1dda0b439e -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h3cfcdeb_1.tar.bz2#37d7568c595f0cfcd0c493f5ca0344ab https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.0-py38h0a891b7_1.tar.bz2#60eff55f2a845f35e58bd0be235fe4b7 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38he865349_0.tar.bz2#b1b3d6847a68251a1465206ab466b475 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_0.tar.bz2#90b4ee61abb81fb3f3995ec9d4c734f0 -https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 -https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38hbc0797c_2.tar.bz2#7e4c695d10aa5e4576e87fb00a9d2511 +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.6-hbd2fdc8_0.tar.bz2#129c70116050ccf66b362ca3203fb660 +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 -https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_4.tar.bz2#9c4bbee6f682f2fc7d7803df3996e77e -https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h497a2fe_3.tar.bz2#131de7d638aa59fb8afbce59f1a8aa98 -https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-ha98a1a1_5.tar.bz2#9b27fa0b1044a2119fb1b290617fe06f -https://conda.anaconda.org/conda-forge/linux-64/setuptools-60.9.3-py38h578d9bd_0.tar.bz2#864b832ea94d9c0b37ddfbbb8adb42f1 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h497a2fe_2.tar.bz2#63b3b55c98b4239134e0be080f448944 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.13.3-py38h578d9bd_0.tar.bz2#4f2dd671de7a8666acdc51a9dd6d4324 -https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h497a2fe_1003.tar.bz2#9189b42c42b9c87b2b2068cbe31901a8 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b -https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.1-py38h3e25421_0.tar.bz2#acc14d0d71dbf74f6a15f2456951b6cf -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.2.1-pyhd8ed1ab_0.tar.bz2#0cb751f07e68fda1d631a02faa66f0de -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.29.1-py38h497a2fe_0.tar.bz2#121e02be214af4980911bb2cbd5b2742 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-3.4.0-hb4a5f5f_0.tar.bz2#42190c4597593e9742513d7b39b02c49 -https://conda.anaconda.org/conda-forge/noarch/jinja2-3.0.3-pyhd8ed1ab_0.tar.bz2#036d872c653780cb26e797e2e2f61b4c +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h7f98852_6.tar.bz2#28bfe0a70154e6881da7bae97517c948 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h36c2ea0_1013.tar.bz2#cf7190238072a41e9579e4476a6a60b8 +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.1-py38h43a58ef_0.tar.bz2#1083ebe2edc30e4fb9568d1f66e3588b -https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff +https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 +https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.7-pyhd8ed1ab_0.tar.bz2#727e2216d9c47455d8ddc060eb2caad9 +https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d +https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#fe9a66a351bfa7a84c3108304c7bcba5 +https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.7.0-pyhd8ed1ab_1.tar.bz2#b689b2cbc8481b224777415e1a193170 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38h5383654_1.tar.bz2#5b600e019fa7c33be73bdb626236936b +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h7f98852_0.tar.bz2#5c214edc675a7fb7cbb34b1d854e5141 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.1-hd4edc92_1.tar.bz2#ebebb56f78dd7518dcc94d6c26aa2249 +https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c +https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h497a2fe_3.tar.bz2#131de7d638aa59fb8afbce59f1a8aa98 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h3ec907f_0.tar.bz2#3562860c28f129d73c4431cb69a13ce1 +https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 +https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.0-py38h43d8883_0.tar.bz2#e1d977faa8e5cec62eaf960cdf83d2b5 https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h6c62de6_1.tar.bz2#a350e3f4ca899e95122f66806e048858 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.2.0-py38h6c62de6_1.tar.bz2#2953d3fc0113fc6ffb955a5b72811fb0 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h3ec907f_1.tar.bz2#4cf16ae4d975b6935582ab87dba69f0e +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2#561e277319a41d4f24f5c05a9ef63c04 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.76-h2350873_0.tar.bz2#0ecbc685e45dfa8874a68f2cc832d9d1 +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h9c3ff4c_0.tar.bz2#fb31bcb7af058244479ca635d20f0f4a +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h7f98852_6.tar.bz2#c7c03a2592cac92246a13a0732bd1573 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.3.0-pyhd8ed1ab_0.tar.bz2#f428eca4a3c0f1bc39ca6c8f2ae53611 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.1-hcf0ee16_1.tar.bz2#4c41fc47db7d631862f12e5e5c885cb9 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h596eeab_5.tar.bz2#ec3b783081e14a9dc0eb5ce609649728 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1n-h166bdaf_0.tar.bz2#cf0ddbd911fa547e6bc4291ca5e17379 +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe +https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-1.0.0-pyhd8ed1ab_0.tar.bz2#9f633f2f2869184e31acfeae95b24345 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 -https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h1fd1430_1.tar.bz2#c494f75082f9c052944fda1b22c83336 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 -https://conda.anaconda.org/conda-forge/noarch/identify-2.4.11-pyhd8ed1ab_0.tar.bz2#979d7dfda4d04702391e80158c322039 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.5-h4dcc4a0_0.tar.bz2#56ce3e3bec0d5c9e6db22083a3ef5e13 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 -https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_14.tar.bz2#c3b55849172e4bfa01d3349ea8e73a3a +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.1.0-h40b6f09_0.tar.bz2#567de1f208bd0547b37d4ea9760606c0 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.13.96-h8e229c2_2.tar.bz2#70d2ba376dff51a33343169642aebb44 https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38ha217159_3.tar.bz2#d7461e191f7a0522e4709612786bdf4e -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_0.tar.bz2#22274a82cf664d94a9e7c8f9deddf52a +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.18-pthreads_h8fe5266_0.tar.bz2#41532e4448c0cce086d6570f95e4e12e +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 +https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.1-py38h43a58ef_0.tar.bz2#1083ebe2edc30e4fb9568d1f66e3588b +https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 +https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179 +https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_14.tar.bz2#f8b51fb7bccd48f959982973df578165 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h36c2ea0_1013.tar.bz2#dcddf696ff5dfcab567100d691678e18 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908 +https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_14.tar.bz2#47e6c01d149b26090748d9d1ac32491b +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h497a2fe_1003.tar.bz2#9189b42c42b9c87b2b2068cbe31901a8 +https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_2.tar.bz2#aa768fdaad03509a97df37f81163346b -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.17.0-py38h578d9bd_0.tar.bz2#839ac9dba9a6126c9532781a9ea4506b -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.8-pyhd8ed1ab_1.tar.bz2#53f1387c68c21cecb386e2cde51b3f7c -https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_0.tar.bz2#e5521af56c6e927397ca9851eecb2f48 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-ha770c72_0.tar.bz2#56594fdd5a80774a80d546fbbccf2c03 +https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 +https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 +https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a +https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h497a2fe_2.tar.bz2#63b3b55c98b4239134e0be080f448944 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.1-py38h578d9bd_0.tar.bz2#0d78be9cf1c400ba8e3077cf060492f1 -https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 -https://conda.anaconda.org/conda-forge/noarch/sphinx-4.4.0-pyh6c4a22f_1.tar.bz2#a9025d14c2a609e0d895ad3e75b5369c -https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a -https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-1.0.0-pyhd8ed1ab_0.tar.bz2#9f633f2f2869184e31acfeae95b24345 +https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa +https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e +https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.1-h846660c_100.tar.bz2#4b85205b094808088bb0862e08251653 +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_0.tar.bz2#90b4ee61abb81fb3f3995ec9d4c734f0 +https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 +https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38he865349_0.tar.bz2#b1b3d6847a68251a1465206ab466b475 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-hfa10184_0.tar.bz2#aac17542e50a474e2e632878dc696d50 +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b +https://conda.anaconda.org/conda-forge/noarch/nose-1.3.7-py_1006.tar.bz2#382019d5f8e9362ef6f60a8d4e7bce8f +https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.0-pyhd8ed1ab_0.tar.bz2#57701269794b583dc907037bc66ae6ec +https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b +https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 +https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.2.0-pyhd8ed1ab_0.tar.bz2#2aa546be05be34b8e1744afd327b623f +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_1.tar.bz2#63d2f874f990fdcab47c822b608d6ade +https://conda.anaconda.org/conda-forge/noarch/locket-0.2.0-py_2.tar.bz2#709e8671651c7ec3d1ad07800339ff1d +https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_14.tar.bz2#67328431d4aeab35e5a2c6c22669c22b +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 +https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 From 3ab9eb80acd1d49ba95ab8a104cd808c382b634c Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Thu, 31 Mar 2022 11:58:53 +0100 Subject: [PATCH 062/319] 'Deep' benchmarks to catch scaling with field count (#4654) * 'Deep' benchmarks to catch scaling with field count * Better sized benchmarks * What's new * Update benchmarks/benchmarks/load/__init__.py Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- benchmarks/benchmarks/load/__init__.py | 8 +++++--- docs/src/whatsnew/dev.rst | 3 +++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/benchmarks/benchmarks/load/__init__.py b/benchmarks/benchmarks/load/__init__.py index 74a751a46b..1b0ea696f6 100644 --- a/benchmarks/benchmarks/load/__init__.py +++ b/benchmarks/benchmarks/load/__init__.py @@ -24,8 +24,10 @@ class LoadAndRealise: + # For data generation + timeout = 600.0 params = [ - [(2, 2, 2), (1280, 960, 5)], + [(2, 2, 2), (1280, 960, 5), (2, 2, 1000)], [False, True], ["FF", "PP", "NetCDF"], ] @@ -68,7 +70,7 @@ def time_realise(self, _, __, ___, ____) -> None: class STASHConstraint: # xyz sizes mimic LoadAndRealise to maximise file re-use. - params = [[(2, 2, 2), (1280, 960, 5)], ["FF", "PP"]] + params = [[(2, 2, 2), (1280, 960, 5), (2, 2, 1000)], ["FF", "PP"]] param_names = ["xyz", "file_format"] def setup_cache(self) -> dict: @@ -155,7 +157,7 @@ class StructuredFF: avoiding the cost of merging. """ - params = [[(2, 2, 2), (1280, 960, 5)], [False, True]] + params = [[(2, 2, 2), (1280, 960, 5), (2, 2, 1000)], [False, True]] param_names = ["xyz", "structured_loading"] def setup_cache(self) -> dict: diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 7d4a190ac9..8cf0ac31cf 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -106,6 +106,9 @@ This document explains the changes made to Iris for this release infrastructure (see :ref:`contributing.benchmarks`), building on 2 hard years of lessons learned 🎉. (:pull:`4477`, :pull:`4562`, :pull:`4571`, :pull:`4583`, :pull:`4621`) +#. `@wjbenfold`_ used the aforementioned benchmarking infrastructure to + introduce deep (large 3rd dimension) loading and realisation benchmarks. + (:pull:`4654`) #. `@wjbenfold`_ made :func:`iris.tests.stock.simple_1d` respect the ``with_bounds`` argument. (:pull:`4658`) From 07991fd33bdd8e762579caafaef37a74890253fb Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 31 Mar 2022 15:55:05 +0100 Subject: [PATCH 063/319] Automatic reversal of DimCoord bounds (#4466) * first pass at ensuring contiguous bounds * reduce scope and fix old tests * bounds setter tests * test contiguity preserved * check bounds within reverse tests * add comment; merge test * simpler approach * revert now redundant changes * update cml * cell equality to ignore bound order * update cml * move merge test to integration * review: simple actions * review: split up cube reverse tests * review: check bounds in all cube reverse tests * whatsnew * review: reduce test repetition * tweak whatsnew * blank line --- docs/src/whatsnew/dev.rst | 10 +- lib/iris/coords.py | 23 +++- lib/iris/tests/integration/merge/__init__.py | 6 + .../tests/integration/merge/test_merge.py | 37 +++++++ .../coord_api/intersection_reversed.xml | 16 +-- .../cube_slice/2d_intersect_and_reverse.cml | 18 +-- .../results/cube_slice/2d_to_2d_revesed.cml | 20 ++-- lib/iris/tests/test_coord_api.py | 6 +- lib/iris/tests/unit/coords/test_DimCoord.py | 12 ++ lib/iris/tests/unit/util/test_reverse.py | 104 ++++++++++-------- 10 files changed, 176 insertions(+), 76 deletions(-) create mode 100644 lib/iris/tests/integration/merge/__init__.py create mode 100644 lib/iris/tests/integration/merge/test_merge.py diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 8cf0ac31cf..0307a8d82f 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -63,6 +63,14 @@ This document explains the changes made to Iris for this release cases where the requested point is float and the coordinate has integer bounds, reported at :issue:`2969`. (:pull:`4245`) +#. `@rcomer`_ modified bounds setting on :obj:`~iris.coords.DimCoord` instances + so that the order of the cell bounds is automatically reversed + to match the coordinate's direction if necessary. This is consistent with + the `Bounds for 1-D coordinate variables` subsection of the `Cell Boundaries`_ + section of the CF Conventions and ensures that contiguity is preserved if a + coordinate's direction is reversed. (:issue:`3249`, :issue:`423`, + :issue:`4078`, :issue:`3756`, :pull:`4466`) + 💣 Incompatible Changes ======================= @@ -123,4 +131,4 @@ This document explains the changes made to Iris for this release .. comment Whatsnew resources in alphabetical order: - +.. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 8bcba776fd..3862c9057b 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -1360,7 +1360,9 @@ def __eq__(self, other): else: return self.point == other elif isinstance(other, Cell): - return (self.point == other.point) and (self.bound == other.bound) + return (self.point == other.point) and ( + self.bound == other.bound or self.bound == other.bound[::-1] + ) elif ( isinstance(other, str) and self.bound is None @@ -2805,6 +2807,10 @@ def _new_bounds_requirements(self, bounds): * bounds are not masked, and * bounds are monotonic in the first dimension. + Also reverse the order of the second dimension if necessary to match the + first dimension's direction. I.e. both should increase or both should + decrease. + """ # Ensure the bounds are a compatible shape. if self.shape != bounds.shape[:-1] and not ( @@ -2854,6 +2860,16 @@ def _new_bounds_requirements(self, bounds): emsg.format(self.name(), self.__class__.__name__) ) + if n_bounds == 2: + # Make ordering of bounds consistent with coord's direction + # if possible. + (direction,) = directions + diffs = bounds[:, 0] - bounds[:, 1] + if np.all(np.sign(diffs) == direction): + bounds = np.flip(bounds, axis=1) + + return bounds + @Coord.bounds.setter def bounds(self, bounds): if bounds is not None: @@ -2862,8 +2878,9 @@ def bounds(self, bounds): # Make sure we have an array (any type of array). bounds = np.asanyarray(bounds) - # Check validity requirements for dimension-coordinate bounds. - self._new_bounds_requirements(bounds) + # Check validity requirements for dimension-coordinate bounds and reverse + # trailing dimension if necessary. + bounds = self._new_bounds_requirements(bounds) # Cast to a numpy array for masked arrays with no mask. bounds = np.array(bounds) diff --git a/lib/iris/tests/integration/merge/__init__.py b/lib/iris/tests/integration/merge/__init__.py new file mode 100644 index 0000000000..9374976532 --- /dev/null +++ b/lib/iris/tests/integration/merge/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for the :mod:`iris._merge` package.""" diff --git a/lib/iris/tests/integration/merge/test_merge.py b/lib/iris/tests/integration/merge/test_merge.py new file mode 100644 index 0000000000..f5f92a7a7d --- /dev/null +++ b/lib/iris/tests/integration/merge/test_merge.py @@ -0,0 +1,37 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Integration tests for merging cubes. + +""" + +# import iris tests first so that some things can be initialised +# before importing anything else. +import iris.tests as tests # isort:skip + +from iris.coords import DimCoord +from iris.cube import Cube, CubeList + + +class TestContiguous(tests.IrisTest): + def test_form_contiguous_dimcoord(self): + # Test that cube sliced up and remerged in the opposite order maintains + # contiguity. + cube1 = Cube([1, 2, 3], "air_temperature", units="K") + coord1 = DimCoord([3, 2, 1], long_name="spam") + coord1.guess_bounds() + cube1.add_dim_coord(coord1, 0) + cubes = CubeList(cube1.slices_over("spam")) + cube2 = cubes.merge_cube() + coord2 = cube2.coord("spam") + + self.assertTrue(coord2.is_contiguous()) + self.assertArrayEqual(coord2.points, [1, 2, 3]) + self.assertArrayEqual(coord2.bounds, coord1.bounds[::-1, ::-1]) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/results/coord_api/intersection_reversed.xml b/lib/iris/tests/results/coord_api/intersection_reversed.xml index b966a09b54..b489f95451 100644 --- a/lib/iris/tests/results/coord_api/intersection_reversed.xml +++ b/lib/iris/tests/results/coord_api/intersection_reversed.xml @@ -1,9 +1,9 @@ - + diff --git a/lib/iris/tests/results/cube_slice/2d_intersect_and_reverse.cml b/lib/iris/tests/results/cube_slice/2d_intersect_and_reverse.cml index 3f9e5fef9e..f272cebeb1 100644 --- a/lib/iris/tests/results/cube_slice/2d_intersect_and_reverse.cml +++ b/lib/iris/tests/results/cube_slice/2d_intersect_and_reverse.cml @@ -9,15 +9,15 @@ - + - + Date: Sat, 2 Apr 2022 10:09:04 +0100 Subject: [PATCH 064/319] Updated environment lockfiles (#4677) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 392 ++++++++++---------- 1 file changed, 196 insertions(+), 196 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index ae6c224e8a..b9a064b2d3 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -2,224 +2,224 @@ # platform: linux-64 # input_hash: d7bddc89ba289d4c1b48871b1289eb0bf45715ef2e274de01e245547fb6a8df6 @EXPLICIT -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 -https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h1fd1430_1.tar.bz2#c494f75082f9c052944fda1b22c83336 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.2.0-pyhd8ed1ab_0.tar.bz2#f31e31092035d427b05233ab924c7613 -https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-13_linux64_openblas.tar.bz2#018b80e8f21d8560ae4961567e3e00c9 -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.7-h27087fc_0.tar.bz2#b7fcfe2c2e21e5b696a98d774ac3f701 -https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f -https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h7f98852_6.tar.bz2#612385c4a83edb0619fe911d9da317f4 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_0.tar.bz2#0d1a1c4a830d2bc306af194f4e4896e7 https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 -https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-13_linux64_openblas.tar.bz2#8a4038563ed92dfa622bd72c0d8f31d3 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/noarch/identify-2.4.12-pyhd8ed1ab_0.tar.bz2#9f7b07b3f40905fcf600aacf63ae0c41 https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_14.tar.bz2#f8b51fb7bccd48f959982973df578165 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_14.tar.bz2#c3b55849172e4bfa01d3349ea8e73a3a +https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_14.tar.bz2#67328431d4aeab35e5a2c6c22669c22b https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_14.tar.bz2#a77fb1a92411cb8d979de1c2d81dd210 -https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 -https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_0.tar.bz2#00bdc412edc320d1717d1f7156d8edfd -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.1-h4ff8645_0.tar.bz2#8057ac02d6d10a162d7eb4b0ca7ed291 -https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe -https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff -https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2#561e277319a41d4f24f5c05a9ef63c04 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_14.tar.bz2#47e6c01d149b26090748d9d1ac32491b +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 +https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 +https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#fe9a66a351bfa7a84c3108304c7bcba5 +https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d +https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 +https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa +https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h7f98852_0.tar.bz2#5c214edc675a7fb7cbb34b1d854e5141 +https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 +https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h7f98852_6.tar.bz2#b0f44f63f7d771d7670747a1dd5d5ac1 https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.10-h7f98852_0.tar.bz2#ffa3a757a97e851293909b49f49f28fb -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.31.2-py38h0a891b7_0.tar.bz2#381ffd61d2617af9bddb1cee60352480 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.13.4-py38h578d9bd_0.tar.bz2#ff64ee0548fdd8de60b0a6a0356e0f0d https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 -https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-13_linux64_openblas.tar.bz2#b17676dbd6688396c3a3076259fb7907 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 -https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 +https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.18-pthreads_h8fe5266_0.tar.bz2#41532e4448c0cce086d6570f95e4e12e https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f -https://conda.anaconda.org/conda-forge/linux-64/curl-7.82.0-h7bff187_0.tar.bz2#631777dcd00a2714cf0c415ffe40c480 -https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.17.0-py38h578d9bd_0.tar.bz2#839ac9dba9a6126c9532781a9ea4506b -https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 +https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h166bdaf_1014.tar.bz2#757138ba3ddc6777b82e91d9ff62e7b9 +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.1-h846660c_100.tar.bz2#4b85205b094808088bb0862e08251653 +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h9c3ff4c_0.tar.bz2#fb31bcb7af058244479ca635d20f0f4a +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1n-h166bdaf_0.tar.bz2#cf0ddbd911fa547e6bc4291ca5e17379 +https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa +https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 +https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 +https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.0.10-h7f98852_0.tar.bz2#d6b0b50b49eccfe0be0373be628be0f3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-61.0.0-py38h578d9bd_0.tar.bz2#f22d6162248bb633b199c495268d8fc4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908 +https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 +https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 +https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 +https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 +https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-13_linux64_openblas.tar.bz2#8a4038563ed92dfa622bd72c0d8f31d3 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h7f98852_6.tar.bz2#c7c03a2592cac92246a13a0732bd1573 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h7f98852_6.tar.bz2#28bfe0a70154e6881da7bae97517c948 +https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba +https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 +https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-haf5c9bc_2.tar.bz2#3593de3e1062c658062a53bf3874ff41 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 +https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h166bdaf_1014.tar.bz2#def3b82d1a03aa695bb38ac1dd072ff2 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h7f98852_6.tar.bz2#9e94bf16f14c78a36561d5019f490d22 +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-13_linux64_openblas.tar.bz2#b17676dbd6688396c3a3076259fb7907 +https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-13_linux64_openblas.tar.bz2#018b80e8f21d8560ae4961567e3e00c9 +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_2.tar.bz2#bc001857cad0d2859f1507f420691bcc +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.1-h4ff8645_0.tar.bz2#8057ac02d6d10a162d7eb4b0ca7ed291 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h7f98852_6.tar.bz2#612385c4a83edb0619fe911d9da317f4 +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa -https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.1-hd4edc92_1.tar.bz2#ebebb56f78dd7518dcc94d6c26aa2249 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.82.0-h7bff187_0.tar.bz2#fa26f833ca8796ad44f9561c5402013d -https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h7f98852_6.tar.bz2#b0f44f63f7d771d7670747a1dd5d5ac1 -https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 -https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h7f98852_6.tar.bz2#9e94bf16f14c78a36561d5019f490d22 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b +https://conda.anaconda.org/conda-forge/linux-64/nss-3.77-h2350873_0.tar.bz2#260617b7829b86e9e939b01c9cad1526 +https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb +https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f +https://conda.anaconda.org/conda-forge/linux-64/curl-7.82.0-h7bff187_0.tar.bz2#631777dcd00a2714cf0c415ffe40c480 +https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.13.96-h8e229c2_2.tar.bz2#70d2ba376dff51a33343169642aebb44 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.3.0-pyhd8ed1ab_0.tar.bz2#960b78685ccbedb992886fc4ce37bf6e +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.1-hcf0ee16_1.tar.bz2#4c41fc47db7d631862f12e5e5c885cb9 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 +https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/noarch/locket-0.2.0-py_2.tar.bz2#709e8671651c7ec3d1ad07800339ff1d +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 +https://conda.anaconda.org/conda-forge/noarch/nose-1.3.7-py_1006.tar.bz2#382019d5f8e9362ef6f60a8d4e7bce8f +https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.0-h93bde94_1.tar.bz2#cf908994f24ea526afc59f295d5b07c1 -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_4.tar.bz2#9c4bbee6f682f2fc7d7803df3996e77e -https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 -https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc -https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 -https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba -https://conda.anaconda.org/conda-forge/noarch/sphinx-4.4.0-pyh6c4a22f_1.tar.bz2#a9025d14c2a609e0d895ad3e75b5369c +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.7-pyhd8ed1ab_0.tar.bz2#727e2216d9c47455d8ddc060eb2caad9 +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.2.0-pyhd8ed1ab_0.tar.bz2#2aa546be05be34b8e1744afd327b623f +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 +https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 +https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a +https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 +https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 -https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_1.tar.bz2#52a6cee65a5d10ed1c3f0af24fb48dd3 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.7.0-pyhd8ed1ab_1.tar.bz2#b689b2cbc8481b224777415e1a193170 +https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a +https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d +https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 +https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_1.tar.bz2#21862e22238907d485e460f9c2e9ae4d +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_0.tar.bz2#f6962263e6503aed8b6ea087d8cc892e +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38hbc0797c_2.tar.bz2#7e4c695d10aa5e4576e87fb00a9d2511 -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb -https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.6-hbd2fdc8_0.tar.bz2#129c70116050ccf66b362ca3203fb660 -https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38he865349_0.tar.bz2#b1b3d6847a68251a1465206ab466b475 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_0.tar.bz2#90b4ee61abb81fb3f3995ec9d4c734f0 +https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 +https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h7f98852_6.tar.bz2#28bfe0a70154e6881da7bae97517c948 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h36c2ea0_1013.tar.bz2#cf7190238072a41e9579e4476a6a60b8 -https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef -https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 -https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.7-pyhd8ed1ab_0.tar.bz2#727e2216d9c47455d8ddc060eb2caad9 -https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d -https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#fe9a66a351bfa7a84c3108304c7bcba5 -https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.7.0-pyhd8ed1ab_1.tar.bz2#b689b2cbc8481b224777415e1a193170 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 +https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc +https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_4.tar.bz2#9c4bbee6f682f2fc7d7803df3996e77e +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 +https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe +https://conda.anaconda.org/conda-forge/linux-64/setuptools-61.3.0-py38h578d9bd_0.tar.bz2#9d042d3e103851e2f59433313ff84df4 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.0-py38h578d9bd_1.tar.bz2#6c6a5da4d7e2af1dbbfb1e21daa559cc +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h497a2fe_1003.tar.bz2#9189b42c42b9c87b2b2068cbe31901a8 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b +https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_0.tar.bz2#22274a82cf664d94a9e7c8f9deddf52a +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.0-pyhd8ed1ab_0.tar.bz2#a47051950a34bef40d8369a5f320b78d +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.31.2-py38h0a891b7_0.tar.bz2#381ffd61d2617af9bddb1cee60352480 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.0-h40b6f09_0.tar.bz2#017b20e7e98860f0bfa7492ce16390a7 +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.1-pyhd8ed1ab_0.tar.bz2#40b3f446c61729cdd21ed9d85627df6e +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.1-py38h43a58ef_0.tar.bz2#1083ebe2edc30e4fb9568d1f66e3588b +https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 -https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h7f98852_0.tar.bz2#5c214edc675a7fb7cbb34b1d854e5141 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.1-hd4edc92_1.tar.bz2#ebebb56f78dd7518dcc94d6c26aa2249 -https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h497a2fe_3.tar.bz2#131de7d638aa59fb8afbce59f1a8aa98 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h3ec907f_0.tar.bz2#3562860c28f129d73c4431cb69a13ce1 -https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 -https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.0-py38h43d8883_0.tar.bz2#e1d977faa8e5cec62eaf960cdf83d2b5 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38hbc0797c_2.tar.bz2#7e4c695d10aa5e4576e87fb00a9d2511 https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h3ec907f_1.tar.bz2#4cf16ae4d975b6935582ab87dba69f0e -https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2#561e277319a41d4f24f5c05a9ef63c04 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.76-h2350873_0.tar.bz2#0ecbc685e45dfa8874a68f2cc832d9d1 -https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h9c3ff4c_0.tar.bz2#fb31bcb7af058244479ca635d20f0f4a -https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h7f98852_6.tar.bz2#c7c03a2592cac92246a13a0732bd1573 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.3.0-pyhd8ed1ab_0.tar.bz2#f428eca4a3c0f1bc39ca6c8f2ae53611 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.1-hcf0ee16_1.tar.bz2#4c41fc47db7d631862f12e5e5c885cb9 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h3ec907f_0.tar.bz2#3562860c28f129d73c4431cb69a13ce1 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h596eeab_5.tar.bz2#ec3b783081e14a9dc0eb5ce609649728 -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1n-h166bdaf_0.tar.bz2#cf0ddbd911fa547e6bc4291ca5e17379 -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe -https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-1.0.0-pyhd8ed1ab_0.tar.bz2#9f633f2f2869184e31acfeae95b24345 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_14.tar.bz2#c3b55849172e4bfa01d3349ea8e73a3a -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.1.0-h40b6f09_0.tar.bz2#567de1f208bd0547b37d4ea9760606c0 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.13.96-h8e229c2_2.tar.bz2#70d2ba376dff51a33343169642aebb44 -https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea -https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a -https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_0.tar.bz2#22274a82cf664d94a9e7c8f9deddf52a -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.18-pthreads_h8fe5266_0.tar.bz2#41532e4448c0cce086d6570f95e4e12e -https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 -https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.1-py38h43a58ef_0.tar.bz2#1083ebe2edc30e4fb9568d1f66e3588b -https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179 -https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 -https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_14.tar.bz2#f8b51fb7bccd48f959982973df578165 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h36c2ea0_1013.tar.bz2#dcddf696ff5dfcab567100d691678e18 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908 -https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_14.tar.bz2#47e6c01d149b26090748d9d1ac32491b -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h1fd1430_1.tar.bz2#c494f75082f9c052944fda1b22c83336 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/noarch/identify-2.4.12-pyhd8ed1ab_0.tar.bz2#9f7b07b3f40905fcf600aacf63ae0c41 +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.6-hbd2fdc8_0.tar.bz2#129c70116050ccf66b362ca3203fb660 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 -https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf -https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h497a2fe_1003.tar.bz2#9189b42c42b9c87b2b2068cbe31901a8 -https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d -https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e +https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 +https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_2.tar.bz2#aa768fdaad03509a97df37f81163346b -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-ha770c72_0.tar.bz2#56594fdd5a80774a80d546fbbccf2c03 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 -https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 -https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a -https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h497a2fe_2.tar.bz2#63b3b55c98b4239134e0be080f448944 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.1-py38h578d9bd_0.tar.bz2#0d78be9cf1c400ba8e3077cf060492f1 -https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa -https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e -https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.1-h846660c_100.tar.bz2#4b85205b094808088bb0862e08251653 -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_0.tar.bz2#90b4ee61abb81fb3f3995ec9d4c734f0 -https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 -https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38he865349_0.tar.bz2#b1b3d6847a68251a1465206ab466b475 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-hfa10184_0.tar.bz2#aac17542e50a474e2e632878dc696d50 -https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 -https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.17.0-py38h578d9bd_0.tar.bz2#839ac9dba9a6126c9532781a9ea4506b https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b -https://conda.anaconda.org/conda-forge/noarch/nose-1.3.7-py_1006.tar.bz2#382019d5f8e9362ef6f60a8d4e7bce8f -https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.0-pyhd8ed1ab_0.tar.bz2#57701269794b583dc907037bc66ae6ec -https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b -https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 -https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/pyshp-2.2.0-pyhd8ed1ab_0.tar.bz2#2aa546be05be34b8e1744afd327b623f -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 -https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.1-py38h578d9bd_0.tar.bz2#0d78be9cf1c400ba8e3077cf060492f1 +https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 +https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 +https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-1.0.0-pyhd8ed1ab_0.tar.bz2#9f633f2f2869184e31acfeae95b24345 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_1.tar.bz2#63d2f874f990fdcab47c822b608d6ade -https://conda.anaconda.org/conda-forge/noarch/locket-0.2.0-py_2.tar.bz2#709e8671651c7ec3d1ad07800339ff1d -https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_14.tar.bz2#67328431d4aeab35e5a2c6c22669c22b -https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 -https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 From ceff5000f9a6f2099f387d9a8bc6149cb942349f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 15:44:48 +0100 Subject: [PATCH 065/319] Updated environment lockfiles (#4678) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index b9a064b2d3..7883f1deaa 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -31,7 +31,7 @@ https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1 https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h7f98852_0.tar.bz2#5c214edc675a7fb7cbb34b1d854e5141 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e -https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h7f98852_6.tar.bz2#b0f44f63f7d771d7670747a1dd5d5ac1 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.10-h7f98852_0.tar.bz2#ffa3a757a97e851293909b49f49f28fb https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 @@ -65,8 +65,8 @@ https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f6 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-13_linux64_openblas.tar.bz2#8a4038563ed92dfa622bd72c0d8f31d3 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h7f98852_6.tar.bz2#c7c03a2592cac92246a13a0732bd1573 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h7f98852_6.tar.bz2#28bfe0a70154e6881da7bae97517c948 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba @@ -79,7 +79,7 @@ https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h166bdaf_1014.tar.bz2#def3b82d1a03aa695bb38ac1dd072ff2 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 -https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h7f98852_6.tar.bz2#9e94bf16f14c78a36561d5019f490d22 +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-13_linux64_openblas.tar.bz2#b17676dbd6688396c3a3076259fb7907 @@ -96,7 +96,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_2.tar https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.1-h4ff8645_0.tar.bz2#8057ac02d6d10a162d7eb4b0ca7ed291 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h7f98852_6.tar.bz2#612385c4a83edb0619fe911d9da317f4 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 @@ -155,7 +155,7 @@ https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_1.tar.bz2#21862e22238907d485e460f9c2e9ae4d -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_0.tar.bz2#f6962263e6503aed8b6ea087d8cc892e +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_1.tar.bz2#34c284cb94bd8c5118ccfe6d6fc3dd3f https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 @@ -166,7 +166,7 @@ https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_4.tar.bz2#9c4bbee6f682f2fc7d7803df3996e77e +https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 @@ -175,7 +175,7 @@ https://conda.anaconda.org/conda-forge/linux-64/setuptools-61.3.0-py38h578d9bd_0 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.0-py38h578d9bd_1.tar.bz2#6c6a5da4d7e2af1dbbfb1e21daa559cc -https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h497a2fe_1003.tar.bz2#9189b42c42b9c87b2b2068cbe31901a8 +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_0.tar.bz2#22274a82cf664d94a9e7c8f9deddf52a https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.0-pyhd8ed1ab_0.tar.bz2#a47051950a34bef40d8369a5f320b78d From a78230e996584a998bab4007f4acf8d89fc013c0 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Mon, 4 Apr 2022 15:58:07 +0100 Subject: [PATCH 066/319] Reorder Constraints sections of user guide, add time bounds example (#4656) * move constraint detail to subsetting cube, add example of constraining bound * Add whats new --- docs/src/userguide/cube_maths.rst | 2 +- docs/src/userguide/loading_iris_cubes.rst | 237 +--------------------- docs/src/userguide/subsetting_a_cube.rst | 234 ++++++++++++++++++++- docs/src/whatsnew/dev.rst | 3 + 4 files changed, 234 insertions(+), 242 deletions(-) diff --git a/docs/src/userguide/cube_maths.rst b/docs/src/userguide/cube_maths.rst index e8a1744a44..fe9a5d63d2 100644 --- a/docs/src/userguide/cube_maths.rst +++ b/docs/src/userguide/cube_maths.rst @@ -38,7 +38,7 @@ Let's load some air temperature which runs from 1860 to 2100:: air_temp = iris.load_cube(filename, 'air_temperature') We can now get the first and last time slices using indexing -(see :ref:`subsetting_a_cube` for a reminder):: +(see :ref:`cube_indexing` for a reminder):: t_first = air_temp[0, :, :] t_last = air_temp[-1, :, :] diff --git a/docs/src/userguide/loading_iris_cubes.rst b/docs/src/userguide/loading_iris_cubes.rst index fb938975e8..9e9c343300 100644 --- a/docs/src/userguide/loading_iris_cubes.rst +++ b/docs/src/userguide/loading_iris_cubes.rst @@ -206,241 +206,8 @@ a specific ``model_level_number``:: level_10 = iris.Constraint(model_level_number=10) cubes = iris.load(filename, level_10) -Constraints can be combined using ``&`` to represent a more restrictive -constraint to ``load``:: - - filename = iris.sample_data_path('uk_hires.pp') - forecast_6 = iris.Constraint(forecast_period=6) - level_10 = iris.Constraint(model_level_number=10) - cubes = iris.load(filename, forecast_6 & level_10) - -.. note:: - - Whilst ``&`` is supported, the ``|`` that might reasonably be expected is - not. Explanation as to why is in the :class:`iris.Constraint` reference - documentation. - - For an example of constraining to multiple ranges of the same coordinate to - generate one cube, see the :class:`iris.Constraint` reference documentation. - - To generate multiple cubes, each constrained to a different range of the - same coordinate, use :py:func:`iris.load_cubes`. - -As well as being able to combine constraints using ``&``, -the :class:`iris.Constraint` class can accept multiple arguments, -and a list of values can be given to constrain a coordinate to one of -a collection of values:: - - filename = iris.sample_data_path('uk_hires.pp') - level_10_or_16_fp_6 = iris.Constraint(model_level_number=[10, 16], forecast_period=6) - cubes = iris.load(filename, level_10_or_16_fp_6) - -A common requirement is to limit the value of a coordinate to a specific range, -this can be achieved by passing the constraint a function:: - - def bottom_16_levels(cell): - # return True or False as to whether the cell in question should be kept - return cell <= 16 - - filename = iris.sample_data_path('uk_hires.pp') - level_lt_16 = iris.Constraint(model_level_number=bottom_16_levels) - cubes = iris.load(filename, level_lt_16) - -.. note:: - - As with many of the examples later in this documentation, the - simple function above can be conveniently written as a lambda function - on a single line:: - - bottom_16_levels = lambda cell: cell <= 16 - - -Note also the :ref:`warning on equality constraints with floating point coordinates `. - - -Cube attributes can also be part of the constraint criteria. Supposing a -cube attribute of ``STASH`` existed, as is the case when loading ``PP`` files, -then specific STASH codes can be filtered:: - - filename = iris.sample_data_path('uk_hires.pp') - level_10_with_stash = iris.AttributeConstraint(STASH='m01s00i004') & iris.Constraint(model_level_number=10) - cubes = iris.load(filename, level_10_with_stash) - -.. seealso:: - - For advanced usage there are further examples in the - :class:`iris.Constraint` reference documentation. - - -Constraining a Circular Coordinate Across its Boundary -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Occasionally you may need to constrain your cube with a region that crosses the -boundary of a circular coordinate (this is often the meridian or the dateline / -antimeridian). An example use-case of this is to extract the entire Pacific Ocean -from a cube whose longitudes are bounded by the dateline. - -This functionality cannot be provided reliably using constraints. Instead you should use the -functionality provided by :meth:`cube.intersection ` -to extract this region. - - -.. _using-time-constraints: - -Constraining on Time -^^^^^^^^^^^^^^^^^^^^ -Iris follows NetCDF-CF rules in representing time coordinate values as normalised, -purely numeric, values which are normalised by the calendar specified in the coordinate's -units (e.g. "days since 1970-01-01"). -However, when constraining by time we usually want to test calendar-related -aspects such as hours of the day or months of the year, so Iris -provides special features to facilitate this: - -Firstly, when Iris evaluates Constraint expressions, it will convert time-coordinate -values (points and bounds) from numbers into :class:`~datetime.datetime`-like objects -for ease of calendar-based testing. - - >>> filename = iris.sample_data_path('uk_hires.pp') - >>> cube_all = iris.load_cube(filename, 'air_potential_temperature') - >>> print('All times :\n' + str(cube_all.coord('time'))) - All times : - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) - points: [2009-11-19 10:00:00, 2009-11-19 11:00:00, 2009-11-19 12:00:00] - shape: (3,) - dtype: float64 - standard_name: 'time' - >>> # Define a function which accepts a datetime as its argument (this is simplified in later examples). - >>> hour_11 = iris.Constraint(time=lambda cell: cell.point.hour == 11) - >>> cube_11 = cube_all.extract(hour_11) - >>> print('Selected times :\n' + str(cube_11.coord('time'))) - Selected times : - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) - points: [2009-11-19 11:00:00] - shape: (1,) - dtype: float64 - standard_name: 'time' - -Secondly, the :class:`iris.time` module provides flexible time comparison -facilities. An :class:`iris.time.PartialDateTime` object can be compared to -objects such as :class:`datetime.datetime` instances, and this comparison will -then test only those 'aspects' which the PartialDateTime instance defines: - - >>> import datetime - >>> from iris.time import PartialDateTime - >>> dt = datetime.datetime(2011, 3, 7) - >>> print(dt > PartialDateTime(year=2010, month=6)) - True - >>> print(dt > PartialDateTime(month=6)) - False - >>> - -These two facilities can be combined to provide straightforward calendar-based -time selections when loading or extracting data. - -The previous constraint example can now be written as: - - >>> the_11th_hour = iris.Constraint(time=iris.time.PartialDateTime(hour=11)) - >>> print(iris.load_cube( - ... iris.sample_data_path('uk_hires.pp'), - ... 'air_potential_temperature' & the_11th_hour).coord('time')) - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) - points: [2009-11-19 11:00:00] - shape: (1,) - dtype: float64 - standard_name: 'time' - -It is common that a cube will need to be constrained between two given dates. -In the following example we construct a time sequence representing the first -day of every week for many years: - -.. testsetup:: timeseries_range - - import datetime - import numpy as np - from iris.time import PartialDateTime - long_ts = iris.cube.Cube(np.arange(150), long_name='data', units='1') - _mondays = iris.coords.DimCoord(7 * np.arange(150), standard_name='time', units='days since 2007-04-09') - long_ts.add_dim_coord(_mondays, 0) - - -.. doctest:: timeseries_range - :options: +NORMALIZE_WHITESPACE, +ELLIPSIS - - >>> print(long_ts.coord('time')) - DimCoord : time / (days since 2007-04-09, gregorian calendar) - points: [ - 2007-04-09 00:00:00, 2007-04-16 00:00:00, ..., - 2010-02-08 00:00:00, 2010-02-15 00:00:00] - shape: (150,) - dtype: int64 - standard_name: 'time' - -Given two dates in datetime format, we can select all points between them. - -.. doctest:: timeseries_range - :options: +NORMALIZE_WHITESPACE, +ELLIPSIS - - >>> d1 = datetime.datetime.strptime('20070715T0000Z', '%Y%m%dT%H%MZ') - >>> d2 = datetime.datetime.strptime('20070825T0000Z', '%Y%m%dT%H%MZ') - >>> st_swithuns_daterange_07 = iris.Constraint( - ... time=lambda cell: d1 <= cell.point < d2) - >>> within_st_swithuns_07 = long_ts.extract(st_swithuns_daterange_07) - >>> print(within_st_swithuns_07.coord('time')) - DimCoord : time / (days since 2007-04-09, gregorian calendar) - points: [ - 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, - 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00] - shape: (6,) - dtype: int64 - standard_name: 'time' - -Alternatively, we may rewrite this using :class:`iris.time.PartialDateTime` -objects. - -.. doctest:: timeseries_range - :options: +NORMALIZE_WHITESPACE, +ELLIPSIS - - >>> pdt1 = PartialDateTime(year=2007, month=7, day=15) - >>> pdt2 = PartialDateTime(year=2007, month=8, day=25) - >>> st_swithuns_daterange_07 = iris.Constraint( - ... time=lambda cell: pdt1 <= cell.point < pdt2) - >>> within_st_swithuns_07 = long_ts.extract(st_swithuns_daterange_07) - >>> print(within_st_swithuns_07.coord('time')) - DimCoord : time / (days since 2007-04-09, gregorian calendar) - points: [ - 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, - 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00] - shape: (6,) - dtype: int64 - standard_name: 'time' - -A more complex example might require selecting points over an annually repeating -date range. We can select points within a certain part of the year, in this case -between the 15th of July through to the 25th of August. By making use of -PartialDateTime this becomes simple: - -.. doctest:: timeseries_range - - >>> st_swithuns_daterange = iris.Constraint( - ... time=lambda cell: PartialDateTime(month=7, day=15) <= cell < PartialDateTime(month=8, day=25)) - >>> within_st_swithuns = long_ts.extract(st_swithuns_daterange) - ... - >>> # Note: using summary(max_values) to show more of the points - >>> print(within_st_swithuns.coord('time').summary(max_values=100)) - DimCoord : time / (days since 2007-04-09, gregorian calendar) - points: [ - 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, - 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00, - 2008-07-21 00:00:00, 2008-07-28 00:00:00, 2008-08-04 00:00:00, - 2008-08-11 00:00:00, 2008-08-18 00:00:00, 2009-07-20 00:00:00, - 2009-07-27 00:00:00, 2009-08-03 00:00:00, 2009-08-10 00:00:00, - 2009-08-17 00:00:00, 2009-08-24 00:00:00] - shape: (17,) - dtype: int64 - standard_name: 'time' - -Notice how the dates printed are between the range specified in the ``st_swithuns_daterange`` -and that they span multiple years. +Further details on using :class:`iris.Constraint` are +discussed later in :ref:`cube_extraction`. .. _strict-loading: diff --git a/docs/src/userguide/subsetting_a_cube.rst b/docs/src/userguide/subsetting_a_cube.rst index 5112d9689a..6523ab1cd7 100644 --- a/docs/src/userguide/subsetting_a_cube.rst +++ b/docs/src/userguide/subsetting_a_cube.rst @@ -10,9 +10,10 @@ However it is often necessary to reduce the dimensionality of a cube down to som Iris provides several ways of reducing both the amount of data and/or the number of dimensions in your cube depending on the circumstance. In all cases **the subset of a valid cube is itself a valid cube**. +.. _cube_extraction: Cube Extraction -^^^^^^^^^^^^^^^^ +--------------- A subset of a cube can be "extracted" from a multi-dimensional cube in order to reduce its dimensionality: >>> import iris @@ -34,15 +35,14 @@ A subset of a cube can be "extracted" from a multi-dimensional cube in order to In this example we start with a 3 dimensional cube, with dimensions of ``height``, ``grid_latitude`` and ``grid_longitude``, -and extract every point where the latitude is 0, resulting in a 2d cube with axes of ``height`` and ``grid_longitude``. - +and use :class:`iris.Constraint` to extract every point where the latitude is 0, resulting in a 2d cube with axes of ``height`` and ``grid_longitude``. .. _floating-point-warning: .. warning:: Caution is required when using equality constraints with floating point coordinates such as ``grid_latitude``. Printing the points of a coordinate does not necessarily show the full precision of the underlying number and it - is very easy return no matches to a constraint when one was expected. + is very easy to return no matches to a constraint when one was expected. This can be avoided by using a function as the argument to the constraint:: def near_zero(cell): @@ -68,6 +68,33 @@ The two steps required to get ``height`` of 9000 m at the equator can be simplif equator_height_9km_slice = cube.extract(iris.Constraint(grid_latitude=0, height=9000)) print(equator_height_9km_slice) +Alternatively, constraints can be combined using ``&``:: + + cube = iris.load_cube(filename, 'electron density') + equator_constraint = iris.Constraint(grid_latitude=0) + height_constraint = iris.Constraint(height=9000) + equator_height_9km_slice = cube.extract(equator_constraint & height_constraint) + +.. note:: + + Whilst ``&`` is supported, the ``|`` that might reasonably be expected is + not. Explanation as to why is in the :class:`iris.Constraint` reference + documentation. + + For an example of constraining to multiple ranges of the same coordinate to + generate one cube, see the :class:`iris.Constraint` reference documentation. + +A common requirement is to limit the value of a coordinate to a specific range, +this can be achieved by passing the constraint a function:: + + def below_9km(cell): + # return True or False as to whether the cell in question should be kept + return cell <= 9000 + + cube = iris.load_cube(filename, 'electron density') + height_below_9km = iris.Constraint(height=below_9km) + below_9km_slice = cube.extract(height_below_9km) + As we saw in :doc:`loading_iris_cubes` the result of :func:`iris.load` is a :class:`CubeList `. The ``extract`` method also exists on a :class:`CubeList ` and behaves in exactly the same way as loading with constraints: @@ -100,9 +127,203 @@ same way as loading with constraints: source 'Data from Met Office Unified Model' um_version '7.3' +Cube attributes can also be part of the constraint criteria. Supposing a +cube attribute of ``STASH`` existed, as is the case when loading ``PP`` files, +then specific STASH codes can be filtered:: + + filename = iris.sample_data_path('uk_hires.pp') + level_10_with_stash = iris.AttributeConstraint(STASH='m01s00i004') & iris.Constraint(model_level_number=10) + cubes = iris.load(filename).extract(level_10_with_stash) + +.. seealso:: + + For advanced usage there are further examples in the + :class:`iris.Constraint` reference documentation. + +Constraining a Circular Coordinate Across its Boundary +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Occasionally you may need to constrain your cube with a region that crosses the +boundary of a circular coordinate (this is often the meridian or the dateline / +antimeridian). An example use-case of this is to extract the entire Pacific Ocean +from a cube whose longitudes are bounded by the dateline. + +This functionality cannot be provided reliably using constraints. Instead you should use the +functionality provided by :meth:`cube.intersection ` +to extract this region. + + +.. _using-time-constraints: + +Constraining on Time +^^^^^^^^^^^^^^^^^^^^ +Iris follows NetCDF-CF rules in representing time coordinate values as normalised, +purely numeric, values which are normalised by the calendar specified in the coordinate's +units (e.g. "days since 1970-01-01"). +However, when constraining by time we usually want to test calendar-related +aspects such as hours of the day or months of the year, so Iris +provides special features to facilitate this. + +Firstly, when Iris evaluates :class:`iris.Constraint` expressions, it will convert +time-coordinate values (points and bounds) from numbers into :class:`~datetime.datetime`-like +objects for ease of calendar-based testing. + + >>> filename = iris.sample_data_path('uk_hires.pp') + >>> cube_all = iris.load_cube(filename, 'air_potential_temperature') + >>> print('All times :\n' + str(cube_all.coord('time'))) + All times : + DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + points: [2009-11-19 10:00:00, 2009-11-19 11:00:00, 2009-11-19 12:00:00] + shape: (3,) + dtype: float64 + standard_name: 'time' + >>> # Define a function which accepts a datetime as its argument (this is simplified in later examples). + >>> hour_11 = iris.Constraint(time=lambda cell: cell.point.hour == 11) + >>> cube_11 = cube_all.extract(hour_11) + >>> print('Selected times :\n' + str(cube_11.coord('time'))) + Selected times : + DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + points: [2009-11-19 11:00:00] + shape: (1,) + dtype: float64 + standard_name: 'time' + +Secondly, the :class:`iris.time` module provides flexible time comparison +facilities. An :class:`iris.time.PartialDateTime` object can be compared to +objects such as :class:`datetime.datetime` instances, and this comparison will +then test only those 'aspects' which the PartialDateTime instance defines: + + >>> import datetime + >>> from iris.time import PartialDateTime + >>> dt = datetime.datetime(2011, 3, 7) + >>> print(dt > PartialDateTime(year=2010, month=6)) + True + >>> print(dt > PartialDateTime(month=6)) + False + +These two facilities can be combined to provide straightforward calendar-based +time selections when loading or extracting data. + +The previous constraint example can now be written as: + + >>> the_11th_hour = iris.Constraint(time=iris.time.PartialDateTime(hour=11)) + >>> print(iris.load_cube( + ... iris.sample_data_path('uk_hires.pp'), + ... 'air_potential_temperature' & the_11th_hour).coord('time')) + DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + points: [2009-11-19 11:00:00] + shape: (1,) + dtype: float64 + standard_name: 'time' + +It is common that a cube will need to be constrained between two given dates. +In the following example we construct a time sequence representing the first +day of every week for many years: + +.. testsetup:: timeseries_range + + import datetime + import numpy as np + from iris.time import PartialDateTime + long_ts = iris.cube.Cube(np.arange(150), long_name='data', units='1') + _mondays = iris.coords.DimCoord(7 * np.arange(150), standard_name='time', units='days since 2007-04-09') + long_ts.add_dim_coord(_mondays, 0) + + +.. doctest:: timeseries_range + :options: +NORMALIZE_WHITESPACE, +ELLIPSIS + + >>> print(long_ts.coord('time')) + DimCoord : time / (days since 2007-04-09, gregorian calendar) + points: [ + 2007-04-09 00:00:00, 2007-04-16 00:00:00, ..., + 2010-02-08 00:00:00, 2010-02-15 00:00:00] + shape: (150,) + dtype: int64 + standard_name: 'time' + +Given two dates in datetime format, we can select all points between them. +Instead of constraining at loaded time, we already have the time coord so +we constrain that coord using :class:`iris.cube.Cube.extract` + +.. doctest:: timeseries_range + :options: +NORMALIZE_WHITESPACE, +ELLIPSIS + + >>> d1 = datetime.datetime.strptime('20070715T0000Z', '%Y%m%dT%H%MZ') + >>> d2 = datetime.datetime.strptime('20070825T0000Z', '%Y%m%dT%H%MZ') + >>> st_swithuns_daterange_07 = iris.Constraint( + ... time=lambda cell: d1 <= cell.point < d2) + >>> within_st_swithuns_07 = long_ts.extract(st_swithuns_daterange_07) + >>> print(within_st_swithuns_07.coord('time')) + DimCoord : time / (days since 2007-04-09, gregorian calendar) + points: [ + 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, + 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00] + shape: (6,) + dtype: int64 + standard_name: 'time' + +Alternatively, we may rewrite this using :class:`iris.time.PartialDateTime` +objects. + +.. doctest:: timeseries_range + :options: +NORMALIZE_WHITESPACE, +ELLIPSIS + + >>> pdt1 = PartialDateTime(year=2007, month=7, day=15) + >>> pdt2 = PartialDateTime(year=2007, month=8, day=25) + >>> st_swithuns_daterange_07 = iris.Constraint( + ... time=lambda cell: pdt1 <= cell.point < pdt2) + >>> within_st_swithuns_07 = long_ts.extract(st_swithuns_daterange_07) + >>> print(within_st_swithuns_07.coord('time')) + DimCoord : time / (days since 2007-04-09, gregorian calendar) + points: [ + 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, + 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00] + shape: (6,) + dtype: int64 + standard_name: 'time' + +A more complex example might require selecting points over an annually repeating +date range. We can select points within a certain part of the year, in this case +between the 15th of July through to the 25th of August. By making use of +PartialDateTime this becomes simple: + +.. doctest:: timeseries_range + + >>> st_swithuns_daterange = iris.Constraint( + ... time=lambda cell: PartialDateTime(month=7, day=15) <= cell.point < PartialDateTime(month=8, day=25)) + >>> within_st_swithuns = long_ts.extract(st_swithuns_daterange) + ... + >>> # Note: using summary(max_values) to show more of the points + >>> print(within_st_swithuns.coord('time').summary(max_values=100)) + DimCoord : time / (days since 2007-04-09, gregorian calendar) + points: [ + 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, + 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00, + 2008-07-21 00:00:00, 2008-07-28 00:00:00, 2008-08-04 00:00:00, + 2008-08-11 00:00:00, 2008-08-18 00:00:00, 2009-07-20 00:00:00, + 2009-07-27 00:00:00, 2009-08-03 00:00:00, 2009-08-10 00:00:00, + 2009-08-17 00:00:00, 2009-08-24 00:00:00] + shape: (17,) + dtype: int64 + standard_name: 'time' + +Notice how the dates printed are between the range specified in the ``st_swithuns_daterange`` +and that they span multiple years. + +The above examples involve constraining on the points of the time coordinate. Constraining +on bounds can be done in the following way:: + + filename = iris.sample_data_path('ostia_monthly.nc') + cube = iris.load_cube(filename, 'surface_temperature') + dtmin = datetime.datetime(2008, 1, 1) + cube.extract(iris.Constraint(time = lambda cell: any(bound > dtmin for bound in cell.bound))) + +The above example constrains to cells where either the upper or lower bound occur +after 1st January 2008. Cube Iteration -^^^^^^^^^^^^^^^ +-------------- It is not possible to directly iterate over an Iris cube. That is, you cannot use code such as ``for x in cube:``. However, you can iterate over cube slices, as this section details. @@ -151,9 +372,10 @@ slicing the 3 dimensional cube (15, 100, 100) by longitude (i starts at 0 and 15 Once the your code can handle a 2d slice, it is then an easy step to loop over **all** 2d slices within the bigger cube using the slices method. +.. _cube_indexing: Cube Indexing -^^^^^^^^^^^^^ +------------- In the same way that you would expect a numeric multidimensional array to be **indexed** to take a subset of your original array, you can **index** a Cube for the same purpose. diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 0307a8d82f..4e7a16adb2 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -105,6 +105,9 @@ This document explains the changes made to Iris for this release :ref:`voted_issues`. (:issue:`3307`, :pull:`4617`) #. `@wjbenfold`_ added a note about fixing proxy URLs in lockfiles generated because dependencies have changed. (:pull:`4666`) +#. `@lbdreyer`_ moved most of the User Guide's :class:`iris.Constraint` examples + from :doc:`loading_iris_cubes` to :ref:`cube_extraction` and added an + example of constraining on bounded time. (:pull:`4656`) 💼 Internal From 5eeb2c02215122c038c60e76b784cdb37f8a1d94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Apr 2022 10:34:22 +0100 Subject: [PATCH 067/319] Bump peter-evans/create-pull-request from 4.0.0 to 4.0.1 (#4675) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/d6d5519d05f5814158ef015b8448f2f74648c421...f1a7646cead32c950d90344a4fb5d4e926972a8f) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index d191910b44..c48a631bbd 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -72,7 +72,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@d6d5519d05f5814158ef015b8448f2f74648c421 + uses: peter-evans/create-pull-request@f1a7646cead32c950d90344a4fb5d4e926972a8f with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From 3a8eee44e1e2dfc39b9ee3ee3e4b494c44ff7c4a Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 6 Apr 2022 14:00:40 +0100 Subject: [PATCH 068/319] tweak whatsnew (#4682) --- docs/src/whatsnew/dev.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst index 4e7a16adb2..843815b46b 100644 --- a/docs/src/whatsnew/dev.rst +++ b/docs/src/whatsnew/dev.rst @@ -106,7 +106,7 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ added a note about fixing proxy URLs in lockfiles generated because dependencies have changed. (:pull:`4666`) #. `@lbdreyer`_ moved most of the User Guide's :class:`iris.Constraint` examples - from :doc:`loading_iris_cubes` to :ref:`cube_extraction` and added an + from :ref:`loading_iris_cubes` to :ref:`cube_extraction` and added an example of constraining on bounded time. (:pull:`4656`) From 591c9f4817e04422cd076a878704afc85482f1dc Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Thu, 7 Apr 2022 17:11:44 +0100 Subject: [PATCH 069/319] Implement Pydata theme (#4661) * new theme baseline * update * added discussion directive and whatsnew * fixed bullet point indentation * updated NOX_CACHE_BUILD to force update the nox cache * added pr ref * Update lock-file generation in response to conda-lock change * Make lockfile shareable and bump nox cache * MAin branch updates to latest.rst (from dev.rst) * change references of dev.rst to latest.rst * corrected bullet points indentation to render correctly. * updated * update whatsnew * Review action and fixed typo for link * update lock * updated from main Co-authored-by: Will Benfold Co-authored-by: Bill Little --- .cirrus.yml | 2 +- docs/gallery_code/README.rst | 2 + docs/gallery_code/general/README.rst | 1 + docs/src/_static/icon_api.svg | 144 ++++++++++ docs/src/_static/icon_development.svg | 63 +++++ docs/src/_static/icon_instructions.svg | 162 ++++++++++++ docs/src/_static/icon_new_product.svg | 182 +++++++++++++ docs/src/_static/icon_shuttle.svg | 71 +++++ docs/src/_static/icon_support.png | Bin 0 -> 8037 bytes docs/src/_static/icon_thumb.png | Bin 0 -> 18412 bytes docs/src/_static/iris-logo-title.svg | 23 +- docs/src/_static/theme_override.css | 42 +-- docs/src/_templates/custom_footer.html | 1 + .../custom_sidebar_logo_version.html | 20 ++ docs/src/_templates/footer.html | 5 - docs/src/_templates/layout.html | 40 +-- docs/src/common_links.inc | 3 +- docs/src/conf.py | 91 ++++--- .../contributing_deprecations.rst | 72 ++--- .../contributing_getting_involved.rst | 33 ++- .../contributing_graphics_tests.rst | 70 ++--- .../contributing_pull_request_checklist.rst | 16 +- .../developers_guide/contributing_testing.rst | 4 +- .../documenting/docstrings.rst | 4 +- .../documenting/rest_guide.rst | 4 +- .../documenting/whats_new_contributions.rst | 27 +- .../gitwash/development_workflow.rst | 2 +- docs/src/developers_guide/release.rst | 34 ++- docs/src/further_topics/index.rst | 26 -- docs/src/further_topics/metadata.rst | 32 +-- docs/src/further_topics/ugrid/data_model.rst | 12 +- docs/src/getting_started.rst | 15 ++ docs/src/index.rst | 185 ++++++------- docs/src/installing.rst | 4 +- docs/src/userguide/code_maintenance.rst | 24 +- docs/src/userguide/index.rst | 62 +++-- .../interpolation_and_regridding.rst | 56 ++-- docs/src/userguide/iris_cubes.rst | 246 ++++++++++-------- docs/src/userguide/loading_iris_cubes.rst | 14 +- docs/src/userguide/merge_and_concat.rst | 16 +- docs/src/userguide/plotting_a_cube.rst | 30 +-- docs/src/userguide/real_and_lazy_data.rst | 26 +- docs/src/voted_issues.rst | 2 +- docs/src/whatsnew/3.2.rst | 2 +- docs/src/whatsnew/dev.rst | 137 ---------- docs/src/whatsnew/index.rst | 2 +- docs/src/whatsnew/latest.rst | 146 ++++++++++- .../{dev.rst.template => latest.rst.template} | 0 docs/src/why_iris.rst | 44 ++++ requirements/ci/nox.lock/py38-linux-64.lock | 40 +-- requirements/ci/py38.yml | 2 +- tools/update_lockfiles.py | 3 +- 52 files changed, 1484 insertions(+), 760 deletions(-) create mode 100644 docs/src/_static/icon_api.svg create mode 100644 docs/src/_static/icon_development.svg create mode 100644 docs/src/_static/icon_instructions.svg create mode 100644 docs/src/_static/icon_new_product.svg create mode 100644 docs/src/_static/icon_shuttle.svg create mode 100644 docs/src/_static/icon_support.png create mode 100644 docs/src/_static/icon_thumb.png create mode 100644 docs/src/_templates/custom_footer.html create mode 100644 docs/src/_templates/custom_sidebar_logo_version.html delete mode 100644 docs/src/_templates/footer.html delete mode 100644 docs/src/further_topics/index.rst create mode 100644 docs/src/getting_started.rst delete mode 100644 docs/src/whatsnew/dev.rst mode change 120000 => 100644 docs/src/whatsnew/latest.rst rename docs/src/whatsnew/{dev.rst.template => latest.rst.template} (100%) create mode 100644 docs/src/why_iris.rst diff --git a/.cirrus.yml b/.cirrus.yml index c9c1d71859..534f9f895c 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -30,7 +30,7 @@ env: # Increment the build number to force new conda cache upload. CONDA_CACHE_BUILD: "0" # Increment the build number to force new nox cache upload. - NOX_CACHE_BUILD: "0" + NOX_CACHE_BUILD: "2" # Increment the build number to force new pip cache upload. PIP_CACHE_BUILD: "0" # Pip packages to be upgraded/installed. diff --git a/docs/gallery_code/README.rst b/docs/gallery_code/README.rst index 720fd1e6f6..85bf0552b4 100644 --- a/docs/gallery_code/README.rst +++ b/docs/gallery_code/README.rst @@ -1,3 +1,5 @@ +.. _gallery_index: + Gallery ======= diff --git a/docs/gallery_code/general/README.rst b/docs/gallery_code/general/README.rst index c846755f1e..3a48e7cd8e 100644 --- a/docs/gallery_code/general/README.rst +++ b/docs/gallery_code/general/README.rst @@ -1,2 +1,3 @@ General ------- + diff --git a/docs/src/_static/icon_api.svg b/docs/src/_static/icon_api.svg new file mode 100644 index 0000000000..841b105973 --- /dev/null +++ b/docs/src/_static/icon_api.svg @@ -0,0 +1,144 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/src/_static/icon_development.svg b/docs/src/_static/icon_development.svg new file mode 100644 index 0000000000..dbc342688c --- /dev/null +++ b/docs/src/_static/icon_development.svg @@ -0,0 +1,63 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/docs/src/_static/icon_instructions.svg b/docs/src/_static/icon_instructions.svg new file mode 100644 index 0000000000..62b3fc3620 --- /dev/null +++ b/docs/src/_static/icon_instructions.svg @@ -0,0 +1,162 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/src/_static/icon_new_product.svg b/docs/src/_static/icon_new_product.svg new file mode 100644 index 0000000000..f222e1e066 --- /dev/null +++ b/docs/src/_static/icon_new_product.svg @@ -0,0 +1,182 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/_static/icon_shuttle.svg b/docs/src/_static/icon_shuttle.svg new file mode 100644 index 0000000000..46ba64d2e0 --- /dev/null +++ b/docs/src/_static/icon_shuttle.svg @@ -0,0 +1,71 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/docs/src/_static/icon_support.png b/docs/src/_static/icon_support.png new file mode 100644 index 0000000000000000000000000000000000000000..567cdb1b2fd899d4c62784cca6d18839cc41ed7c GIT binary patch literal 8037 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 literal 0 HcmV?d00001 diff --git a/docs/src/_static/icon_thumb.png b/docs/src/_static/icon_thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..6a14875e22f325d2aaabe6bb28393cf1fca7adae GIT binary patch literal 18412 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 h|G$5REA#WIhi_wGt+whIA=+>{8n + units="cm" + scale-x="1.2" /> @@ -63,8 +64,8 @@ id="layer1" transform="translate(1765.8435,1039.1033)"> Iris diff --git a/docs/src/_static/theme_override.css b/docs/src/_static/theme_override.css index c56b720f69..326c1d4d4a 100644 --- a/docs/src/_static/theme_override.css +++ b/docs/src/_static/theme_override.css @@ -1,33 +1,10 @@ /* import the standard theme css */ @import url("css/theme.css"); -/* now we can add custom any css */ - -/* set the width of the logo */ -.wy-side-nav-search>a img.logo, -.wy-side-nav-search .wy-dropdown>a img.logo { - width: 12rem -} - -/* color of the logo background in the top left corner */ -.wy-side-nav-search { - background-color: lightgray; -} - -/* color of the font for the version in the top left corner */ -.wy-side-nav-search>div.version { - color: black; - font-weight: bold; -} - -/* Ensures tables do now have width scroll bars */ -table.docutils td { - white-space: unset; - word-wrap: break-word; -} +/* now we can add custom css.... */ /* Used for very strong warning */ -#slim-red-box-message { +#slim-red-box-banner { background: #ff0000; box-sizing: border-box; color: #ffffff; @@ -35,8 +12,17 @@ table.docutils td { padding: 0.5em; } -#slim-red-box-message a { +#slim-red-box-banner a { color: #ffffff; - font-weight: normal; - text-decoration:underline; + font-weight: normal; + text-decoration: underline; +} + +/* bullet point list with green ticks */ +ul.squarelist { + /* https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type */ + list-style-type: "\2705"; + margin-left: 0; + text-indent: 1em; + padding-left: 5em; } diff --git a/docs/src/_templates/custom_footer.html b/docs/src/_templates/custom_footer.html new file mode 100644 index 0000000000..f81fcc583e --- /dev/null +++ b/docs/src/_templates/custom_footer.html @@ -0,0 +1 @@ +

Built using Python {{ python_version }}.

diff --git a/docs/src/_templates/custom_sidebar_logo_version.html b/docs/src/_templates/custom_sidebar_logo_version.html new file mode 100644 index 0000000000..48bbe604a0 --- /dev/null +++ b/docs/src/_templates/custom_sidebar_logo_version.html @@ -0,0 +1,20 @@ +{% if on_rtd %} + {% if rtd_version == 'latest' %} + + + + {% elif rtd_version == 'stable' %} + + + + {% else %} + + + + {% endif %} +{%- else %} + {# not on rtd #} + + + +{%- endif %} diff --git a/docs/src/_templates/footer.html b/docs/src/_templates/footer.html deleted file mode 100644 index 1d5fb08b78..0000000000 --- a/docs/src/_templates/footer.html +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "!footer.html" %} -{% block extrafooter %} - Built using Python {{ python_version }}. - {{ super() }} -{% endblock %} diff --git a/docs/src/_templates/layout.html b/docs/src/_templates/layout.html index 96a2e0913e..7377e866b7 100644 --- a/docs/src/_templates/layout.html +++ b/docs/src/_templates/layout.html @@ -1,16 +1,16 @@ -{% extends "!layout.html" %} +{% extends "pydata_sphinx_theme/layout.html" %} -{# This uses blocks. See: +{# This uses blocks. See: https://www.sphinx-doc.org/en/master/templating.html #} -/*---------------------------------------------------------------------------*/ -{%- block document %} - {% if READTHEDOCS and rtd_version == 'latest' %} -
+ {%- block docs_body %} + + {% if on_rtd and rtd_version == 'latest' %} +
You are viewing the latest unreleased documentation - v{{ version }}. You may prefer a + v{{ version }}. You may prefer a stable version.
@@ -19,29 +19,3 @@ {{ super() }} {%- endblock %} - -/*-----------------------------------------------------z----------------------*/ - -{% block menu %} - {{ super() }} - - {# menu_links and menu_links_name are set in conf.py (html_context) #} - - {% if menu_links %} -

- - {% if menu_links_name %} - {{ menu_links_name }} - {% else %} - External links - {% endif %} - -

-
    - {% for text, link in menu_links %} -
  • {{ text }}
  • - {% endfor %} -
- {% endif %} -{% endblock %} - diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index ce7f498d80..e80a597976 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -10,10 +10,9 @@ .. _conda: https://docs.conda.io/en/latest/ .. _contributor: https://github.com/SciTools/scitools.org.uk/blob/master/contributors.json .. _core developers: https://github.com/SciTools/scitools.org.uk/blob/master/contributors.json -.. _discussions: https://github.com/SciTools/iris/discussions .. _generating sss keys for GitHub: https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account .. _GitHub Help Documentation: https://docs.github.com/en/github -.. _Iris GitHub Discussions: https://github.com/SciTools/iris/discussions +.. _GitHub Discussions: https://github.com/SciTools/iris/discussions .. _Iris: https://github.com/SciTools/iris .. _Iris GitHub: https://github.com/SciTools/iris .. _iris-sample-data: https://github.com/SciTools/iris-sample-data diff --git a/docs/src/conf.py b/docs/src/conf.py index 3b7731ab1b..0af6ec072f 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -41,20 +41,23 @@ def autolog(message): # -- Are we running on the readthedocs server, if so do some setup ----------- on_rtd = os.environ.get("READTHEDOCS") == "True" +# This is the rtd reference to the version, such as: latest, stable, v3.0.1 etc +rtd_version = os.environ.get("READTHEDOCS_VERSION") + +# For local testing purposes we can force being on RTD and the version +# on_rtd = True # useful for testing +# rtd_version = "latest" # useful for testing +# rtd_version = "stable" # useful for testing + if on_rtd: autolog("Build running on READTHEDOCS server") # list all the READTHEDOCS environment variables that may be of use - # at some point autolog("Listing all environment variables on the READTHEDOCS server...") for item, value in os.environ.items(): autolog("[READTHEDOCS] {} = {}".format(item, value)) -# This is the rtd reference to the version, such as: latest, stable, v3.0.1 etc -# For local testing purposes this could be explicitly set latest or stable. -rtd_version = os.environ.get("READTHEDOCS_VERSION") - # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, @@ -91,6 +94,7 @@ def autolog(message): else: # major.minor.patch-dev -> major.minor.patch version = ".".join(iris.__version__.split("-")[0].split(".")[:3]) + # The full version, including alpha/beta/rc tags. release = iris.__version__ @@ -171,6 +175,7 @@ def _dotv(version): # -- panels extension --------------------------------------------------------- # See https://sphinx-panels.readthedocs.io/en/latest/ +panels_add_bootstrap_css = False # -- Napoleon extension ------------------------------------------------------- # See https://sphinxcontrib-napoleon.readthedocs.io/en/latest/sphinxcontrib.napoleon.html @@ -246,6 +251,10 @@ def _dotv(version): extlinks = { "issue": ("https://github.com/SciTools/iris/issues/%s", "Issue #"), "pull": ("https://github.com/SciTools/iris/pull/%s", "PR #"), + "discussion": ( + "https://github.com/SciTools/iris/discussions/%s", + "Discussion #", + ), } # -- Doctest ("make doctest")-------------------------------------------------- @@ -259,41 +268,61 @@ def _dotv(version): # html_logo = "_static/iris-logo-title.png" html_favicon = "_static/favicon.ico" -html_theme = "sphinx_rtd_theme" +html_theme = "pydata_sphinx_theme" + +# See https://pydata-sphinx-theme.readthedocs.io/en/latest/user_guide/configuring.html#configure-the-search-bar-position +html_sidebars = { + "**": [ + "custom_sidebar_logo_version", + "search-field", + "sidebar-nav-bs", + "sidebar-ethical-ads", + ] +} +# See https://pydata-sphinx-theme.readthedocs.io/en/latest/user_guide/configuring.html html_theme_options = { - "display_version": True, - "style_external_links": True, - "logo_only": "True", + "footer_items": ["copyright", "sphinx-version", "custom_footer"], + "collapse_navigation": True, + "navigation_depth": 3, + "show_prev_next": True, + "navbar_align": "content", + "github_url": "https://github.com/SciTools/iris", + "twitter_url": "https://twitter.com/scitools_iris", + # icons available: https://fontawesome.com/v5.15/icons?d=gallery&m=free + "icon_links": [ + { + "name": "GitHub Discussions", + "url": "https://github.com/SciTools/iris/discussions", + "icon": "far fa-comments", + }, + { + "name": "PyPI", + "url": "https://pypi.org/project/scitools-iris/", + "icon": "fas fa-box", + }, + { + "name": "Conda", + "url": "https://anaconda.org/conda-forge/iris", + "icon": "fas fa-boxes", + }, + ], + "use_edit_page_button": True, + "show_toc_level": 1, } html_context = { + # pydata_theme + "github_repo": "iris", + "github_user": "scitools", + "github_version": "main", + "doc_path": "docs/src", + # custom + "on_rtd": on_rtd, "rtd_version": rtd_version, "version": version, "copyright_years": copyright_years, "python_version": build_python_version, - # menu_links and menu_links_name are used in _templates/layout.html - # to include some nice icons. See http://fontawesome.io for a list of - # icons (used in the sphinx_rtd_theme) - "menu_links_name": "Support", - "menu_links": [ - ( - ' Source Code', - "https://github.com/SciTools/iris", - ), - ( - ' GitHub Discussions', - "https://github.com/SciTools/iris/discussions", - ), - ( - ' StackOverflow for "How Do I?"', - "https://stackoverflow.com/questions/tagged/python-iris", - ), - ( - ' Legacy Documentation', - "https://scitools.org.uk/iris/docs/v2.4.0/index.html", - ), - ], } # Add any paths that contain custom static files (such as style sheets) here, diff --git a/docs/src/developers_guide/contributing_deprecations.rst b/docs/src/developers_guide/contributing_deprecations.rst index 1ecafdca9f..0b22e2cbd2 100644 --- a/docs/src/developers_guide/contributing_deprecations.rst +++ b/docs/src/developers_guide/contributing_deprecations.rst @@ -25,29 +25,29 @@ deprecation is accompanied by the introduction of a new public API. Under these circumstances the following points apply: - - Using the deprecated API must result in a concise deprecation warning which - is an instance of :class:`iris.IrisDeprecation`. - It is easiest to call - :func:`iris._deprecation.warn_deprecated`, which is a - simple wrapper to :func:`warnings.warn` with the signature - `warn_deprecation(message, **kwargs)`. - - Where possible, your deprecation warning should include advice on - how to avoid using the deprecated API. For example, you might - reference a preferred API, or more detailed documentation elsewhere. - - You must update the docstring for the deprecated API to include a - Sphinx deprecation directive: - - :literal:`.. deprecated:: ` - - where you should replace `` with the major and minor version - of Iris in which this API is first deprecated. For example: `1.8`. - - As with the deprecation warning, you should include advice on how to - avoid using the deprecated API within the content of this directive. - Feel free to include more detail in the updated docstring than in the - deprecation warning. - - You should check the documentation for references to the deprecated - API and update them as appropriate. +- Using the deprecated API must result in a concise deprecation warning which + is an instance of :class:`iris.IrisDeprecation`. + It is easiest to call + :func:`iris._deprecation.warn_deprecated`, which is a + simple wrapper to :func:`warnings.warn` with the signature + `warn_deprecation(message, **kwargs)`. +- Where possible, your deprecation warning should include advice on + how to avoid using the deprecated API. For example, you might + reference a preferred API, or more detailed documentation elsewhere. +- You must update the docstring for the deprecated API to include a + Sphinx deprecation directive: + + :literal:`.. deprecated:: ` + + where you should replace `` with the major and minor version + of Iris in which this API is first deprecated. For example: `1.8`. + + As with the deprecation warning, you should include advice on how to + avoid using the deprecated API within the content of this directive. + Feel free to include more detail in the updated docstring than in the + deprecation warning. +- You should check the documentation for references to the deprecated + API and update them as appropriate. Changing a Default ------------------ @@ -64,14 +64,14 @@ it causes the corresponding public API to use its new default behaviour. The following points apply in addition to those for removing a public API: - - You should add a new boolean attribute to :data:`iris.FUTURE` (by - modifying :class:`iris.Future`) that controls the default behaviour - of the public API that needs updating. The initial state of the new - boolean attribute should be `False`. You should name the new boolean - attribute to indicate that setting it to `True` will select the new - default behaviour. - - You should include a reference to this :data:`iris.FUTURE` flag in your - deprecation warning and corresponding Sphinx deprecation directive. +- You should add a new boolean attribute to :data:`iris.FUTURE` (by + modifying :class:`iris.Future`) that controls the default behaviour + of the public API that needs updating. The initial state of the new + boolean attribute should be `False`. You should name the new boolean + attribute to indicate that setting it to `True` will select the new + default behaviour. +- You should include a reference to this :data:`iris.FUTURE` flag in your + deprecation warning and corresponding Sphinx deprecation directive. Removing a Deprecation @@ -94,11 +94,11 @@ and/or example code should be removed/updated as appropriate. Changing a Default ------------------ - - You should update the initial state of the relevant boolean attribute - of :data:`iris.FUTURE` to `True`. - - You should deprecate setting the relevant boolean attribute of - :class:`iris.Future` in the same way as described in - :ref:`removing-a-public-api`. +- You should update the initial state of the relevant boolean attribute + of :data:`iris.FUTURE` to `True`. +- You should deprecate setting the relevant boolean attribute of + :class:`iris.Future` in the same way as described in + :ref:`removing-a-public-api`. .. rubric:: Footnotes diff --git a/docs/src/developers_guide/contributing_getting_involved.rst b/docs/src/developers_guide/contributing_getting_involved.rst index f7bd4733a3..f4e677cea2 100644 --- a/docs/src/developers_guide/contributing_getting_involved.rst +++ b/docs/src/developers_guide/contributing_getting_involved.rst @@ -1,8 +1,9 @@ .. include:: ../common_links.inc .. _development_where_to_start: +.. _developers_guide: -Getting Involved +Developers Guide ---------------- Iris_ is an Open Source project hosted on Github and as such anyone with a @@ -17,7 +18,7 @@ The `Iris GitHub`_ project has been configured to use templates for each of the above issue types when creating a `new issue`_ to ensure the appropriate information is provided. -Alternatively, **join the conversation** in `Iris GitHub Discussions`_, when +Alternatively, **join the conversation** in Iris `GitHub Discussions`_, when you would like the opinions of the Iris community. A `pull request`_ may also be created by anyone who has become a @@ -25,7 +26,7 @@ A `pull request`_ may also be created by anyone who has become a ``main`` branch are only given to **core developers** of Iris_, this is to ensure a measure of control. -To get started we suggest reading recent `issues`_, `discussions`_ and +To get started we suggest reading recent `issues`_, `GitHub Discussions`_ and `pull requests`_ for Iris. If you are new to using GitHub we recommend reading the @@ -36,5 +37,29 @@ If you are new to using GitHub we recommend reading the `Governance `_ section of the `SciTools`_ ogranization web site. - .. _GitHub getting started: https://docs.github.com/en/github/getting-started-with-github + + +.. toctree:: + :maxdepth: 1 + :caption: Developers Guide + :name: development_index + :hidden: + + gitwash/index + contributing_documentation + contributing_codebase_index + contributing_changes + release + + +.. toctree:: + :maxdepth: 1 + :caption: Reference + :hidden: + + ../generated/api/iris + ../whatsnew/index + ../techpapers/index + ../copyright + ../voted_issues diff --git a/docs/src/developers_guide/contributing_graphics_tests.rst b/docs/src/developers_guide/contributing_graphics_tests.rst index 1268aa2686..07e2301141 100644 --- a/docs/src/developers_guide/contributing_graphics_tests.rst +++ b/docs/src/developers_guide/contributing_graphics_tests.rst @@ -44,24 +44,24 @@ reference images in the Iris repository itself. This consists of: - * The ``iris.tests.IrisTest_nometa.check_graphic`` function uses a perceptual - **image hash** of the outputs (see https://github.com/JohannesBuchner/imagehash) - as the basis for checking test results. +* The ``iris.tests.IrisTest_nometa.check_graphic`` function uses a perceptual + **image hash** of the outputs (see https://github.com/JohannesBuchner/imagehash) + as the basis for checking test results. - * The hashes of known **acceptable** results for each test are stored in a - lookup dictionary, saved to the repo file - ``lib/iris/tests/results/imagerepo.json`` - (`link `_) . +* The hashes of known **acceptable** results for each test are stored in a + lookup dictionary, saved to the repo file + ``lib/iris/tests/results/imagerepo.json`` + (`link `_) . - * An actual reference image for each hash value is stored in a *separate* - public repository https://github.com/SciTools/test-iris-imagehash. +* An actual reference image for each hash value is stored in a *separate* + public repository https://github.com/SciTools/test-iris-imagehash. - * The reference images allow human-eye assessment of whether a new output is - judged to be close enough to the older ones, or not. +* The reference images allow human-eye assessment of whether a new output is + judged to be close enough to the older ones, or not. - * The utility script ``iris/tests/idiff.py`` automates checking, enabling the - developer to easily compare proposed new **acceptable** result images - against the existing accepted reference images, for each failing test. +* The utility script ``iris/tests/idiff.py`` automates checking, enabling the + developer to easily compare proposed new **acceptable** result images + against the existing accepted reference images, for each failing test. The acceptable images for each test can be viewed online. The :ref:`testing.imagehash_index` lists all the graphical tests in the test suite and shows the known acceptable result images for comparison. @@ -92,29 +92,29 @@ you should follow: If the change is **accepted**: - * the imagehash value of the new result image is added into the relevant - set of 'valid result hashes' in the image result database file, - ``tests/results/imagerepo.json`` + * the imagehash value of the new result image is added into the relevant + set of 'valid result hashes' in the image result database file, + ``tests/results/imagerepo.json`` - * the relevant output file in ``tests/result_image_comparison`` is - renamed according to the image hash value, as ``.png``. - A copy of this new PNG file must then be added into the reference image - repository at https://github.com/SciTools/test-iris-imagehash - (See below). + * the relevant output file in ``tests/result_image_comparison`` is + renamed according to the image hash value, as ``.png``. + A copy of this new PNG file must then be added into the reference image + repository at https://github.com/SciTools/test-iris-imagehash + (See below). If a change is **skipped**: - * no further changes are made in the repo. + * no further changes are made in the repo. - * when you run ``iris/tests/idiff.py`` again, the skipped choice will be - presented again. + * when you run ``iris/tests/idiff.py`` again, the skipped choice will be + presented again. If a change is **rejected**: - * the output image is deleted from ``result_image_comparison``. + * the output image is deleted from ``result_image_comparison``. - * when you run ``iris/tests/idiff.py`` again, the skipped choice will not - appear, unless the relevant failing test is re-run. + * when you run ``iris/tests/idiff.py`` again, the skipped choice will not + appear, unless the relevant failing test is re-run. #. **Now re-run the tests**. The **new** result should now be recognised and the relevant test should pass. However, some tests can perform *multiple* @@ -132,16 +132,16 @@ To add your changes to Iris, you need to make two pull requests (PR). #. The first PR is made in the ``test-iris-imagehash`` repository, at https://github.com/SciTools/test-iris-imagehash. - * First, add all the newly-generated referenced PNG files into the - ``images/v4`` directory. In your Iris repo, these files are to be found - in the temporary results folder ``iris/tests/result_image_comparison``. + * First, add all the newly-generated referenced PNG files into the + ``images/v4`` directory. In your Iris repo, these files are to be found + in the temporary results folder ``iris/tests/result_image_comparison``. - * Then, to update the file which lists available images, - ``v4_files_listing.txt``, run from the project root directory:: + * Then, to update the file which lists available images, + ``v4_files_listing.txt``, run from the project root directory:: - python recreate_v4_files_listing.py + python recreate_v4_files_listing.py - * Create a PR proposing these changes, in the usual way. + * Create a PR proposing these changes, in the usual way. #. The second PR is created in the Iris_ repository, and should only include the change to the image results database, diff --git a/docs/src/developers_guide/contributing_pull_request_checklist.rst b/docs/src/developers_guide/contributing_pull_request_checklist.rst index 5afb461d68..524b5d2c17 100644 --- a/docs/src/developers_guide/contributing_pull_request_checklist.rst +++ b/docs/src/developers_guide/contributing_pull_request_checklist.rst @@ -16,8 +16,8 @@ is merged. Before submitting a pull request please consider this list. #. **Provide a helpful description** of the Pull Request. This should include: - * The aim of the change / the problem addressed / a link to the issue. - * How the change has been delivered. + * The aim of the change / the problem addressed / a link to the issue. + * How the change has been delivered. #. **Include a "What's New" entry**, if appropriate. See :ref:`whats_new_contributions`. @@ -51,12 +51,12 @@ is merged. Before submitting a pull request please consider this list. #. **Check for updates needed for supporting projects for test or example data**. For example: - * `iris-test-data`_ is a github project containing all the data to support - the tests. - * `iris-sample-data`_ is a github project containing all the data to support - the gallery and examples. - * `test-iris-imagehash`_ is a github project containing reference plot - images to support Iris :ref:`testing.graphics`. + * `iris-test-data`_ is a github project containing all the data to support + the tests. + * `iris-sample-data`_ is a github project containing all the data to support + the gallery and examples. + * `test-iris-imagehash`_ is a github project containing reference plot + images to support Iris :ref:`testing.graphics`. If new files are required by tests or code examples, they must be added to the appropriate supporting project via a suitable pull-request. This pull diff --git a/docs/src/developers_guide/contributing_testing.rst b/docs/src/developers_guide/contributing_testing.rst index d0c96834a9..a65bcebd55 100644 --- a/docs/src/developers_guide/contributing_testing.rst +++ b/docs/src/developers_guide/contributing_testing.rst @@ -8,8 +8,8 @@ Test Categories There are two main categories of tests within Iris: - - :ref:`testing.unit_test` - - :ref:`testing.integration` +- :ref:`testing.unit_test` +- :ref:`testing.integration` Ideally, all code changes should be accompanied by one or more unit tests, and by zero or more integration tests. diff --git a/docs/src/developers_guide/documenting/docstrings.rst b/docs/src/developers_guide/documenting/docstrings.rst index 8a06024ee2..ef0679aac0 100644 --- a/docs/src/developers_guide/documenting/docstrings.rst +++ b/docs/src/developers_guide/documenting/docstrings.rst @@ -10,8 +10,8 @@ the code and may be read directly in the source or via the :ref:`Iris`. This document has been influenced by the following PEP's, - * Attribute Docstrings :pep:`224` - * Docstring Conventions :pep:`257` +* Attribute Docstrings :pep:`224` +* Docstring Conventions :pep:`257` For consistency always use: diff --git a/docs/src/developers_guide/documenting/rest_guide.rst b/docs/src/developers_guide/documenting/rest_guide.rst index 4845132b15..c4330b1e63 100644 --- a/docs/src/developers_guide/documenting/rest_guide.rst +++ b/docs/src/developers_guide/documenting/rest_guide.rst @@ -14,8 +14,8 @@ reST is a lightweight markup language intended to be highly readable in source format. This guide will cover some of the more frequently used advanced reST markup syntaxes, for the basics of reST the following links may be useful: - * https://www.sphinx-doc.org/en/master/usage/restructuredtext/ - * http://packages.python.org/an_example_pypi_project/sphinx.html +* https://www.sphinx-doc.org/en/master/usage/restructuredtext/ +* http://packages.python.org/an_example_pypi_project/sphinx.html Reference documentation for reST can be found at http://docutils.sourceforge.net/rst.html. diff --git a/docs/src/developers_guide/documenting/whats_new_contributions.rst b/docs/src/developers_guide/documenting/whats_new_contributions.rst index 576fc5f6a6..c9dbe04b71 100644 --- a/docs/src/developers_guide/documenting/whats_new_contributions.rst +++ b/docs/src/developers_guide/documenting/whats_new_contributions.rst @@ -4,21 +4,16 @@ Contributing a "What's New" Entry ================================= -Iris uses a file named ``dev.rst`` to keep a draft of upcoming development changes -that will form the next stable release. Contributions to the :ref:`iris_whatsnew` -document are written by the developer most familiar with the change made. -The contribution should be included as part of the Iris Pull Request that -introduces the change. +Iris uses a file named ``latest.rst`` to keep a draft of upcoming development +changes that will form the next stable release. Contributions to the +:ref:`iris_whatsnew` document are written by the developer most familiar +with the change made. The contribution should be included as part of +the Iris Pull Request that introduces the change. -The ``dev.rst`` and the past release notes are kept in the +The ``latest.rst`` and the past release notes are kept in the ``docs/src/whatsnew/`` directory. If you are writing the first contribution after -an Iris release: **create the new** ``dev.rst`` by copying the content from -``dev.rst.template`` in the same directory. - -.. note:: - - Ensure that the symbolic link ``latest.rst`` references the ``dev.rst`` file - within the ``docs/src/whatsnew`` directory. +an Iris release: **create the new** ``latest.rst`` by copying the content from +``latest.rst.template`` in the same directory. Since the `Contribution categories`_ include Internal changes, **all** Iris Pull Requests should be accompanied by a "What's New" contribution. @@ -27,7 +22,7 @@ Pull Requests should be accompanied by a "What's New" contribution. Git Conflicts ============= -If changes to ``dev.rst`` are being suggested in several simultaneous +If changes to ``latest.rst`` are being suggested in several simultaneous Iris Pull Requests, Git will likely encounter merge conflicts. If this situation is thought likely (large PR, high repo activity etc.): @@ -38,7 +33,7 @@ situation is thought likely (large PR, high repo activity etc.): a **new pull request** be created specifically for the "What's New" entry, which references the main pull request and titled (e.g. for PR#9999): - What's New for #9999 + What's New for #9999 * PR author: create the "What's New" pull request @@ -48,7 +43,7 @@ situation is thought likely (large PR, high repo activity etc.): * PR reviewer: review the "What's New" PR, merge once acceptable -These measures should mean the suggested ``dev.rst`` changes are outstanding +These measures should mean the suggested ``latest.rst`` changes are outstanding for the minimum time, minimising conflicts and minimising the need to rebase or merge from trunk. diff --git a/docs/src/developers_guide/gitwash/development_workflow.rst b/docs/src/developers_guide/gitwash/development_workflow.rst index 0536ebfb62..6e74e8546d 100644 --- a/docs/src/developers_guide/gitwash/development_workflow.rst +++ b/docs/src/developers_guide/gitwash/development_workflow.rst @@ -25,7 +25,7 @@ In what follows we'll refer to the upstream iris ``main`` branch, as * If you can possibly avoid it, avoid merging trunk or any other branches into your feature branch while you are working. * If you do find yourself merging from trunk, consider :ref:`rebase-on-trunk` -* Ask on the `Iris GitHub Discussions`_ if you get stuck. +* Ask on the Iris `GitHub Discussions`_ if you get stuck. * Ask for code review! This way of working helps to keep work well organized, with readable history. diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index f4d44781fc..a918253ef7 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -19,7 +19,8 @@ A Release Manager will be nominated for each release of Iris. This role involves * deciding which features and bug fixes should be included in the release * managing the project board for the release -* using a `GitHub Releases Discussion Forum`_ for documenting intent and capturing any +* using :discussion:`GitHub Discussion releases category ` + for documenting intent and capturing any discussion about the release The Release Manager will make the release, ensuring that all the steps outlined @@ -183,7 +184,7 @@ back onto the ``SciTools/iris`` ``main`` branch. To achieve this, first cut a local branch from the latest ``main`` branch, and `git merge` the :literal:`.x` release branch into it. Ensure that the -``iris.__version__``, ``docs/src/whatsnew/index.rst``, ``docs/src/whatsnew/dev.rst``, +``iris.__version__``, ``docs/src/whatsnew/index.rst``, and ``docs/src/whatsnew/latest.rst`` are correct, before committing these changes and then proposing a pull-request on the ``main`` branch of ``SciTools/iris``. @@ -218,24 +219,22 @@ Release Steps #. Update the ``iris.__init__.py`` version string e.g., to ``1.9.0`` #. Update the ``whatsnew`` for the release: - * Use ``git`` to rename ``docs/src/whatsnew/dev.rst`` to the release - version file ``v1.9.rst`` - * Update the symbolic link ``latest.rst`` to reference the latest - whatsnew ``v1.9.rst`` - * Use ``git`` to delete the ``docs/src/whatsnew/dev.rst.template`` file - * In ``v1.9.rst`` remove the ``[unreleased]`` caption from the page title. - Note that, the Iris version and release date are updated automatically - when the documentation is built - * Review the file for correctness - * Work with the development team to populate the ``Release Highlights`` - dropdown at the top of the file, which provides extra detail on notable - changes - * Use ``git`` to add and commit all changes, including removal of - ``dev.rst.template`` and update to the ``latest.rst`` symbolic link. + * Use ``git`` to rename ``docs/src/whatsnew/latest.rst`` to the release + version file ``v1.9.rst`` + * Use ``git`` to delete the ``docs/src/whatsnew/latest.rst.template`` file + * In ``v1.9.rst`` remove the ``[unreleased]`` caption from the page title. + Note that, the Iris version and release date are updated automatically + when the documentation is built + * Review the file for correctness + * Work with the development team to populate the ``Release Highlights`` + dropdown at the top of the file, which provides extra detail on notable + changes + * Use ``git`` to add and commit all changes, including removal of + ``latest.rst.template``. #. Update the ``whatsnew`` index ``docs/src/whatsnew/index.rst`` - * Remove the reference to ``dev.rst`` + * Remove the reference to ``latest.rst`` * Add a reference to ``v1.9.rst`` to the top of the list #. Check your changes by building the documentation and reviewing @@ -261,7 +260,6 @@ Post Release Steps .. _SciTools/iris: https://github.com/SciTools/iris .. _tag on the SciTools/Iris: https://github.com/SciTools/iris/releases -.. _GitHub Releases Discussion Forum: https://github.com/SciTools/iris/discussions/categories/releases .. _conda-forge Anaconda channel: https://anaconda.org/conda-forge/iris .. _conda-forge iris-feedstock: https://github.com/conda-forge/iris-feedstock .. _CFEP-05: https://github.com/conda-forge/cfep/blob/master/cfep-05.md diff --git a/docs/src/further_topics/index.rst b/docs/src/further_topics/index.rst deleted file mode 100644 index 81bff2f764..0000000000 --- a/docs/src/further_topics/index.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. _further topics: - -Introduction -============ - -Some specific areas of Iris may require further explanation or a deep dive -into additional detail above and beyond that offered by the -:ref:`User Guide `. - -This section provides a collection of additional material on focused topics -that may be of interest to the more advanced or curious user. - -.. hint:: - - If you wish further documentation on any specific topics or areas of Iris - that are missing, then please let us know by raising a :issue:`GitHub Documentation Issue` - on `SciTools/Iris`_. - - -* :doc:`metadata` -* :doc:`lenient_metadata` -* :doc:`lenient_maths` -* :ref:`ugrid` - - -.. _SciTools/iris: https://github.com/SciTools/iris diff --git a/docs/src/further_topics/metadata.rst b/docs/src/further_topics/metadata.rst index 1b81f7055c..de1afb15af 100644 --- a/docs/src/further_topics/metadata.rst +++ b/docs/src/further_topics/metadata.rst @@ -1,3 +1,4 @@ +.. _further topics: .. _metadata: Metadata @@ -63,25 +64,26 @@ For example, the collective metadata used to define an ``var_name``, ``units``, and ``attributes`` members. Note that, these are the actual `data attribute`_ names of the metadata members on the Iris class. + .. _metadata members table: -.. table:: - Iris classes that model `CF Conventions`_ metadata +.. table:: Iris classes that model `CF Conventions`_ metadata :widths: auto :align: center - =================== ======================================= ============================== ========================================== ================================= ======================== ============================== =================== - Metadata Members :class:`~iris.coords.AncillaryVariable` :class:`~iris.coords.AuxCoord` :class:`~iris.aux_factory.AuxCoordFactory` :class:`~iris.coords.CellMeasure` :class:`~iris.cube.Cube` :class:`~iris.coords.DimCoord` Metadata Members - =================== ======================================= ============================== ========================================== ================================= ======================== ============================== =================== - ``standard_name`` ✔ ✔ ✔ ✔ ✔ ✔ ``standard_name`` - ``long_name`` ✔ ✔ ✔ ✔ ✔ ✔ ``long_name`` - ``var_name`` ✔ ✔ ✔ ✔ ✔ ✔ ``var_name`` - ``units`` ✔ ✔ ✔ ✔ ✔ ✔ ``units`` - ``attributes`` ✔ ✔ ✔ ✔ ✔ ✔ ``attributes`` - ``coord_system`` ✔ ✔ ✔ ``coord_system`` - ``climatological`` ✔ ✔ ✔ ``climatological`` - ``measure`` ✔ ``measure`` - ``cell_methods`` ✔ ``cell_methods`` - ``circular`` ✔ ``circular`` - =================== ======================================= ============================== ========================================== ================================= ======================== ============================== =================== + =================== ======================================= ============================== ========================================== ================================= ======================== ============================== + Metadata Members :class:`~iris.coords.AncillaryVariable` :class:`~iris.coords.AuxCoord` :class:`~iris.aux_factory.AuxCoordFactory` :class:`~iris.coords.CellMeasure` :class:`~iris.cube.Cube` :class:`~iris.coords.DimCoord` + =================== ======================================= ============================== ========================================== ================================= ======================== ============================== + ``standard_name`` ✔ ✔ ✔ ✔ ✔ ✔ + ``long_name`` ✔ ✔ ✔ ✔ ✔ ✔ + ``var_name`` ✔ ✔ ✔ ✔ ✔ ✔ + ``units`` ✔ ✔ ✔ ✔ ✔ ✔ + ``attributes`` ✔ ✔ ✔ ✔ ✔ ✔ + ``coord_system`` ✔ ✔ ✔ + ``climatological`` ✔ ✔ ✔ + ``measure`` ✔ + ``cell_methods`` ✔ + ``circular`` ✔ + =================== ======================================= ============================== ========================================== ================================= ======================== ============================== .. note:: diff --git a/docs/src/further_topics/ugrid/data_model.rst b/docs/src/further_topics/ugrid/data_model.rst index 4a2f64f627..55e4f79a96 100644 --- a/docs/src/further_topics/ugrid/data_model.rst +++ b/docs/src/further_topics/ugrid/data_model.rst @@ -52,7 +52,7 @@ example. .. _data_structured_grid: .. figure:: images/data_structured_grid.svg :alt: Diagram of how data is represented on a structured grid - :align: right + :align: left :width: 1280 Data on a structured grid. @@ -131,7 +131,7 @@ example of what is described above. .. _data_ugrid_mesh: .. figure:: images/data_ugrid_mesh.svg :alt: Diagram of how data is represented on an unstructured mesh - :align: right + :align: left :width: 1280 Data on an unstructured mesh @@ -157,7 +157,7 @@ elements. See :numref:`ugrid_element_centres` for a visualised example. .. _ugrid_element_centres: .. figure:: images/ugrid_element_centres.svg :alt: Diagram demonstrating mesh face-centred data. - :align: right + :align: left :width: 1280 Data can be assigned to mesh edge/face/volume 'centres' @@ -180,7 +180,7 @@ Every node is completely independent - every one can have unique X andY (and Z) .. _ugrid_node_independence: .. figure:: images/ugrid_node_independence.svg :alt: Diagram demonstrating the independence of each mesh node - :align: right + :align: left :width: 300 Every mesh node is completely independent @@ -199,7 +199,7 @@ array. See :numref:`ugrid_variable_faces`. .. _ugrid_variable_faces: .. figure:: images/ugrid_variable_faces.svg :alt: Diagram demonstrating mesh faces with variable node counts - :align: right + :align: left :width: 300 Mesh faces can have different node counts (using masking) @@ -216,7 +216,7 @@ areas (faces). See :numref:`ugrid_edge_data`. .. _ugrid_edge_data: .. figure:: images/ugrid_edge_data.svg :alt: Diagram demonstrating data assigned to mesh edges - :align: right + :align: left :width: 300 Data can be assigned to mesh edges diff --git a/docs/src/getting_started.rst b/docs/src/getting_started.rst new file mode 100644 index 0000000000..24299a4060 --- /dev/null +++ b/docs/src/getting_started.rst @@ -0,0 +1,15 @@ +.. _getting_started_index: + +Getting Started +=============== + +To get started with Iris we recommend reading :ref:`why_iris` was created and to +explore the examples in the :ref:`gallery_index` after :ref:`installing_iris` +Iris. + +.. toctree:: + :maxdepth: 1 + + why_iris + installing + generated/gallery/index \ No newline at end of file diff --git a/docs/src/index.rst b/docs/src/index.rst index d247b93411..fb0e93b1ae 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -1,7 +1,9 @@ +.. include:: common_links.inc .. _iris_docs: -Iris |version| -======================== + +Iris +==== **A powerful, format-agnostic, community-driven Python package for analysing and visualising Earth science data.** @@ -11,158 +13,137 @@ giving you a powerful, format-agnostic interface for working with your data. It excels when working with multi-dimensional Earth Science data, where tabular representations become unwieldy and inefficient. -`CF Standard names `_, -`units `_, and coordinate metadata -are built into Iris, giving you a rich and expressive interface for maintaining -an accurate representation of your data. Its treatment of data and -associated metadata as first-class objects includes: - -* visualisation interface based on `matplotlib `_ and - `cartopy `_, -* unit conversion, -* subsetting and extraction, -* merge and concatenate, -* aggregations and reductions (including min, max, mean and weighted averages), -* interpolation and regridding (including nearest-neighbor, linear and - area-weighted), and -* operator overloads (``+``, ``-``, ``*``, ``/``, etc.). - -A number of file formats are recognised by Iris, including CF-compliant NetCDF, -GRIB, and PP, and it has a plugin architecture to allow other formats to be -added seamlessly. - -Building upon `NumPy `_ and -`dask `_, Iris scales from efficient -single-machine workflows right through to multi-core clusters and HPC. -Interoperability with packages from the wider scientific Python ecosystem comes -from Iris' use of standard NumPy/dask arrays as its underlying data storage. - -Iris is part of SciTools, for more information see https://scitools.org.uk/. -For **Iris 2.4** and earlier documentation please see the -:link-badge:`https://scitools.org.uk/iris/docs/v2.4.0/,"legacy documentation",cls=badge-info text-white`. - +For more information see :ref:`why_iris`. .. panels:: :container: container-lg pb-3 - :column: col-lg-4 col-md-4 col-sm-6 col-xs-12 p-2 + :column: col-lg-4 col-md-4 col-sm-6 col-xs-12 p-2 text-center + :img-top-cls: w-50 m-auto px-1 py-2 - Install Iris as a user or developer. - +++ - .. link-button:: installing_iris - :type: ref - :text: Installing Iris - :classes: btn-outline-primary btn-block --- - Example code to create a variety of plots. + :img-top: _static/icon_shuttle.svg + + Information on Iris, how to install and a gallery of examples that + create plots. +++ - .. link-button:: sphx_glr_generated_gallery + .. link-button:: getting_started :type: ref - :text: Gallery - :classes: btn-outline-primary btn-block + :text: Getting Started + :classes: btn-outline-info btn-block + + --- - Find out what has recently changed in Iris. + :img-top: _static/icon_instructions.svg + + Learn how to use Iris, including loading, navigating, saving, + plotting and more. +++ - .. link-button:: iris_whatsnew + .. link-button:: user_guide_index :type: ref - :text: What's New - :classes: btn-outline-primary btn-block + :text: User Guide + :classes: btn-outline-info btn-block + --- - Learn how to use Iris. + :img-top: _static/icon_development.svg + + As a developer you can contribute to Iris. +++ - .. link-button:: user_guide_index + .. link-button:: development_where_to_start :type: ref - :text: User Guide - :classes: btn-outline-primary btn-block + :text: Developers Guide + :classes: btn-outline-info btn-block + --- + :img-top: _static/icon_api.svg + Browse full Iris functionality by module. +++ .. link-button:: Iris :type: ref :text: Iris API - :classes: btn-outline-primary btn-block + :classes: btn-outline-info btn-block + --- - As a developer you can contribute to Iris. + :img-top: _static/icon_new_product.svg + + Find out what has recently changed in Iris. +++ - .. link-button:: development_where_to_start + .. link-button:: iris_whatsnew :type: ref - :text: Getting Involved - :classes: btn-outline-primary btn-block + :text: What's New + :classes: btn-outline-info btn-block + --- + :img-top: _static/icon_thumb.png -.. toctree:: - :maxdepth: 1 - :caption: Getting Started - :hidden: + Raise the profile of issues by voting on them. + +++ + .. link-button:: voted_issues_top + :type: ref + :text: Voted Issues + :classes: btn-outline-info btn-block + + +Icons made by `FreePik `_ from +`Flaticon `_ + + +Support +~~~~~~~ + +We, the Iris developers have adopted `GitHub Discussions`_ to capture any +discussions or support questions related to Iris. + +See also `StackOverflow for "How Do I? `_ +that may be useful but we do not actively monitor this. + +The legacy support resources: - installing - generated/gallery/index +* `Users Google Group `_ +* `Developers Google Group `_ +* `Legacy Documentation`_ (Iris 2.4 or earlier) .. toctree:: + :caption: Getting Started :maxdepth: 1 - :caption: What's New in Iris :hidden: - whatsnew/latest - Archive + getting_started .. toctree:: - :maxdepth: 1 :caption: User Guide + :maxdepth: 1 :name: userguide_index :hidden: userguide/index - userguide/iris_cubes - userguide/loading_iris_cubes - userguide/saving_iris_cubes - userguide/navigating_a_cube - userguide/subsetting_a_cube - userguide/real_and_lazy_data - userguide/plotting_a_cube - userguide/interpolation_and_regridding - userguide/merge_and_concat - userguide/cube_statistics - userguide/cube_maths - userguide/citation - userguide/code_maintenance - - -.. _developers_guide: + .. toctree:: + :caption: Developers Guide :maxdepth: 1 - :caption: Further Topics + :name: developers_index :hidden: - further_topics/index - further_topics/metadata - further_topics/lenient_metadata - further_topics/lenient_maths - further_topics/ugrid/index + developers_guide/contributing_getting_involved .. toctree:: - :maxdepth: 2 - :caption: Developers Guide - :name: development_index + :caption: Iris API + :maxdepth: 1 :hidden: - developers_guide/contributing_getting_involved - developers_guide/gitwash/index - developers_guide/contributing_documentation - developers_guide/contributing_codebase_index - developers_guide/contributing_changes - developers_guide/release + generated/api/iris .. toctree:: + :caption: Developers Guide :maxdepth: 1 - :caption: Reference + :name: whats_new_index :hidden: - generated/api/iris - techpapers/index - copyright - voted_issues + whatsnew/index + +.. todolist:: \ No newline at end of file diff --git a/docs/src/installing.rst b/docs/src/installing.rst index 37a8942ab3..33b15610fa 100644 --- a/docs/src/installing.rst +++ b/docs/src/installing.rst @@ -1,7 +1,7 @@ .. _installing_iris: -Installing Iris -=============== +Installing +========== Iris is available using conda for the following platforms: diff --git a/docs/src/userguide/code_maintenance.rst b/docs/src/userguide/code_maintenance.rst index b2b498bc80..c01c1975a7 100644 --- a/docs/src/userguide/code_maintenance.rst +++ b/docs/src/userguide/code_maintenance.rst @@ -12,17 +12,17 @@ In practice, as Iris develops, most users will want to periodically upgrade their installed version to access new features or at least bug fixes. This is obvious if you are still developing other code that uses Iris, or using -code from other sources. +code from other sources. However, even if you have only legacy code that remains untouched, some code maintenance effort is probably still necessary: - * On the one hand, *in principle*, working code will go on working, as long - as you don't change anything else. +* On the one hand, *in principle*, working code will go on working, as long + as you don't change anything else. - * However, such "version stasis" can easily become a growing burden, if you - are simply waiting until an update becomes unavoidable, often that will - eventually occur when you need to update some other software component, - for some completely unconnected reason. +* However, such "version stasis" can easily become a growing burden, if you + are simply waiting until an update becomes unavoidable, often that will + eventually occur when you need to update some other software component, + for some completely unconnected reason. Principles of Change Management @@ -35,13 +35,13 @@ In Iris, however, we aim to reduce code maintenance problems to an absolute minimum by following defined change management rules. These ensure that, *within a major release number* : - * you can be confident that your code will still work with subsequent minor - releases +* you can be confident that your code will still work with subsequent minor + releases - * you will be aware of future incompatibility problems in advance +* you will be aware of future incompatibility problems in advance - * you can defer making code compatibility changes for some time, until it - suits you +* you can defer making code compatibility changes for some time, until it + suits you The above applies to minor version upgrades : e.g. code that works with version "1.4.2" should still work with a subsequent minor release such as "1.5.0" or diff --git a/docs/src/userguide/index.rst b/docs/src/userguide/index.rst index 2a3b32fe11..08923e7662 100644 --- a/docs/src/userguide/index.rst +++ b/docs/src/userguide/index.rst @@ -1,31 +1,47 @@ .. _user_guide_index: .. _user_guide_introduction: -Introduction -============ +User Guide +========== -If you are reading this user guide for the first time it is strongly recommended that you read the user guide -fully before experimenting with your own data files. +If you are reading this user guide for the first time it is strongly +recommended that you read the user guide fully before experimenting with your +own data files. - -Much of the content has supplementary links to the reference documentation; you will not need to follow these -links in order to understand the guide but they may serve as a useful reference for future exploration. +Much of the content has supplementary links to the reference documentation; +you will not need to follow these links in order to understand the guide but +they may serve as a useful reference for future exploration. .. only:: html - Since later pages depend on earlier ones, try reading this user guide sequentially using the ``next`` and ``previous`` links. - - -* :doc:`iris_cubes` -* :doc:`loading_iris_cubes` -* :doc:`saving_iris_cubes` -* :doc:`navigating_a_cube` -* :doc:`subsetting_a_cube` -* :doc:`real_and_lazy_data` -* :doc:`plotting_a_cube` -* :doc:`interpolation_and_regridding` -* :doc:`merge_and_concat` -* :doc:`cube_statistics` -* :doc:`cube_maths` -* :doc:`citation` -* :doc:`code_maintenance` + Since later pages depend on earlier ones, try reading this user guide + sequentially using the ``next`` and ``previous`` links at the bottom + of each page. + + +.. toctree:: + :maxdepth: 2 + + iris_cubes + loading_iris_cubes + saving_iris_cubes + navigating_a_cube + subsetting_a_cube + real_and_lazy_data + plotting_a_cube + interpolation_and_regridding + merge_and_concat + cube_statistics + cube_maths + citation + code_maintenance + + +.. toctree:: + :maxdepth: 2 + :caption: Further Topics + + ../further_topics/metadata + ../further_topics/lenient_metadata + ../further_topics/lenient_maths + ../further_topics/ugrid/index diff --git a/docs/src/userguide/interpolation_and_regridding.rst b/docs/src/userguide/interpolation_and_regridding.rst index f590485606..deae4427ed 100644 --- a/docs/src/userguide/interpolation_and_regridding.rst +++ b/docs/src/userguide/interpolation_and_regridding.rst @@ -19,14 +19,14 @@ In Iris we refer to the available types of interpolation and regridding as `schemes`. The following are the interpolation schemes that are currently available in Iris: - * linear interpolation (:class:`iris.analysis.Linear`), and - * nearest-neighbour interpolation (:class:`iris.analysis.Nearest`). +* linear interpolation (:class:`iris.analysis.Linear`), and +* nearest-neighbour interpolation (:class:`iris.analysis.Nearest`). The following are the regridding schemes that are currently available in Iris: - * linear regridding (:class:`iris.analysis.Linear`), - * nearest-neighbour regridding (:class:`iris.analysis.Nearest`), and - * area-weighted regridding (:class:`iris.analysis.AreaWeighted`, first-order conservative). +* linear regridding (:class:`iris.analysis.Linear`), +* nearest-neighbour regridding (:class:`iris.analysis.Nearest`), and +* area-weighted regridding (:class:`iris.analysis.AreaWeighted`, first-order conservative). The linear, nearest-neighbor, and area-weighted regridding schemes support lazy regridding, i.e. if the source cube has lazy data, the resulting cube @@ -42,8 +42,8 @@ Interpolation Interpolating a cube is achieved with the :meth:`~iris.cube.Cube.interpolate` method. This method expects two arguments: - #. the sample points to interpolate, and - #. the interpolation scheme to use. +#. the sample points to interpolate, and +#. the interpolation scheme to use. The result is a new cube, interpolated at the sample points. @@ -51,9 +51,9 @@ Sample points must be defined as an iterable of ``(coord, value(s))`` pairs. The `coord` argument can be either a coordinate name or coordinate instance. The specified coordinate must exist on the cube being interpolated! For example: - * coordinate names and scalar sample points: ``[('latitude', 51.48), ('longitude', 0)]``, - * a coordinate instance and a scalar sample point: ``[(cube.coord('latitude'), 51.48)]``, and - * a coordinate name and a NumPy array of sample points: ``[('longitude', np.linspace(-11, 2, 14))]`` +* coordinate names and scalar sample points: ``[('latitude', 51.48), ('longitude', 0)]``, +* a coordinate instance and a scalar sample point: ``[(cube.coord('latitude'), 51.48)]``, and +* a coordinate name and a NumPy array of sample points: ``[('longitude', np.linspace(-11, 2, 14))]`` are all examples of valid sample points. @@ -175,11 +175,11 @@ The extrapolation mode is controlled by the ``extrapolation_mode`` keyword. For the available interpolation schemes available in Iris, the ``extrapolation_mode`` keyword must be one of: - * ``extrapolate`` -- the extrapolation points will be calculated by extending the gradient of the closest two points, - * ``error`` -- a ValueError exception will be raised, notifying an attempt to extrapolate, - * ``nan`` -- the extrapolation points will be be set to NaN, - * ``mask`` -- the extrapolation points will always be masked, even if the source data is not a MaskedArray, or - * ``nanmask`` -- if the source data is a MaskedArray the extrapolation points will be masked. Otherwise they will be set to NaN. +* ``extrapolate`` -- the extrapolation points will be calculated by extending the gradient of the closest two points, +* ``error`` -- a ValueError exception will be raised, notifying an attempt to extrapolate, +* ``nan`` -- the extrapolation points will be be set to NaN, +* ``mask`` -- the extrapolation points will always be masked, even if the source data is not a MaskedArray, or +* ``nanmask`` -- if the source data is a MaskedArray the extrapolation points will be masked. Otherwise they will be set to NaN. Using an extrapolation mode is achieved by constructing an interpolation scheme with the extrapolation mode keyword set as required. The constructed scheme @@ -206,8 +206,8 @@ intensive part of an interpolation is setting up the interpolator. To cache an interpolator you must set up an interpolator scheme and call the scheme's interpolator method. The interpolator method takes as arguments: - #. a cube to be interpolated, and - #. an iterable of coordinate names or coordinate instances of the coordinates that are to be interpolated over. +#. a cube to be interpolated, and +#. an iterable of coordinate names or coordinate instances of the coordinates that are to be interpolated over. For example: @@ -244,8 +244,8 @@ regridding is based on the **horizontal** grid of *another cube*. Regridding a cube is achieved with the :meth:`cube.regrid() ` method. This method expects two arguments: - #. *another cube* that defines the target grid onto which the cube should be regridded, and - #. the regridding scheme to use. +#. *another cube* that defines the target grid onto which the cube should be regridded, and +#. the regridding scheme to use. .. note:: @@ -278,15 +278,15 @@ mode when defining the regridding scheme. For the available regridding schemes in Iris, the ``extrapolation_mode`` keyword must be one of: - * ``extrapolate`` -- +* ``extrapolate`` -- - * for :class:`~iris.analysis.Linear` the extrapolation points will be calculated by extending the gradient of the closest two points. - * for :class:`~iris.analysis.Nearest` the extrapolation points will take their value from the nearest source point. + * for :class:`~iris.analysis.Linear` the extrapolation points will be calculated by extending the gradient of the closest two points. + * for :class:`~iris.analysis.Nearest` the extrapolation points will take their value from the nearest source point. - * ``nan`` -- the extrapolation points will be be set to NaN. - * ``error`` -- a ValueError exception will be raised, notifying an attempt to extrapolate. - * ``mask`` -- the extrapolation points will always be masked, even if the source data is not a MaskedArray. - * ``nanmask`` -- if the source data is a MaskedArray the extrapolation points will be masked. Otherwise they will be set to NaN. +* ``nan`` -- the extrapolation points will be be set to NaN. +* ``error`` -- a ValueError exception will be raised, notifying an attempt to extrapolate. +* ``mask`` -- the extrapolation points will always be masked, even if the source data is not a MaskedArray. +* ``nanmask`` -- if the source data is a MaskedArray the extrapolation points will be masked. Otherwise they will be set to NaN. The ``rotated_psl`` cube is defined on a limited area rotated pole grid. If we regridded the ``rotated_psl`` cube onto the global grid as defined by the ``global_air_temp`` cube @@ -395,8 +395,8 @@ intensive part of a regrid is setting up the regridder. To cache a regridder you must set up a regridder scheme and call the scheme's regridder method. The regridder method takes as arguments: - #. a cube (that is to be regridded) defining the source grid, and - #. a cube defining the target grid to regrid the source cube to. +#. a cube (that is to be regridded) defining the source grid, and +#. a cube defining the target grid to regrid the source cube to. For example: diff --git a/docs/src/userguide/iris_cubes.rst b/docs/src/userguide/iris_cubes.rst index d13dee369c..29d8f3cefc 100644 --- a/docs/src/userguide/iris_cubes.rst +++ b/docs/src/userguide/iris_cubes.rst @@ -4,82 +4,105 @@ Iris Data Structures ==================== -The top level object in Iris is called a cube. A cube contains data and metadata about a phenomenon. +The top level object in Iris is called a cube. A cube contains data and +metadata about a phenomenon. -In Iris, a cube is an interpretation of the *Climate and Forecast (CF) Metadata Conventions* whose purpose is to: +In Iris, a cube is an interpretation of the *Climate and Forecast (CF) +Metadata Conventions* whose purpose is to: - *require conforming datasets to contain sufficient metadata that they are self-describing... including physical - units if appropriate, and that each value can be located in space (relative to earth-based coordinates) and time.* +.. panels:: + :container: container-lg pb-3 + :column: col-lg-12 p-2 -Whilst the CF conventions are often mentioned alongside NetCDF, Iris implements several major format importers which can take -files of specific formats and turn them into Iris cubes. Additionally, a framework is provided which allows users -to extend Iris' import capability to cater for specialist or unimplemented formats. + *require conforming datasets to contain sufficient metadata that they are + self-describing... including physical units if appropriate, and that each + value can be located in space (relative to earth-based coordinates) and + time.* -A single cube describes one and only one phenomenon, always has a name, a unit and -an n-dimensional data array to represents the cube's phenomenon. In order to locate the -data spatially, temporally, or in any other higher-dimensional space, a collection of *coordinates* -exist on the cube. + +Whilst the CF conventions are often mentioned alongside NetCDF, Iris implements +several major format importers which can take files of specific formats and +turn them into Iris cubes. Additionally, a framework is provided which allows +users to extend Iris' import capability to cater for specialist or +unimplemented formats. + +A single cube describes one and only one phenomenon, always has a name, a unit +and an n-dimensional data array to represents the cube's phenomenon. In order +to locate the data spatially, temporally, or in any other higher-dimensional +space, a collection of *coordinates* exist on the cube. Coordinates =========== -A coordinate is a container to store metadata about some dimension(s) of a cube's data array and therefore, -by definition, its phenomenon. - - * Each coordinate has a name and a unit. - * When a coordinate is added to a cube, the data dimensions that it represents are also provided. - - * The shape of a coordinate is always the same as the shape of the associated data dimension(s) on the cube. - * A dimension not explicitly listed signifies that the coordinate is independent of that dimension. - * Each dimension of a coordinate must be mapped to a data dimension. The only coordinates with no mapping are - scalar coordinates. - - * Depending on the underlying data that the coordinate is representing, its values may be discrete points or be - bounded to represent interval extents (e.g. temperature at *point x* **vs** rainfall accumulation *between 0000-1200 hours*). - * Coordinates have an attributes dictionary which can hold arbitrary extra metadata, excluding certain restricted CF names - * More complex coordinates may contain a coordinate system which is necessary to fully interpret the values - contained within the coordinate. - +A coordinate is a container to store metadata about some dimension(s) of a +cube's data array and therefore, by definition, its phenomenon. + +* Each coordinate has a name and a unit. +* When a coordinate is added to a cube, the data dimensions that it + represents are also provided. + + * The shape of a coordinate is always the same as the shape of the + associated data dimension(s) on the cube. + * A dimension not explicitly listed signifies that the coordinate is + independent of that dimension. + * Each dimension of a coordinate must be mapped to a data dimension. The + only coordinates with no mapping are scalar coordinates. + +* Depending on the underlying data that the coordinate is representing, its + values may be discrete points or be bounded to represent interval extents + (e.g. temperature at *point x* **vs** rainfall accumulation *between + 0000-1200 hours*). +* Coordinates have an attributes dictionary which can hold arbitrary extra + metadata, excluding certain restricted CF names +* More complex coordinates may contain a coordinate system which is + necessary to fully interpret the values contained within the coordinate. + There are two classes of coordinates: - **DimCoord** - - * Numeric - * Monotonic - * Representative of, at most, a single data dimension (1d) +**DimCoord** + +* Numeric +* Monotonic +* Representative of, at most, a single data dimension (1d) + +**AuxCoord** + +* May be of any type, including strings +* May represent multiple data dimensions (n-dimensional) - **AuxCoord** - - * May be of any type, including strings - * May represent multiple data dimensions (n-dimensional) - Cube ==== A cube consists of: - * a standard name and/or a long name and an appropriate unit - * a data array who's values are representative of the phenomenon - * a collection of coordinates and associated data dimensions on the cube's data array, which are split into two separate lists: +* a standard name and/or a long name and an appropriate unit +* a data array who's values are representative of the phenomenon +* a collection of coordinates and associated data dimensions on the cube's + data array, which are split into two separate lists: + + * *dimension coordinates* - DimCoords which uniquely map to exactly one + data dimension, ordered by dimension. + * *auxiliary coordinates* - DimCoords or AuxCoords which map to as many + data dimensions as the coordinate has dimensions. - * *dimension coordinates* - DimCoords which uniquely map to exactly one data dimension, ordered by dimension. - * *auxiliary coordinates* - DimCoords or AuxCoords which map to as many data dimensions as the coordinate has dimensions. - - * an attributes dictionary which, other than some protected CF names, can hold arbitrary extra metadata. - * a list of cell methods to represent operations which have already been applied to the data (e.g. "mean over time") - * a list of coordinate "factories" used for deriving coordinates from the values of other coordinates in the cube +* an attributes dictionary which, other than some protected CF names, can + hold arbitrary extra metadata. +* a list of cell methods to represent operations which have already been + applied to the data (e.g. "mean over time") +* a list of coordinate "factories" used for deriving coordinates from the + values of other coordinates in the cube Cubes in Practice ----------------- - A Simple Cube Example ===================== -Suppose we have some gridded data which has 24 air temperature readings (in Kelvin) which is located at -4 different longitudes, 2 different latitudes and 3 different heights. Our data array can be represented pictorially: +Suppose we have some gridded data which has 24 air temperature readings +(in Kelvin) which is located at 4 different longitudes, 2 different latitudes +and 3 different heights. Our data array can be represented pictorially: .. image:: multi_array.png @@ -87,61 +110,66 @@ Where dimensions 0, 1, and 2 have lengths 3, 2 and 4 respectively. The Iris cube to represent this data would consist of: - * a standard name of ``air_temperature`` and a unit of ``kelvin`` - * a data array of shape ``(3, 2, 4)`` - * a coordinate, mapping to dimension 0, consisting of: - - * a standard name of ``height`` and unit of ``meters`` - * an array of length 3 representing the 3 ``height`` points - - * a coordinate, mapping to dimension 1, consisting of: - - * a standard name of ``latitude`` and unit of ``degrees`` - * an array of length 2 representing the 2 latitude points - * a coordinate system such that the ``latitude`` points could be fully located on the globe - - * a coordinate, mapping to dimension 2, consisting of: - - * a standard name of ``longitude`` and unit of ``degrees`` - * an array of length 4 representing the 4 longitude points - * a coordinate system such that the ``longitude`` points could be fully located on the globe - +* a standard name of ``air_temperature`` and a unit of ``kelvin`` +* a data array of shape ``(3, 2, 4)`` +* a coordinate, mapping to dimension 0, consisting of: + + * a standard name of ``height`` and unit of ``meters`` + * an array of length 3 representing the 3 ``height`` points +* a coordinate, mapping to dimension 1, consisting of: + * a standard name of ``latitude`` and unit of ``degrees`` + * an array of length 2 representing the 2 latitude points + * a coordinate system such that the ``latitude`` points could be fully + located on the globe -Pictorially the cube has taken on more information than a simple array: +* a coordinate, mapping to dimension 2, consisting of: + + * a standard name of ``longitude`` and unit of ``degrees`` + * an array of length 4 representing the 4 longitude points + * a coordinate system such that the ``longitude`` points could be fully + located on the globe + +Pictorially the cube has taken on more information than a simple array: .. image:: multi_array_to_cube.png -Additionally further information may be optionally attached to the cube. -For example, it is possible to attach any of the following: - - * a coordinate, not mapping to any data dimensions, consisting of: - - * a standard name of ``time`` and unit of ``days since 2000-01-01 00:00`` - * a data array of length 1 representing the time that the data array is valid for - - * an auxiliary coordinate, mapping to dimensions 1 and 2, consisting of: - - * a long name of ``place name`` and no unit - * a 2d string array of shape ``(2, 4)`` with the names of the 8 places that the lat/lons correspond to - - * an auxiliary coordinate "factory", which can derive its own mapping, consisting of: - - * a standard name of ``height`` and a unit of ``feet`` - * knowledge of how data values for this coordinate can be calculated given the ``height in meters`` coordinate - - * a cell method of "mean" over "ensemble" to indicate that the data has been meaned over - a collection of "ensembles" (i.e. multiple model runs). +Additionally further information may be optionally attached to the cube. +For example, it is possible to attach any of the following: + +* a coordinate, not mapping to any data dimensions, consisting of: + + * a standard name of ``time`` and unit of ``days since 2000-01-01 00:00`` + * a data array of length 1 representing the time that the data array is + valid for + +* an auxiliary coordinate, mapping to dimensions 1 and 2, consisting of: + + * a long name of ``place name`` and no unit + * a 2d string array of shape ``(2, 4)`` with the names of the 8 places + that the lat/lons correspond to + +* an auxiliary coordinate "factory", which can derive its own mapping, + consisting of: + + * a standard name of ``height`` and a unit of ``feet`` + * knowledge of how data values for this coordinate can be calculated + given the ``height in meters`` coordinate + +* a cell method of "mean" over "ensemble" to indicate that the data has been + meaned over a collection of "ensembles" (i.e. multiple model runs). Printing a Cube =============== -Every Iris cube can be printed to screen as you will see later in the user guide. It is worth familiarising yourself with the -output as this is the quickest way of inspecting the contents of a cube. Here is the result of printing a real life cube: +Every Iris cube can be printed to screen as you will see later in the user +guide. It is worth familiarising yourself with the output as this is the +quickest way of inspecting the contents of a cube. Here is the result of +printing a real life cube: .. _hybrid_cube_printout: @@ -150,7 +178,7 @@ output as this is the quickest way of inspecting the contents of a cube. Here is import iris filename = iris.sample_data_path('uk_hires.pp') - # NOTE: Every time the output of this cube changes, the full list of deductions below should be re-assessed. + # NOTE: Every time the output of this cube changes, the full list of deductions below should be re-assessed. print(iris.load_cube(filename, 'air_potential_temperature')) .. testoutput:: @@ -178,16 +206,22 @@ output as this is the quickest way of inspecting the contents of a cube. Here is Using this output we can deduce that: - * The cube represents air potential temperature. - * There are 4 data dimensions, and the data has a shape of ``(3, 7, 204, 187)`` - * The 4 data dimensions are mapped to the ``time``, ``model_level_number``, - ``grid_latitude``, ``grid_longitude`` coordinates respectively - * There are three 1d auxiliary coordinates and one 2d auxiliary (``surface_altitude``) - * There is a single ``altitude`` derived coordinate, which spans 3 data dimensions - * There are 7 distinct values in the "model_level_number" coordinate. Similar inferences can - be made for the other dimension coordinates. - * There are 7, not necessarily distinct, values in the ``level_height`` coordinate. - * There is a single ``forecast_reference_time`` scalar coordinate representing the entire cube. - * The cube has one further attribute relating to the phenomenon. - In this case the originating file format, PP, encodes information in a STASH code which in some cases can - be useful for identifying advanced experiment information relating to the phenomenon. +* The cube represents air potential temperature. +* There are 4 data dimensions, and the data has a shape of ``(3, 7, 204, 187)`` +* The 4 data dimensions are mapped to the ``time``, ``model_level_number``, + ``grid_latitude``, ``grid_longitude`` coordinates respectively +* There are three 1d auxiliary coordinates and one 2d auxiliary + (``surface_altitude``) +* There is a single ``altitude`` derived coordinate, which spans 3 data + dimensions +* There are 7 distinct values in the "model_level_number" coordinate. Similar + inferences can + be made for the other dimension coordinates. +* There are 7, not necessarily distinct, values in the ``level_height`` + coordinate. +* There is a single ``forecast_reference_time`` scalar coordinate representing + the entire cube. +* The cube has one further attribute relating to the phenomenon. + In this case the originating file format, PP, encodes information in a STASH + code which in some cases can be useful for identifying advanced experiment + information relating to the phenomenon. diff --git a/docs/src/userguide/loading_iris_cubes.rst b/docs/src/userguide/loading_iris_cubes.rst index 9e9c343300..33ad932d70 100644 --- a/docs/src/userguide/loading_iris_cubes.rst +++ b/docs/src/userguide/loading_iris_cubes.rst @@ -39,15 +39,15 @@ This shows that there were 2 cubes as a result of loading the file, they were: The ``surface_altitude`` cube was 2 dimensional with: - * the two dimensions have extents of 204 and 187 respectively and are - represented by the ``grid_latitude`` and ``grid_longitude`` coordinates. +* the two dimensions have extents of 204 and 187 respectively and are + represented by the ``grid_latitude`` and ``grid_longitude`` coordinates. The ``air_potential_temperature`` cubes were 4 dimensional with: - * the same length ``grid_latitude`` and ``grid_longitude`` dimensions as - ``surface_altitide`` - * a ``time`` dimension of length 3 - * a ``model_level_number`` dimension of length 7 +* the same length ``grid_latitude`` and ``grid_longitude`` dimensions as + ``surface_altitide`` +* a ``time`` dimension of length 3 +* a ``model_level_number`` dimension of length 7 .. note:: @@ -55,7 +55,7 @@ The ``air_potential_temperature`` cubes were 4 dimensional with: (even if it only contains one :class:`iris.cube.Cube` - see :ref:`strict-loading`). Anything that can be done with a Python :class:`list` can be done with an :class:`iris.cube.CubeList`. - + The order of this list should not be relied upon. Ways of loading a specific cube or cubes are covered in :ref:`constrained-loading` and :ref:`strict-loading`. diff --git a/docs/src/userguide/merge_and_concat.rst b/docs/src/userguide/merge_and_concat.rst index e8425df5ec..08c3ce9711 100644 --- a/docs/src/userguide/merge_and_concat.rst +++ b/docs/src/userguide/merge_and_concat.rst @@ -22,14 +22,14 @@ result in fewer cubes as output. The following diagram illustrates the two proce There is one major difference between the ``merge`` and ``concatenate`` processes. - * The ``merge`` process combines multiple input cubes into a - single resultant cube with new dimensions created from the - *scalar coordinate values* of the input cubes. - - * The ``concatenate`` process combines multiple input cubes into a - single resultant cube with the same *number of dimensions* as the input cubes, - but with the length of one or more dimensions extended by *joining together - sequential dimension coordinates*. +* The ``merge`` process combines multiple input cubes into a + single resultant cube with new dimensions created from the + *scalar coordinate values* of the input cubes. + +* The ``concatenate`` process combines multiple input cubes into a + single resultant cube with the same *number of dimensions* as the input cubes, + but with the length of one or more dimensions extended by *joining together + sequential dimension coordinates*. Let's imagine 28 individual cubes representing the temperature at a location ``(y, x)``; one cube for each day of February. We can use diff --git a/docs/src/userguide/plotting_a_cube.rst b/docs/src/userguide/plotting_a_cube.rst index cfb3445d9b..a2334367c5 100644 --- a/docs/src/userguide/plotting_a_cube.rst +++ b/docs/src/userguide/plotting_a_cube.rst @@ -101,15 +101,15 @@ see :py:func:`matplotlib.pyplot.savefig`). Some of the formats which are supported by **plt.savefig**: - ====== ====== ====================================================================== - Format Type Description - ====== ====== ====================================================================== - EPS Vector Encapsulated PostScript - PDF Vector Portable Document Format - PNG Raster Portable Network Graphics, a format with a lossless compression method - PS Vector PostScript, ideal for printer output - SVG Vector Scalable Vector Graphics, XML based - ====== ====== ====================================================================== +====== ====== ====================================================================== +Format Type Description +====== ====== ====================================================================== +EPS Vector Encapsulated PostScript +PDF Vector Portable Document Format +PNG Raster Portable Network Graphics, a format with a lossless compression method +PS Vector PostScript, ideal for printer output +SVG Vector Scalable Vector Graphics, XML based +====== ====== ====================================================================== ****************** Iris Cube Plotting @@ -125,12 +125,12 @@ wrapper functions. As a rule of thumb: - * if you wish to do a visualisation with a cube, use ``iris.plot`` or - ``iris.quickplot``. - * if you wish to show, save or manipulate **any** visualisation, - including ones created with Iris, use ``matplotlib.pyplot``. - * if you wish to create a non cube visualisation, also use - ``matplotlib.pyplot``. +* if you wish to do a visualisation with a cube, use ``iris.plot`` or + ``iris.quickplot``. +* if you wish to show, save or manipulate **any** visualisation, + including ones created with Iris, use ``matplotlib.pyplot``. +* if you wish to create a non cube visualisation, also use + ``matplotlib.pyplot``. The ``iris.quickplot`` module is exactly the same as the ``iris.plot`` module, except that ``quickplot`` will add a title, x and y labels and a colorbar diff --git a/docs/src/userguide/real_and_lazy_data.rst b/docs/src/userguide/real_and_lazy_data.rst index 0bc1846457..9d66a2f086 100644 --- a/docs/src/userguide/real_and_lazy_data.rst +++ b/docs/src/userguide/real_and_lazy_data.rst @@ -140,11 +140,11 @@ Core Data Cubes have the concept of "core data". This returns the cube's data in its current state: - * If a cube has lazy data, calling the cube's :meth:`~iris.cube.Cube.core_data` method - will return the cube's lazy dask array. Calling the cube's - :meth:`~iris.cube.Cube.core_data` method **will never realise** the cube's data. - * If a cube has real data, calling the cube's :meth:`~iris.cube.Cube.core_data` method - will return the cube's real NumPy array. +* If a cube has lazy data, calling the cube's :meth:`~iris.cube.Cube.core_data` method + will return the cube's lazy dask array. Calling the cube's + :meth:`~iris.cube.Cube.core_data` method **will never realise** the cube's data. +* If a cube has real data, calling the cube's :meth:`~iris.cube.Cube.core_data` method + will return the cube's real NumPy array. For example:: @@ -174,14 +174,14 @@ In the same way that Iris cubes contain a data array, Iris coordinates contain a points array and an optional bounds array. Coordinate points and bounds arrays can also be real or lazy: - * A :class:`~iris.coords.DimCoord` will only ever have **real** points and bounds - arrays because of monotonicity checks that realise lazy arrays. - * An :class:`~iris.coords.AuxCoord` can have **real or lazy** points and bounds. - * An :class:`~iris.aux_factory.AuxCoordFactory` (or derived coordinate) - can have **real or lazy** points and bounds. If all of the - :class:`~iris.coords.AuxCoord` instances used to construct the derived coordinate - have real points and bounds then the derived coordinate will have real points - and bounds, otherwise the derived coordinate will have lazy points and bounds. +* A :class:`~iris.coords.DimCoord` will only ever have **real** points and bounds + arrays because of monotonicity checks that realise lazy arrays. +* An :class:`~iris.coords.AuxCoord` can have **real or lazy** points and bounds. +* An :class:`~iris.aux_factory.AuxCoordFactory` (or derived coordinate) + can have **real or lazy** points and bounds. If all of the + :class:`~iris.coords.AuxCoord` instances used to construct the derived coordinate + have real points and bounds then the derived coordinate will have real points + and bounds, otherwise the derived coordinate will have lazy points and bounds. Iris cubes and coordinates have very similar interfaces, which extends to accessing coordinates' lazy points and bounds: diff --git a/docs/src/voted_issues.rst b/docs/src/voted_issues.rst index 473af3351e..7d983448b9 100644 --- a/docs/src/voted_issues.rst +++ b/docs/src/voted_issues.rst @@ -1,6 +1,6 @@ .. include:: common_links.inc -.. _voted_issues: +.. _voted_issues_top: Voted Issues ============ diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index 4a82b24857..63deb5d459 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -289,7 +289,7 @@ v3.2.1 (11 Mar 2022) #. `@rcomer`_ updated the "Plotting Wind Direction Using Quiver" Gallery example. (:pull:`4120`) -#. `@trexfeathers`_ included `Iris GitHub Discussions`_ in +#. `@trexfeathers`_ included Iris `GitHub Discussions`_ in :ref:`get involved `. (:pull:`4307`) #. `@wjbenfold`_ improved readability in :ref:`userguide interpolation diff --git a/docs/src/whatsnew/dev.rst b/docs/src/whatsnew/dev.rst deleted file mode 100644 index 843815b46b..0000000000 --- a/docs/src/whatsnew/dev.rst +++ /dev/null @@ -1,137 +0,0 @@ -.. include:: ../common_links.inc - -|iris_version| |build_date| [unreleased] -**************************************** - -This document explains the changes made to Iris for this release -(:doc:`View all changes `.) - - -.. dropdown:: :opticon:`report` |iris_version| Release Highlights - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - :open: - - The highlights for this minor release of Iris include: - - * N/A - - And finally, get in touch with us on :issue:`GitHub` if you have - any issues or feature requests for improving Iris. Enjoy! - - -📢 Announcements -================ - -#. N/A - - -✨ Features -=========== - -#. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to - :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`) - -#. `@rcomer`_ implemented lazy aggregation for the - :obj:`iris.analysis.PERCENTILE` aggregator. (:pull:`3901`) - -#. `@pp-mo`_ fixed cube arithmetic operation for cubes with meshes. - (:issue:`4454`, :pull:`4651`) - - -🐛 Bugs Fixed -============= - -#. `@rcomer`_ reverted part of the change from :pull:`3906` so that - :func:`iris.plot.plot` no longer defaults to placing a "Y" coordinate (e.g. - latitude) on the y-axis of the plot. (:issue:`4493`, :pull:`4601`) - -#. `@rcomer`_ enabled passing of scalar objects to :func:`~iris.plot.plot` and - :func:`~iris.plot.scatter`. (:pull:`4616`) - -#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.aggregated_by` with `mdtol` for 1D - cubes where an aggregated section is entirely masked, reported at - :issue:`3190`. (:pull:`4246`) - -#. `@rcomer`_ ensured that a :class:`matplotlib.axes.Axes`'s position is preserved - when Iris replaces it with a :class:`cartopy.mpl.geoaxes.GeoAxes`, fixing - :issue:`1157`. (:pull:`4273`) - -#. `@rcomer`_ fixed :meth:`~iris.coords.Coord.nearest_neighbour_index` for edge - cases where the requested point is float and the coordinate has integer - bounds, reported at :issue:`2969`. (:pull:`4245`) - -#. `@rcomer`_ modified bounds setting on :obj:`~iris.coords.DimCoord` instances - so that the order of the cell bounds is automatically reversed - to match the coordinate's direction if necessary. This is consistent with - the `Bounds for 1-D coordinate variables` subsection of the `Cell Boundaries`_ - section of the CF Conventions and ensures that contiguity is preserved if a - coordinate's direction is reversed. (:issue:`3249`, :issue:`423`, - :issue:`4078`, :issue:`3756`, :pull:`4466`) - - -💣 Incompatible Changes -======================= - -#. N/A - - -🚀 Performance Enhancements -=========================== - -#. N/A - - -🔥 Deprecations -=============== - -#. N/A - - -🔗 Dependencies -=============== - -#. `@rcomer`_ introduced the ``nc-time-axis >=1.4`` minimum pin, reflecting that - we no longer use the deprecated :class:`nc_time_axis.CalendarDateTime` - when plotting against time coordinates. (:pull:`4584`) - - -📚 Documentation -================ - -#. `@tkknight`_ added a page to show the issues that have been voted for. See - :ref:`voted_issues`. (:issue:`3307`, :pull:`4617`) -#. `@wjbenfold`_ added a note about fixing proxy URLs in lockfiles generated - because dependencies have changed. (:pull:`4666`) -#. `@lbdreyer`_ moved most of the User Guide's :class:`iris.Constraint` examples - from :ref:`loading_iris_cubes` to :ref:`cube_extraction` and added an - example of constraining on bounded time. (:pull:`4656`) - - -💼 Internal -=========== - -#. `@trexfeathers`_ and `@pp-mo`_ finished implementing a mature benchmarking - infrastructure (see :ref:`contributing.benchmarks`), building on 2 hard - years of lessons learned 🎉. (:pull:`4477`, :pull:`4562`, :pull:`4571`, - :pull:`4583`, :pull:`4621`) -#. `@wjbenfold`_ used the aforementioned benchmarking infrastructure to - introduce deep (large 3rd dimension) loading and realisation benchmarks. - (:pull:`4654`) -#. `@wjbenfold`_ made :func:`iris.tests.stock.simple_1d` respect the - ``with_bounds`` argument. (:pull:`4658`) - - -.. comment - Whatsnew author names (@github name) in alphabetical order. Note that, - core dev names are automatically included by the common_links.inc: - - - - -.. comment - Whatsnew resources in alphabetical order: - -.. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index 7e0829da5b..949d727a99 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -10,7 +10,7 @@ Iris versions. .. toctree:: :maxdepth: 1 - dev.rst + latest.rst 3.2.rst 3.1.rst 3.0.rst diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst deleted file mode 120000 index 56aebe92dd..0000000000 --- a/docs/src/whatsnew/latest.rst +++ /dev/null @@ -1 +0,0 @@ -dev.rst \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst new file mode 100644 index 0000000000..52313d5d81 --- /dev/null +++ b/docs/src/whatsnew/latest.rst @@ -0,0 +1,145 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`report` |iris_version| Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to + :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`) + +#. `@rcomer`_ implemented lazy aggregation for the + :obj:`iris.analysis.PERCENTILE` aggregator. (:pull:`3901`) + +#. `@pp-mo`_ fixed cube arithmetic operation for cubes with meshes. + (:issue:`4454`, :pull:`4651`) + + +🐛 Bugs Fixed +============= + +#. `@rcomer`_ reverted part of the change from :pull:`3906` so that + :func:`iris.plot.plot` no longer defaults to placing a "Y" coordinate (e.g. + latitude) on the y-axis of the plot. (:issue:`4493`, :pull:`4601`) + +#. `@rcomer`_ enabled passing of scalar objects to :func:`~iris.plot.plot` and + :func:`~iris.plot.scatter`. (:pull:`4616`) + +#. `@rcomer`_ fixed :meth:`~iris.cube.Cube.aggregated_by` with `mdtol` for 1D + cubes where an aggregated section is entirely masked, reported at + :issue:`3190`. (:pull:`4246`) + +#. `@rcomer`_ ensured that a :class:`matplotlib.axes.Axes`'s position is preserved + when Iris replaces it with a :class:`cartopy.mpl.geoaxes.GeoAxes`, fixing + :issue:`1157`. (:pull:`4273`) + +#. `@rcomer`_ fixed :meth:`~iris.coords.Coord.nearest_neighbour_index` for edge + cases where the requested point is float and the coordinate has integer + bounds, reported at :issue:`2969`. (:pull:`4245`) + +#. `@rcomer`_ modified bounds setting on :obj:`~iris.coords.DimCoord` instances + so that the order of the cell bounds is automatically reversed + to match the coordinate's direction if necessary. This is consistent with + the `Bounds for 1-D coordinate variables` subsection of the `Cell Boundaries`_ + section of the CF Conventions and ensures that contiguity is preserved if a + coordinate's direction is reversed. (:issue:`3249`, :issue:`423`, + :issue:`4078`, :issue:`3756`, :pull:`4466`) + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. `@rcomer`_ introduced the ``nc-time-axis >=1.4`` minimum pin, reflecting that + we no longer use the deprecated :class:`nc_time_axis.CalendarDateTime` + when plotting against time coordinates. (:pull:`4584`) + + +📚 Documentation +================ + +#. `@tkknight`_ added a page to show the issues that have been voted for. See + :ref:`voted_issues_top`. (:issue:`3307`, :pull:`4617`) + +#. `@wjbenfold`_ added a note about fixing proxy URLs in lockfiles generated + because dependencies have changed. (:pull:`4666`) + +#. `@lbdreyer`_ moved most of the User Guide's :class:`iris.Constraint` examples + from :ref:`loading_iris_cubes` to :ref:`cube_extraction` and added an + example of constraining on bounded time. (:pull:`4656`) + +#. `@tkknight`_ adopted the `PyData Sphinx Theme`_ for the documentation. + (:discussion:`4344`, :pull:`4661`) + + +💼 Internal +=========== + +#. `@trexfeathers`_ and `@pp-mo`_ finished implementing a mature benchmarking + infrastructure (see :ref:`contributing.benchmarks`), building on 2 hard + years of lessons learned 🎉. (:pull:`4477`, :pull:`4562`, :pull:`4571`, + :pull:`4583`, :pull:`4621`) + +#. `@wjbenfold`_ used the aforementioned benchmarking infrastructure to + introduce deep (large 3rd dimension) loading and realisation benchmarks. + (:pull:`4654`) + +#. `@wjbenfold`_ made :func:`iris.tests.stock.simple_1d` respect the + ``with_bounds`` argument. (:pull:`4658`) + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: + +.. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries +.. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html diff --git a/docs/src/whatsnew/dev.rst.template b/docs/src/whatsnew/latest.rst.template similarity index 100% rename from docs/src/whatsnew/dev.rst.template rename to docs/src/whatsnew/latest.rst.template diff --git a/docs/src/why_iris.rst b/docs/src/why_iris.rst new file mode 100644 index 0000000000..63a515f68e --- /dev/null +++ b/docs/src/why_iris.rst @@ -0,0 +1,44 @@ +.. _why_iris: + +Why Iris +======== + +**A powerful, format-agnostic, community-driven Python package for analysing +and visualising Earth science data.** + +Iris implements a data model based on the `CF conventions `_ +giving you a powerful, format-agnostic interface for working with your data. +It excels when working with multi-dimensional Earth Science data, where tabular +representations become unwieldy and inefficient. + +`CF Standard names `_, +`units `_, and coordinate metadata +are built into Iris, giving you a rich and expressive interface for maintaining +an accurate representation of your data. Its treatment of data and +associated metadata as first-class objects includes: + +.. rst-class:: squarelist + +* visualisation interface based on `matplotlib `_ and + `cartopy `_, +* unit conversion, +* subsetting and extraction, +* merge and concatenate, +* aggregations and reductions (including min, max, mean and weighted averages), +* interpolation and regridding (including nearest-neighbor, linear and + area-weighted), and +* operator overloads (``+``, ``-``, ``*``, ``/``, etc.). + +A number of file formats are recognised by Iris, including CF-compliant NetCDF, +GRIB, and PP, and it has a plugin architecture to allow other formats to be +added seamlessly. + +Building upon `NumPy `_ and +`dask `_, Iris scales from efficient +single-machine workflows right through to multi-core clusters and HPC. +Interoperability with packages from the wider scientific Python ecosystem comes +from Iris' use of standard NumPy/dask arrays as its underlying data storage. + +Iris is part of SciTools, for more information see https://scitools.org.uk/. +For **Iris 2.4** and earlier documentation please see the +:link-badge:`https://scitools.org.uk/iris/docs/v2.4.0/,"legacy documentation",cls=badge-info text-white`. diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 7883f1deaa..c91683f0cf 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: d7bddc89ba289d4c1b48871b1289eb0bf45715ef2e274de01e245547fb6a8df6 +# input_hash: c7823bd2d790451e307ab5cbb450b09ee9460f9e8334dbf40f32d8bab4a2fefa @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 @@ -39,7 +39,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.18-pthreads_h8fe5266_0.tar.bz2#41532e4448c0cce086d6570f95e4e12e +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d @@ -64,7 +64,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 -https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-13_linux64_openblas.tar.bz2#8a4038563ed92dfa622bd72c0d8f31d3 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-14_linux64_openblas.tar.bz2#fb31fbbde682414550bbe15e3964420f https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 @@ -72,7 +72,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-haf5c9bc_2.tar.bz2#3593de3e1062c658062a53bf3874ff41 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-haf5c9bc_3.tar.bz2#3fece7479fccc385e5e4f898277b7983 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 @@ -82,17 +82,17 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#52 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 -https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-13_linux64_openblas.tar.bz2#b17676dbd6688396c3a3076259fb7907 +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-14_linux64_openblas.tar.bz2#1b41ea4c32014d878e84de4e5690df7a https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc -https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-13_linux64_openblas.tar.bz2#018b80e8f21d8560ae4961567e3e00c9 +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-14_linux64_openblas.tar.bz2#13367ebd0243a949cee7564b13c3cd42 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_2.tar.bz2#bc001857cad0d2859f1507f420691bcc +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_3.tar.bz2#ce5af4269e1b874696380f6ace9a72d7 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.1-h4ff8645_0.tar.bz2#8057ac02d6d10a162d7eb4b0ca7ed291 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 @@ -119,7 +119,7 @@ https://conda.anaconda.org/conda-forge/linux-64/curl-7.82.0-h7bff187_0.tar.bz2#6 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.13.96-h8e229c2_2.tar.bz2#70d2ba376dff51a33343169642aebb44 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.3.0-pyhd8ed1ab_0.tar.bz2#960b78685ccbedb992886fc4ce37bf6e https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.1-hcf0ee16_1.tar.bz2#4c41fc47db7d631862f12e5e5c885cb9 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 @@ -139,6 +139,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bf https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.1-pyhd8ed1ab_0.tar.bz2#d821b295c4bd18ad27e1e19543a5784a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 @@ -147,9 +148,10 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.ta https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.7.0-pyhd8ed1ab_1.tar.bz2#b689b2cbc8481b224777415e1a193170 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.10.0-pyha770c72_0.tar.bz2#c29e0b4a2ff546cedbe6dc2921948a1d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a @@ -159,8 +161,8 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_1. https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38he865349_0.tar.bz2#b1b3d6847a68251a1465206ab466b475 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_0.tar.bz2#90b4ee61abb81fb3f3995ec9d4c734f0 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_1.tar.bz2#0856f29eb084060e01a39813fed4dc92 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 @@ -171,21 +173,21 @@ https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe -https://conda.anaconda.org/conda-forge/linux-64/setuptools-61.3.0-py38h578d9bd_0.tar.bz2#9d042d3e103851e2f59433313ff84df4 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.0.0-py38h578d9bd_0.tar.bz2#137bed0dfd03cc5a762978b46dc021e3 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h497a2fe_0.tar.bz2#8da7787169411910df2a62dc8ef533e0 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.0-py38h578d9bd_1.tar.bz2#6c6a5da4d7e2af1dbbfb1e21daa559cc https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b -https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_0.tar.bz2#22274a82cf664d94a9e7c8f9deddf52a +https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.0-pyhd8ed1ab_0.tar.bz2#a47051950a34bef40d8369a5f320b78d -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.31.2-py38h0a891b7_0.tar.bz2#381ffd61d2617af9bddb1cee60352480 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.31.2-py38h0a891b7_1.tar.bz2#46b9f0b776c532c038147915f8fb1e38 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.0-h40b6f09_0.tar.bz2#017b20e7e98860f0bfa7492ce16390a7 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.1-pyhd8ed1ab_0.tar.bz2#40b3f446c61729cdd21ed9d85627df6e https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.1-py38h43a58ef_0.tar.bz2#1083ebe2edc30e4fb9568d1f66e3588b +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_0.tar.bz2#52b2e1f0dbcba061f4d83c9879c9bd76 https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38hbc0797c_2.tar.bz2#7e4c695d10aa5e4576e87fb00a9d2511 @@ -209,17 +211,17 @@ https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c1 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_2.tar.bz2#aa768fdaad03509a97df37f81163346b +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_3.tar.bz2#a08562889b985d021550e22443cf0fce https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.17.0-py38h578d9bd_0.tar.bz2#839ac9dba9a6126c9532781a9ea4506b +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.18.1-py38h578d9bd_0.tar.bz2#b590f730888ff2615587968a73248761 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.1-py38h578d9bd_0.tar.bz2#0d78be9cf1c400ba8e3077cf060492f1 https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 +https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a -https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-1.0.0-pyhd8ed1ab_0.tar.bz2#9f633f2f2869184e31acfeae95b24345 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_1.tar.bz2#63d2f874f990fdcab47c822b608d6ade diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index ef095815c9..91cd9d3f5f 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -44,4 +44,4 @@ dependencies: - sphinx-copybutton - sphinx-gallery - sphinx-panels - - sphinx_rtd_theme + - pydata-sphinx-theme diff --git a/tools/update_lockfiles.py b/tools/update_lockfiles.py index 9d5705c7a7..f05210be87 100755 --- a/tools/update_lockfiles.py +++ b/tools/update_lockfiles.py @@ -53,7 +53,8 @@ 'lock', '--filename-template', ofile_template, '--file', infile, + '-k', 'explicit', '--platform', 'linux-64' ]) print(f"lockfile saved to {ofile_template}".format(platform='linux-64'), - file=sys.stderr) \ No newline at end of file + file=sys.stderr) From 7820f10081634e51c6e5eb35b68e0a9af8d18e28 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 11 Apr 2022 13:36:03 +0100 Subject: [PATCH 070/319] Update percentile tests (#4684) * update percentile tests * review action --- .../tests/unit/analysis/test_PERCENTILE.py | 51 +++++++++++++++---- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/iris/tests/unit/analysis/test_PERCENTILE.py b/lib/iris/tests/unit/analysis/test_PERCENTILE.py index 1e4ba3af0f..d56d1ffb1f 100644 --- a/lib/iris/tests/unit/analysis/test_PERCENTILE.py +++ b/lib/iris/tests/unit/analysis/test_PERCENTILE.py @@ -11,6 +11,7 @@ from unittest import mock +import dask.array as da import numpy as np import numpy.ma as ma @@ -88,11 +89,12 @@ def test_2d_multi(self): self.check_percentile_calc(data, axis, percent, expected, approx=True) -class MaskedAggregateMixin: +class ScipyAggregateMixin: """ - Tests for calculations on masked data. Will only work if using the standard - (scipy) method. Needs to be used with AggregateMixin, as these tests re-use its - method. + Tests for calculations specific to the default (scipy) function. Includes + tests on masked data and tests to verify that the function is called with + the expected keywords. Needs to be used with AggregateMixin, as some of + these tests re-use its method. """ @@ -147,28 +149,40 @@ def test_masked_2d_multi(self): data, axis, percent, expected, mdtol=mdtol, approx=True ) - @mock.patch("scipy.stats.mstats.mquantiles") + @mock.patch("scipy.stats.mstats.mquantiles", return_value=[2, 4]) def test_default_kwargs_passed(self, mocked_mquantiles): data = np.arange(5) - percent = 50 + percent = [42, 75] axis = 0 + if self.lazy: + data = as_lazy_data(data) + self.agg_method(data, axis=axis, percent=percent) + + # Trigger calculation for lazy case. + as_concrete_data(data) for key in ["alphap", "betap"]: self.assertEqual(mocked_mquantiles.call_args.kwargs[key], 1) @mock.patch("scipy.stats.mstats.mquantiles") def test_chosen_kwargs_passed(self, mocked_mquantiles): data = np.arange(5) - percent = 50 + percent = [42, 75] axis = 0 + if self.lazy: + data = as_lazy_data(data) + self.agg_method( data, axis=axis, percent=percent, alphap=0.6, betap=0.5 ) + + # Trigger calculation for lazy case. + as_concrete_data(data) for key, val in zip(["alphap", "betap"], [0.6, 0.5]): self.assertEqual(mocked_mquantiles.call_args.kwargs[key], val) -class Test_aggregate(tests.IrisTest, AggregateMixin, MaskedAggregateMixin): +class Test_aggregate(tests.IrisTest, AggregateMixin, ScipyAggregateMixin): """Tests for standard aggregation method on real data.""" def setUp(self): @@ -200,6 +214,13 @@ def test_masked(self): data, axis=0, percent=50, fast_percentile_method=True ) + @mock.patch("numpy.percentile") + def test_numpy_percentile_called(self, mocked_percentile): + # Basic check that numpy.percentile is called. + data = np.arange(5) + self.agg_method(data, axis=0, percent=42, fast_percentile_method=True) + mocked_percentile.assert_called_once() + class MultiAxisMixin: """ @@ -269,9 +290,21 @@ def test_masked(self): with self.assertRaisesRegex(TypeError, emsg): as_concrete_data(actual) + @mock.patch("numpy.percentile", return_value=np.array([2, 4])) + def test_numpy_percentile_called(self, mocked_percentile): + # Basic check that numpy.percentile is called. + data = da.arange(5) + result = self.agg_method( + data, axis=0, percent=[42, 75], fast_percentile_method=True + ) + + self.assertTrue(is_lazy_data(result)) + as_concrete_data(result) + mocked_percentile.assert_called() + class Test_lazy_aggregate( - tests.IrisTest, AggregateMixin, MaskedAggregateMixin, MultiAxisMixin + tests.IrisTest, AggregateMixin, ScipyAggregateMixin, MultiAxisMixin ): """Tests for standard aggregation on lazy data.""" From 75e2dc4f4fe0e973d58ba493b1a4ca5b947ccd97 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:46:32 +0100 Subject: [PATCH 071/319] Updated environment lockfiles (#4685) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index c91683f0cf..4d5deb9665 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -9,15 +9,15 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed3 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_14.tar.bz2#f8b51fb7bccd48f959982973df578165 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_14.tar.bz2#c3b55849172e4bfa01d3349ea8e73a3a +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_15.tar.bz2#7c2c0cd937b707aa46f3369d693a1f29 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_15.tar.bz2#4ffda9c4352880439042fbb8a9863e8c https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_14.tar.bz2#67328431d4aeab35e5a2c6c22669c22b -https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_14.tar.bz2#a77fb1a92411cb8d979de1c2d81dd210 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_15.tar.bz2#23b9afaf5975e1db0f59d1251425f5d5 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_15.tar.bz2#5b7bff215ad9f30325dbe86dd983cb49 https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2#561e277319a41d4f24f5c05a9ef63c04 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_14.tar.bz2#47e6c01d149b26090748d9d1ac32491b +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_15.tar.bz2#8041e476e66fcde71f0aa3d614e0efcf https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a @@ -46,8 +46,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h166bdaf_1014.tar.bz2#757138ba3ddc6777b82e91d9ff62e7b9 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.1-h846660c_100.tar.bz2#4b85205b094808088bb0862e08251653 -https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h9c3ff4c_0.tar.bz2#fb31bcb7af058244479ca635d20f0f4a +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1n-h166bdaf_0.tar.bz2#cf0ddbd911fa547e6bc4291ca5e17379 https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa @@ -145,13 +145,14 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.t https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e -https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.10.0-pyha770c72_0.tar.bz2#c29e0b4a2ff546cedbe6dc2921948a1d +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.0-pyha770c72_0.tar.bz2#288499a51ef16105a74228b9762836f1 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a @@ -170,7 +171,7 @@ https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_0.tar.bz2#12eaa8cbfedfbf7879e5653467b03c94 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.0.0-py38h578d9bd_0.tar.bz2#137bed0dfd03cc5a762978b46dc021e3 @@ -181,7 +182,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.0-pyhd8ed1ab_0.tar.bz2#a47051950a34bef40d8369a5f320b78d -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.31.2-py38h0a891b7_1.tar.bz2#46b9f0b776c532c038147915f8fb1e38 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.32.0-py38h0a891b7_0.tar.bz2#a6ad9659120d21c3ab44a1c96b1ebee1 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.0-h40b6f09_0.tar.bz2#017b20e7e98860f0bfa7492ce16390a7 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.1-pyhd8ed1ab_0.tar.bz2#40b3f446c61729cdd21ed9d85627df6e https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef @@ -197,7 +198,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h3ec907f_0. https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h596eeab_5.tar.bz2#ec3b783081e14a9dc0eb5ce609649728 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 -https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h1fd1430_1.tar.bz2#c494f75082f9c052944fda1b22c83336 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.4.12-pyhd8ed1ab_0.tar.bz2#9f7b07b3f40905fcf600aacf63ae0c41 @@ -213,15 +214,14 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h91476 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_3.tar.bz2#a08562889b985d021550e22443cf0fce https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.18.1-py38h578d9bd_0.tar.bz2#b590f730888ff2615587968a73248761 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.18.1-py38h578d9bd_1.tar.bz2#026355c0547c2ae491721278f8c90200 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.1-py38h578d9bd_0.tar.bz2#0d78be9cf1c400ba8e3077cf060492f1 https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 -https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 +https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_1.tar.bz2#63d2f874f990fdcab47c822b608d6ade From 2b30041161c17d4e7d320f018854433bf6d257b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:50:39 +0100 Subject: [PATCH 072/319] Bump actions/download-artifact from 2 to 3 (#4686) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 3. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index c48a631bbd..ac98bc9e10 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -61,7 +61,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: get artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v3 with: path: artifacts From 1062b5b3e45d137d9b2df76f86684f0c99dc6e52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:51:12 +0100 Subject: [PATCH 073/319] Bump actions/upload-artifact from 2 to 3 (#4687) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/benchmark.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index fddeffc26d..6237f6ffff 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -115,7 +115,7 @@ jobs: - name: Archive asv results if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: asv-report path: | diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index ac98bc9e10..91f5ca86b9 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -47,7 +47,7 @@ jobs: $CONDA/bin/conda-lock lock -k explicit -p linux-64 -f requirements/ci/py${{matrix.python}}.yml mv conda-linux-64.lock py${{matrix.python}}-linux-64.lock - name: output lockfile - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: path: py${{matrix.python}}-linux-64.lock From d282a0ccbb856d98d503e139c780c76e79d80999 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 10:51:37 +0100 Subject: [PATCH 074/319] Bump peter-evans/create-pull-request from 4.0.1 to 4.0.2 (#4688) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/f1a7646cead32c950d90344a4fb5d4e926972a8f...bd72e1b7922d417764d27d30768117ad7da78a0e) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 91f5ca86b9..dcc4dbbdc0 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -72,7 +72,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@f1a7646cead32c950d90344a4fb5d4e926972a8f + uses: peter-evans/create-pull-request@bd72e1b7922d417764d27d30768117ad7da78a0e with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From a29a02035b0b14bf9d83c98af0c2a66f64e59bc4 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Tue, 12 Apr 2022 14:44:27 +0100 Subject: [PATCH 075/319] Add dynamic matrix to refresh lockfile job (#4573) * Add dynamic matrix to refresh lockfile job * review actions --- .github/workflows/refresh-lockfiles.yml | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index dcc4dbbdc0..54d708104e 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -23,19 +23,29 @@ on: jobs: + get_python_matrix: + # Determines which Python versions should be included in the matrix used in + # the gen_lockfiles job. + if: "github.repository == 'SciTools/iris'" + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.get_py.outputs.matrix }} + steps: + - uses: actions/checkout@v3 + - id: get_py + run: echo "::set-output name=matrix::$(ls -1 requirements/ci/py??.yml | xargs -n1 basename | sed 's/....$//' | jq -cnR '[inputs]')" + gen_lockfiles: # this is a matrix job: it splits to create new lockfiles for each # of the CI test python versions. - # this list below should be changed when covering more python versions - # TODO: generate this matrix automatically from the list of available py**.yml files - # ref: https://tomasvotruba.com/blog/2020/11/16/how-to-make-dynamic-matrix-in-github-actions/ if: "github.repository == 'SciTools/iris'" runs-on: ubuntu-latest + needs: get_python_matrix strategy: matrix: - python: ['38'] - + python: ${{ fromJSON(needs.get_python_matrix.outputs.matrix) }} + steps: - uses: actions/checkout@v3 - name: install conda-lock @@ -44,12 +54,12 @@ jobs: conda install -y -c conda-forge conda-lock - name: generate lockfile run: | - $CONDA/bin/conda-lock lock -k explicit -p linux-64 -f requirements/ci/py${{matrix.python}}.yml - mv conda-linux-64.lock py${{matrix.python}}-linux-64.lock + $CONDA/bin/conda-lock lock -k explicit -p linux-64 -f requirements/ci/${{matrix.python}}.yml + mv conda-linux-64.lock ${{matrix.python}}-linux-64.lock - name: output lockfile uses: actions/upload-artifact@v3 with: - path: py${{matrix.python}}-linux-64.lock + path: ${{matrix.python}}-linux-64.lock create_pr: # once the matrix job has completed all the lock files will have been uploaded as artifacts. From b56f7819564a618bfd9738bd54070054939df42c Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Wed, 13 Apr 2022 10:53:55 +0100 Subject: [PATCH 076/319] added link check and ensured https use (#4694) --- docs/src/conf.py | 2 ++ docs/src/developers_guide/gitwash/development_workflow.rst | 6 +++--- docs/src/developers_guide/gitwash/forking.rst | 2 +- docs/src/developers_guide/gitwash/git_links.inc | 4 ++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 0af6ec072f..348e7057fb 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -347,6 +347,8 @@ def _dotv(version): "http://cfconventions.org", "http://code.google.com/p/msysgit/downloads/list", "http://effbot.org", + "https://help.github.com", + "https://docs.github.com", "https://github.com", "http://www.personal.psu.edu/cab38/ColorBrewer/ColorBrewer_updates.html", "http://schacon.github.com/git", diff --git a/docs/src/developers_guide/gitwash/development_workflow.rst b/docs/src/developers_guide/gitwash/development_workflow.rst index 6e74e8546d..b086922d5b 100644 --- a/docs/src/developers_guide/gitwash/development_workflow.rst +++ b/docs/src/developers_guide/gitwash/development_workflow.rst @@ -157,7 +157,7 @@ Ask for Your Changes to be Reviewed or Merged When you are ready to ask for someone to review your code and consider a merge: #. Go to the URL of your forked repo, say - ``http://github.com/your-user-name/iris``. + ``https://github.com/your-user-name/iris``. #. Use the 'Switch Branches' dropdown menu near the top left of the page to select the branch with your changes: @@ -190,7 +190,7 @@ Delete a Branch on Github git push origin :my-unwanted-branch Note the colon ``:`` before ``test-branch``. See also: -http://github.com/guides/remove-a-remote-branch +https://github.com/guides/remove-a-remote-branch Several People Sharing a Single Repository @@ -203,7 +203,7 @@ share it via github. First fork iris into your account, as from :ref:`forking`. Then, go to your forked repository github page, say -``http://github.com/your-user-name/iris``, select :guilabel:`Settings`, +``https://github.com/your-user-name/iris``, select :guilabel:`Settings`, :guilabel:`Manage Access` and then :guilabel:`Invite collaborator`. .. note:: For more information on sharing your repository see the diff --git a/docs/src/developers_guide/gitwash/forking.rst b/docs/src/developers_guide/gitwash/forking.rst index 161847ed79..247e3cf678 100644 --- a/docs/src/developers_guide/gitwash/forking.rst +++ b/docs/src/developers_guide/gitwash/forking.rst @@ -7,7 +7,7 @@ Making Your own Copy (fork) of Iris =================================== You need to do this only once. The instructions here are very similar -to the instructions at http://help.github.com/forking/, please see +to the instructions at https://help.github.com/forking/, please see that page for more detail. We're repeating some of it here just to give the specifics for the `Iris`_ project, and to suggest some default names. diff --git a/docs/src/developers_guide/gitwash/git_links.inc b/docs/src/developers_guide/gitwash/git_links.inc index 9a87b55d4d..11d037ccf4 100644 --- a/docs/src/developers_guide/gitwash/git_links.inc +++ b/docs/src/developers_guide/gitwash/git_links.inc @@ -9,8 +9,8 @@ nipy, NIPY, Nipy, etc... .. _git: http://git-scm.com/ -.. _github: http://github.com -.. _github help: http://help.github.com +.. _github: https://github.com +.. _github help: https://help.github.com .. _git documentation: https://git-scm.com/docs .. _git clone: http://schacon.github.com/git/git-clone.html From 732800d39d55ae692cd095937b1dd7d3ed401015 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 13 Apr 2022 11:01:17 +0100 Subject: [PATCH 077/319] [pre-commit.ci] pre-commit autoupdate (#4691) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8ce1a3cd28..c2563497de 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ minimum_pre_commit_version: 1.21.0 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: # Prevent giant files from being committed. - id: check-added-large-files From 478fa635a043da138a2f12905c7359739d94ff29 Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Wed, 13 Apr 2022 12:15:41 +0100 Subject: [PATCH 078/319] Comment rendering (#4689) * used numpy docstrings to fix docs rendering * comment rejig * apply numpydoc strings * corrected notes * updated docstring guidance. * added explicit mention of numpydoc strings * added whatsnew * added whatsnew PR ref * Update lib/iris/fileformats/nimrod_load_rules.py Co-authored-by: Will Benfold <69585101+wjbenfold@users.noreply.github.com> * Update docs/src/whatsnew/latest.rst Co-authored-by: Will Benfold <69585101+wjbenfold@users.noreply.github.com> * review actions. Co-authored-by: Will Benfold <69585101+wjbenfold@users.noreply.github.com> --- .../contributing_pull_request_checklist.rst | 3 +- .../documenting/docstrings.rst | 99 +----- docs/src/whatsnew/latest.rst | 4 + lib/iris/fileformats/dot.py | 87 +++-- lib/iris/fileformats/nimrod_load_rules.py | 60 ++-- lib/iris/fileformats/pp.py | 316 +++++++++--------- 6 files changed, 262 insertions(+), 307 deletions(-) diff --git a/docs/src/developers_guide/contributing_pull_request_checklist.rst b/docs/src/developers_guide/contributing_pull_request_checklist.rst index 524b5d2c17..3ae3784a1d 100644 --- a/docs/src/developers_guide/contributing_pull_request_checklist.rst +++ b/docs/src/developers_guide/contributing_pull_request_checklist.rst @@ -34,7 +34,8 @@ is merged. Before submitting a pull request please consider this list. should be generated too, see :ref:`cirrus_test_env`. #. **Check the source documentation been updated to explain all new or changed - features**. See :ref:`docstrings`. + features**. Note, we now use numpydoc strings. Any touched code should + be updated to use the docstrings formatting. See :ref:`docstrings`. #. **Include code examples inside the docstrings where appropriate**. See :ref:`contributing.documentation.testing`. diff --git a/docs/src/developers_guide/documenting/docstrings.rst b/docs/src/developers_guide/documenting/docstrings.rst index ef0679aac0..eeefc71e40 100644 --- a/docs/src/developers_guide/documenting/docstrings.rst +++ b/docs/src/developers_guide/documenting/docstrings.rst @@ -8,10 +8,10 @@ Every public object in the Iris package should have an appropriate docstring. This is important as the docstrings are used by developers to understand the code and may be read directly in the source or via the :ref:`Iris`. -This document has been influenced by the following PEP's, - -* Attribute Docstrings :pep:`224` -* Docstring Conventions :pep:`257` +.. note:: + As of April 2022 we are looking to adopt `numpydoc`_ strings as standard. + We aim to complete the adoption over time as we do changes to the codebase. + For examples of use see `numpydoc`_ and `sphinxcontrib-napoleon`_ For consistency always use: @@ -20,91 +20,14 @@ For consistency always use: docstrings. * ``u"""Unicode triple-quoted string"""`` for Unicode docstrings -All docstrings should be written in reST (reStructuredText) markup. See the -:ref:`reST_quick_start` for more detail. - -There are two forms of docstrings: **single-line** and **multi-line** -docstrings. - - -Single-Line Docstrings -====================== - -The single line docstring of an object must state the **purpose** of that -object, known as the **purpose section**. This terse overview must be on one -line and ideally no longer than 80 characters. - - -Multi-Line Docstrings -===================== - -Multi-line docstrings must consist of at least a purpose section akin to the -single-line docstring, followed by a blank line and then any other content, as -described below. The entire docstring should be indented to the same level as -the quotes at the docstring's first line. - - -Description ------------ - -The multi-line docstring *description section* should expand on what was -stated in the one line *purpose section*. The description section should try -not to document *argument* and *keyword argument* details. Such information -should be documented in the following *arguments and keywords section*. - - -Sample Multi-Line Docstring ---------------------------- - -Here is a simple example of a standard docstring: - -.. literalinclude:: docstrings_sample_routine.py - -This would be rendered as: - - .. currentmodule:: documenting.docstrings_sample_routine - - .. automodule:: documenting.docstrings_sample_routine - :members: - :undoc-members: - -Additionally, a summary can be extracted automatically, which would result in: - - .. autosummary:: - - documenting.docstrings_sample_routine.sample_routine - - -Documenting Classes -=================== - -The class constructor should be documented in the docstring for its -``__init__`` or ``__new__`` method. Methods should be documented by their own -docstring, not in the class header itself. - -If a class subclasses another class and its behaviour is mostly inherited from -that class, its docstring should mention this and summarise the differences. -Use the verb "override" to indicate that a subclass method replaces a -superclass method and does not call the superclass method; use the verb -"extend" to indicate that a subclass method calls the superclass method -(in addition to its own behaviour). - - -Attribute and Property Docstrings ---------------------------------- - -Here is a simple example of a class containing an attribute docstring and a -property docstring: - -.. literalinclude:: docstrings_attribute.py +All docstrings can use reST (reStructuredText) markup to augment the +rendered formatting. See the :ref:`reST_quick_start` for more detail. -This would be rendered as: +For more information including examples pleasee see: - .. currentmodule:: documenting.docstrings_attribute +* `numpydoc`_ +* `sphinxcontrib-napoleon`_ - .. automodule:: documenting.docstrings_attribute - :members: - :undoc-members: -.. note:: The purpose section of the property docstring **must** state whether - the property is read-only. +.. _numpydoc: https://numpydoc.readthedocs.io/en/latest/format.html#style-guide +.. _sphinxcontrib-napoleon: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_numpy.html \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 52313d5d81..8f3bee23df 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -114,6 +114,10 @@ This document explains the changes made to Iris for this release #. `@tkknight`_ adopted the `PyData Sphinx Theme`_ for the documentation. (:discussion:`4344`, :pull:`4661`) +#. `@tkknight`_ updated our developers guidance to show our intent to adopt + numpydoc strings and fixed some API documentation rendering. + See :ref:`docstrings`. (:issue:`4657`, :pull:`4689`) + 💼 Internal =========== diff --git a/lib/iris/fileformats/dot.py b/lib/iris/fileformats/dot.py index 2fb628bebf..50c02e4d04 100644 --- a/lib/iris/fileformats/dot.py +++ b/lib/iris/fileformats/dot.py @@ -59,14 +59,18 @@ def _dot_path(): def save(cube, target): - """Save a dot representation of the cube. - - Args: + """ + Save a dot representation of the cube. - * cube - A :class:`iris.cube.Cube`. - * target - A filename or open file handle. + Args + ---- + cube: :class:`iris.cube.Cube`. + target + A filename or open file handle. - See also :func:`iris.io.save`. + See Also + -------- + :func:`iris.io.save`. """ if isinstance(target, str): @@ -87,19 +91,23 @@ def save(cube, target): def save_png(source, target, launch=False): """ - Produces a "dot" instance diagram by calling dot and optionally launching the resulting image. - - Args: + Produce a "dot" instance diagram by calling dot and optionally launching + the resulting image. - * source - A :class:`iris.cube.Cube`, or dot filename. - * target - A filename or open file handle. - If passing a file handle, take care to open it for binary output. + Args + ---- + source: :class:`iris.cube.Cube`, or dot filename. + target + A filename or open file handle. + If passing a file handle, take care to open it for binary output. - Kwargs: + **kwargs + * launch + Display the image. Default is False. - * launch - Display the image. Default is False. - - See also :func:`iris.io.save`. + See Also + -------- + :func:`iris.io.save`. """ # From cube or dot file? @@ -152,11 +160,13 @@ def save_png(source, target, launch=False): def cube_text(cube): - """Return a DOT text representation a `iris.cube.Cube`. - - Args: + """ + Return a DOT text representation a `iris.cube.Cube`. - * cube - The cube for which to create DOT text. + Args + ---- + cube + The cube for which to create DOT text. """ # We use r'' type string constructor as when we type \n in a string without the r'' constructor @@ -283,13 +293,14 @@ def cube_text(cube): def _coord_text(label, coord): """ - Returns a string containing the dot representation for a single coordinate node. - - Args: + Return a string containing the dot representation for a single coordinate + node. - * label + Args + ---- + label The dot ID of the coordinate node. - * coord + coord The coordinate to convert. """ @@ -315,14 +326,16 @@ def _coord_text(label, coord): def _coord_system_text(cs, uid): """ - Returns a string containing the dot representation for a single coordinate system node. + Return a string containing the dot representation for a single coordinate + system node. - Args: - - * cs + Args + ---- + cs The coordinate system to convert. - * uid - The uid allows/distinguishes non-identical CoordSystems of the same type. + uid + The uid allows/distinguishes non-identical CoordSystems of the same + type. """ attrs = [] @@ -341,15 +354,15 @@ def _coord_system_text(cs, uid): def _dot_node(indent, id, name, attributes): """ - Returns a string containing the dot representation for a single node. - - Args: + Return a string containing the dot representation for a single node. - * id + Args + ---- + id The ID of the node. - * name + name The visual name of the node. - * attributes + attributes An iterable of (name, value) attribute pairs. """ diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index b0e0f6e864..5d8bb11c48 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -33,7 +33,7 @@ class TranslationWarning(Warning): def is_missing(field, value): - """Returns True if value matches an "is-missing" number.""" + """Return True if value matches an "is-missing" number.""" return any( np.isclose(value, [field.int_mdi, field.float32_mdi, NIMROD_DEFAULT]) ) @@ -86,7 +86,8 @@ def name(cube, field, handle_metadata_errors): def remove_unprintable_chars(input_str): """ - Removes unprintable characters from a string and returns the result. + Remove unprintable characters from a string and return the result. + """ return "".join( c if c in string.printable else " " for c in input_str @@ -278,7 +279,7 @@ def forecast_period(cube): def mask_cube(cube, field): """ - Updates cube.data to be a masked array if appropriate. + Update cube.data to be a masked array if appropriate. """ dtype = cube.dtype @@ -307,16 +308,17 @@ def experiment(cube, field): def proj_biaxial_ellipsoid(field, handle_metadata_errors): """ - Returns the correct dictionary of arguments needed to define an + Return the correct dictionary of arguments needed to define an iris.coord_systems.GeogCS. Based firstly on the value given by ellipsoid, then by grid if ellipsoid is missing, select the right pre-defined ellipsoid dictionary (Airy_1830 or international_1924). - References: - Airy 1830: https://georepository.com/ellipsoid_7001/Airy-1830.html - International 1924: https://georepository.com/ellipsoid_7022/International-1924.html + References + ---------- + Airy 1830: https://georepository.com/ellipsoid_7001/Airy-1830.html + International 1924: https://georepository.com/ellipsoid_7022/International-1924.html """ airy_1830 = { @@ -357,10 +359,12 @@ def proj_biaxial_ellipsoid(field, handle_metadata_errors): def set_british_national_grid_defaults(field, handle_metadata_errors): - """Check for missing coord-system meta-data and set default values for + """ + Check for missing coord-system meta-data and set default values for the Ordnance Survey GB Transverse Mercator projection. Some Radarnet - files are missing these.""" + files are missing these. + """ if handle_metadata_errors: if is_missing(field, field.true_origin_latitude): field.true_origin_latitude = 49.0 @@ -472,8 +476,12 @@ def horizontal_grid(cube, field, handle_metadata_errors): def vertical_coord(cube, field): - """Add a vertical coord to the cube, with bounds, if appropriate. - Handles special numbers for "at-sea-level" (8888) and "at-ground-level" (9999).""" + """ + Add a vertical coord to the cube, with bounds, if appropriate. + Handles special numbers for "at-sea-level" (8888) and "at-ground-level" + (9999). + + """ # vertical_codes contains conversions from the Nimrod Documentation for the # header entry 20 for the vertical coordinate type # Unhandled vertical_codes values (no use case identified): @@ -670,7 +678,10 @@ def add_attr(item): def known_threshold_coord(field): """ Supplies known threshold coord meta-data for known use cases. - threshold_value_alt exists because some meta-data are mis-assigned in the Nimrod data. + + threshold_value_alt exists because some meta-data are mis-assigned in the + Nimrod data. + """ coord_keys = {} if ( @@ -715,9 +726,11 @@ def known_threshold_coord(field): def probability_coord(cube, field, handle_metadata_errors): """ - Adds a coord relating to probability meta-data from the header to the + Add a coord relating to probability meta-data from the header to the cube if appropriate. + Must be run after the name method. + """ probtype_lookup = { 1: { @@ -848,7 +861,7 @@ def probability_coord(cube, field, handle_metadata_errors): def soil_type_coord(cube, field): - """Add soil type as a coord if appropriate""" + """Add soil type as a coord if appropriate.""" soil_type_codes = { 1: "broadleaf_tree", 2: "needleleaf_tree", @@ -905,17 +918,18 @@ def run(field, handle_metadata_errors=True): """ Convert a NIMROD field to an Iris cube. - Args: - - * field - a :class:`~iris.fileformats.nimrod.NimrodField` - - * handle_metadata_errors - Set to False to omit handling of known meta-data deficiencies - in Nimrod-format data - - Returns: + Args + ---- + field: :class:`~iris.fileformats.nimrod.NimrodField` - * A new :class:`~iris.cube.Cube`, created from the NimrodField. + handle_metadata_errors + Set to False to omit handling of known meta-data deficiencies + in Nimrod-format data + Returns + ------- + :class:`~iris.cube.Cube` + A new :class:`~iris.cube.Cube`, created from the NimrodField. """ cube = iris.cube.Cube(field.data) diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 9bda98bf61..e2958382cb 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -253,14 +253,13 @@ class STASH(collections.namedtuple("STASH", "model section item")): def __new__(cls, model, section, item): """ - - Args: - - * model + Args + ---- + model A positive integer less than 100, or None. - * section + section A non-negative integer less than 100, or None. - * item + item A positive integer less than 1000, or None. """ @@ -350,7 +349,9 @@ class SplittableInt: >>> print(three_six_two[2]) 3 - .. note:: No support for negative numbers + Notes + ----- + No support for negative numbers """ @@ -358,11 +359,12 @@ def __init__(self, value, name_mapping_dict=None): """ Build a SplittableInt given the positive integer value provided. - Kwargs: - - * name_mapping_dict - (dict) - A special mapping to provide name based access to specific integer - positions: + Args + ---- + **kwargs + * name_mapping_dict - (dict) + A special mapping to provide name based access to specific + integer positions: >>> a = SplittableInt(1234, {'hundreds': 2}) >>> print(a.hundreds) @@ -373,6 +375,7 @@ def __init__(self, value, name_mapping_dict=None): >>> print(a) 1934 + """ if value < 0: raise ValueError( @@ -789,7 +792,7 @@ def _data_bytes_to_shaped_array( def _header_defn(release_number): """ - Returns the zero-indexed header definition for a particular release of + Return the zero-indexed header definition for a particular release of a PPField. """ @@ -803,7 +806,7 @@ def _header_defn(release_number): def _pp_attribute_names(header_defn): """ - Returns the allowed attributes of a PPField: + Return the allowed attributes of a PPField: all of the normal headers (i.e. not the _SPECIAL_HEADERS), the _SPECIAL_HEADERS with '_' prefixed, the possible extra data headers. @@ -860,7 +863,7 @@ def __init__(self, header=None): def __getattr__(self, key): """ - This method supports deferred attribute creation, which offers a + Method supports deferred attribute creation, which offers a significant loading optimisation, particularly when not all attributes are referenced and therefore created on the instance. @@ -922,7 +925,6 @@ def t2(self): def __repr__(self): """Return a string representation of the PP field.""" - # Define an ordering on the basic header names attribute_priority_lookup = { name: loc[0] for name, loc in self.HEADER_DEFN @@ -960,7 +962,7 @@ def __repr__(self): @property def stash(self): """ - A stash property giving access to the associated STASH object, + Stash property giving access to the associated STASH object, now supporting __eq__ """ @@ -1044,7 +1046,7 @@ def lbproc(self, value): @property def data(self): """ - The :class:`numpy.ndarray` representing the multidimensional data + :class:`numpy.ndarray` representing the multidimensional data of the pp file """ @@ -1075,7 +1077,6 @@ def _read_extra_data( self, pp_file, file_reader, extra_len, little_ended=False ): """Read the extra data section and update the self appropriately.""" - dtype_endian_char = "<" if little_ended else ">" # While there is still extra data to decode run this loop while extra_len > 0: @@ -1124,10 +1125,8 @@ def y_bounds(self): def save(self, file_handle): """ - Save the PPField to the given file object - (typically created with :func:`open`). - - :: + Save the PPField to the given file object. + (typically created with :func:`open`):: # to append the field to a file with open(filename, 'ab') as fh: @@ -1137,15 +1136,13 @@ def save(self, file_handle): with open(filename, 'wb') as fh: a_pp_field.save(fh) - - .. note:: - - The fields which are automatically calculated are: 'lbext', - 'lblrec' and 'lbuser[0]'. Some fields are not currently - populated, these are: 'lbegin', 'lbnrec', 'lbuser[1]'. + Notes + ----- + The fields which are automatically calculated are: 'lbext', + 'lblrec' and 'lbuser[0]'. Some fields are not currently + populated, these are: 'lbegin', 'lbnrec', 'lbuser[1]'. """ - # Get the actual data content. data = self.data mdi = self.bmdi @@ -1361,9 +1358,9 @@ def time_unit(self, time_unit, epoch="epoch"): def coord_system(self): """Return a CoordSystem for this PPField. - Returns: - Currently, a :class:`~iris.coord_systems.GeogCS` or - :class:`~iris.coord_systems.RotatedGeogCS`. + Returns + ------- + :class:`~iris.coord_systems.GeogCS` or class:`~iris.coord_systems.RotatedGeogCS`. """ geog_cs = iris.coord_systems.GeogCS(EARTH_RADIUS) @@ -1408,9 +1405,11 @@ def _y_coord_name(self): def copy(self): """ - Returns a deep copy of this PPField. + Return a deep copy of this PPField. - Returns: + Returns + ------- + :class:`PPField`: A copy instance of the :class:`PPField`. """ @@ -1470,7 +1469,7 @@ class PPField2(PPField): @property def t1(self): """ - A cftime.datetime object consisting of the lbyr, lbmon, lbdat, lbhr, + cftime.datetime object consisting of the lbyr, lbmon, lbdat, lbhr, and lbmin attributes. """ @@ -1504,7 +1503,7 @@ def t1(self, dt): @property def t2(self): """ - A cftime.datetime object consisting of the lbyrd, lbmond, lbdatd, + cftime.datetime object consisting of the lbyrd, lbmond, lbdatd, lbhrd, and lbmind attributes. """ @@ -1551,7 +1550,7 @@ class PPField3(PPField): @property def t1(self): """ - A cftime.datetime object consisting of the lbyr, lbmon, lbdat, lbhr, + cftime.datetime object consisting of the lbyr, lbmon, lbdat, lbhr, lbmin, and lbsec attributes. """ @@ -1586,7 +1585,7 @@ def t1(self, dt): @property def t2(self): """ - A cftime.datetime object consisting of the lbyrd, lbmond, lbdatd, + cftime.datetime object consisting of the lbyrd, lbmond, lbdatd, lbhrd, lbmind, and lbsecd attributes. """ @@ -1638,20 +1637,20 @@ def load(filename, read_data=False, little_ended=False): """ Return an iterator of PPFields given a filename. - Args: - - * filename - string of the filename to load. - - Kwargs: - - * read_data - boolean - Flag whether or not the data should be read, if False an empty - data manager will be provided which can subsequently load the data - on demand. Default False. - - * little_ended - boolean - If True, file contains all little-ended words (header and data). - + Args + ---- + filename + string of the filename to load. + **kwargs + * read_data - boolean + Flag whether or not the data should be read, if False an empty + data manager will be provided which can subsequently load the data + on demand. Default False. + * little_ended - boolean + If True, file contains all little-ended words (header and data). + + Notes + ----- To iterate through all of the fields in a pp file:: for field in iris.fileformats.pp.load(filename): @@ -1737,7 +1736,7 @@ def _interpret_fields(fields): def _create_field_data(field, data_shape, land_mask_field=None): """ - Modifies a field's ``_data`` attribute either by: + Modify a field's ``_data`` attribute either by: * converting a 'deferred array bytes' tuple into a lazy array, * converting LoadedArrayBytes into an actual numpy array. @@ -1834,7 +1833,7 @@ def calc_array(mask, values): def _field_gen(filename, read_data_bytes, little_ended=False): """ - Returns a generator of "half-formed" PPField instances derived from + Return a generator of "half-formed" PPField instances derived from the given filename. A field returned by the generator is only "half-formed" because its @@ -1966,7 +1965,7 @@ def _field_gen(filename, read_data_bytes, little_ended=False): def _convert_constraints(constraints): """ - Converts known constraints from Iris semantics to PP semantics + Convert known constraints from Iris semantics to PP semantics ignoring all unknown constraints. """ @@ -1976,8 +1975,9 @@ def _convert_constraints(constraints): def _make_func(stashobj): """ - Provides unique name-space for each lambda function's stashobj + Provide unique name-space for each lambda function's stashobj variable. + """ return lambda stash: stash == stashobj @@ -2010,7 +2010,7 @@ def _make_func(stashobj): def pp_filter(field): """ - return True if field is to be kept, + Return True if field is to be kept, False if field does not match filter """ @@ -2035,24 +2035,23 @@ def pp_filter(field): def load_cubes(filenames, callback=None, constraints=None): """ - Loads cubes from a list of pp filenames. - - Args: - - * filenames - list of pp filenames to load - - Kwargs: - - * constraints - a list of Iris constraints - - * callback - a function which can be passed on to - :func:`iris.io.run_callback` - - .. note:: - - The resultant cubes may not be in the order that they are in the file - (order is not preserved when there is a field with orography - references) + Load cubes from a list of pp filenames. + + Args + ---- + filenames + list of pp filenames to load + **kwargs + * constraints + a list of Iris constraints + * callback + a function which can be passed on to :func:`iris.io.run_callback` + + Notes + ----- + The resultant cubes may not be in the order that they are in the file + (order is not preserved when there is a field with orography + references) """ return _load_cubes_variable_loader( @@ -2062,24 +2061,23 @@ def load_cubes(filenames, callback=None, constraints=None): def load_cubes_little_endian(filenames, callback=None, constraints=None): """ - Loads cubes from a list of pp filenames containing little-endian data. - - Args: - - * filenames - list of pp filenames to load - - Kwargs: - - * constraints - a list of Iris constraints - - * callback - a function which can be passed on to - :func:`iris.io.run_callback` - - .. note:: - - The resultant cubes may not be in the order that they are in the file - (order is not preserved when there is a field with orography - references) + Load cubes from a list of pp filenames containing little-endian data. + + Args + ---- + filenames + list of pp filenames to load + **kwargs + * constraints + a list of Iris constraints + * callback + a function which can be passed on to :func:`iris.io.run_callback` + + Notes + ----- + The resultant cubes may not be in the order that they are in the file + (order is not preserved when there is a field with orography + references) """ return _load_cubes_variable_loader( @@ -2096,14 +2094,18 @@ def load_pairs_from_fields(pp_fields): Convert an iterable of PP fields into an iterable of tuples of (Cubes, PPField). - Args: - - * pp_fields: + Args + ---- + pp_fields: An iterable of :class:`iris.fileformats.pp.PPField`. - Returns: + Returns + ------- + :class:`iris.cube.Cube` An iterable of :class:`iris.cube.Cube`\ s. + Notes + ----- This capability can be used to filter out fields before they are passed to the load pipeline, and amend the cubes once they are created, using PP metadata conditions. Where this filtering @@ -2192,26 +2194,30 @@ def save(cube, target, append=False, field_coords=None): """ Use the PP saving rules (and any user rules) to save a cube to a PP file. - Args: + Args + ---- + cube: :class:`iris.cube.Cube` - * cube - A :class:`iris.cube.Cube` - * target - A filename or open file handle. - - Kwargs: - - * append - Whether to start a new file afresh or add the cube(s) - to the end of the file. - Only applicable when target is a filename, not a file - handle. - Default is False. - - * field_coords - list of 2 coords or coord names which are to be used - for reducing the given cube into 2d slices, - which will ultimately determine the x and y - coordinates of the resulting fields. - If None, the final two dimensions are chosen - for slicing. + target + A filename or open file handle. + **kwargs + * append + Whether to start a new file afresh or add the cube(s) + to the end of the file. + Only applicable when target is a filename, not a file + handle. + Default is False. + * field_coords + list of 2 coords or coord names which are to be used + for reducing the given cube into 2d slices, + which will ultimately determine the x and y + coordinates of the resulting fields. + If None, the final two dimensions are chosen + for slicing. + + Notes + ----- See also :func:`iris.io.save`. Note that :func:`iris.save` is the preferred method of saving. This allows a :class:`iris.cube.CubeList` or a sequence of cubes to be saved to a PP file. @@ -2226,21 +2232,18 @@ def save_pairs_from_cube(cube, field_coords=None, target=None): Use the PP saving rules to convert a cube or iterable of cubes to an iterable of (2D cube, PP field) pairs. - Args: - - * cube: + Args + ---- + cube: A :class:`iris.cube.Cube` - - Kwargs: - - * field_coords: - List of 2 coords or coord names which are to be used for - reducing the given cube into 2d slices, which will ultimately - determine the x and y coordinates of the resulting fields. - If None, the final two dimensions are chosen for slicing. - - * target: - A filename or open file handle. + **kwargs + * field_coords: + List of 2 coords or coord names which are to be used for + reducing the given cube into 2d slices, which will ultimately + determine the x and y coordinates of the resulting fields. + If None, the final two dimensions are chosen for slicing. + * target: + A filename or open file handle. """ # Open issues @@ -2348,21 +2351,18 @@ def as_fields(cube, field_coords=None, target=None): Use the PP saving rules (and any user rules) to convert a cube to an iterable of PP fields. - Args: - - * cube: + Args + ---- + cube A :class:`iris.cube.Cube` - - Kwargs: - - * field_coords: - List of 2 coords or coord names which are to be used for - reducing the given cube into 2d slices, which will ultimately - determine the x and y coordinates of the resulting fields. - If None, the final two dimensions are chosen for slicing. - - * target: - A filename or open file handle. + **kwargs + * field_coords: + List of 2 coords or coord names which are to be used for + reducing the given cube into 2d slices, which will ultimately + determine the x and y coordinates of the resulting fields. + If None, the final two dimensions are chosen for slicing. + * target: + A filename or open file handle. """ return ( @@ -2377,22 +2377,22 @@ def save_fields(fields, target, append=False): """ Save an iterable of PP fields to a PP file. - Args: - - * fields: + Args + ---- + fields: An iterable of PP fields. - * target: + target: A filename or open file handle. - - Kwargs: - - * append: - Whether to start a new file afresh or add the cube(s) to the end - of the file. - Only applicable when target is a filename, not a file handle. - Default is False. - - See also :func:`iris.io.save`. + **kwargs + * append: + Whether to start a new file afresh or add the cube(s) to the end + of the file. + Only applicable when target is a filename, not a file handle. + Default is False. + + See Also + -------- + :func:`iris.io.save`. """ # Open issues From 9e40513c5dfc0485550249b3c0c8aa54d3880cb0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 21 Apr 2022 12:25:22 +0100 Subject: [PATCH 079/319] Updated environment lockfiles (#4696) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 29 +++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 4d5deb9665..9482fff357 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -93,7 +93,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_3.tar.bz2#ce5af4269e1b874696380f6ace9a72d7 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.37.1-h4ff8645_0.tar.bz2#8057ac02d6d10a162d7eb4b0ca7ed291 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.2-h4ff8645_0.tar.bz2#9d4b7a876d5531fdbb682ed526f8b847 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 @@ -102,6 +102,7 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.b https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.1-hd4edc92_1.tar.bz2#ebebb56f78dd7518dcc94d6c26aa2249 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.82.0-h7bff187_0.tar.bz2#fa26f833ca8796ad44f9561c5402013d https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 @@ -133,7 +134,7 @@ https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.0-h93bde94_1.tar.bz2#cf908994f24ea526afc59f295d5b07c1 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff -https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.7-pyhd8ed1ab_0.tar.bz2#727e2216d9c47455d8ddc060eb2caad9 +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.8-pyhd8ed1ab_0.tar.bz2#7f5738c49fdccd0fc755bfd25a5ea66c https://conda.anaconda.org/conda-forge/noarch/pyshp-2.2.0-pyhd8ed1ab_0.tar.bz2#2aa546be05be34b8e1744afd327b623f https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 @@ -152,21 +153,21 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e -https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.0-pyha770c72_0.tar.bz2#288499a51ef16105a74228b9762836f1 +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_1.tar.bz2#8fb11f3e7218a51d26c4516fc6f53fea https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_1.tar.bz2#21862e22238907d485e460f9c2e9ae4d https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_1.tar.bz2#34c284cb94bd8c5118ccfe6d6fc3dd3f https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h05e7239_1.tar.bz2#0856f29eb084060e01a39813fed4dc92 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h1d589f8_2.tar.bz2#6aa7762a9c8d2280e284dc3df9b2fea8 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 +https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.2-py38h9776b28_0.tar.bz2#bd527d652ba06fb2aae61640bcf7c435 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 @@ -174,12 +175,12 @@ https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe -https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.0.0-py38h578d9bd_0.tar.bz2#137bed0dfd03cc5a762978b46dc021e3 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.1.0-py38h578d9bd_0.tar.bz2#201ea562e8f45f05a66f715f18f1b728 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.0-py38h578d9bd_1.tar.bz2#6c6a5da4d7e2af1dbbfb1e21daa559cc +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_0.tar.bz2#41427ff3fd8d35e5ab1cdcec4d94ea6b https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h3ec907f_0.tar.bz2#35411e5fc8dd523f9e68316847e6a25b +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.0-pyhd8ed1ab_0.tar.bz2#a47051950a34bef40d8369a5f320b78d https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.32.0-py38h0a891b7_0.tar.bz2#a6ad9659120d21c3ab44a1c96b1ebee1 @@ -188,15 +189,15 @@ https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.1-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_0.tar.bz2#52b2e1f0dbcba061f4d83c9879c9bd76 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_1.tar.bz2#0cdb1150994e0c449b25d6f92b69eda2 https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38hbc0797c_2.tar.bz2#7e4c695d10aa5e4576e87fb00a9d2511 https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h3ec907f_1.tar.bz2#4cf16ae4d975b6935582ab87dba69f0e -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h3ec907f_0.tar.bz2#3562860c28f129d73c4431cb69a13ce1 +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h596eeab_5.tar.bz2#ec3b783081e14a9dc0eb5ce609649728 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h97f7145_6.tar.bz2#c633638d6fc8e7507e383dcb76696ad3 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 @@ -205,7 +206,7 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.4.12-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.6-hbd2fdc8_0.tar.bz2#129c70116050ccf66b362ca3203fb660 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea From 33deead5846b37019902ba067c87e710e55ff6e6 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Thu, 21 Apr 2022 15:36:56 +0100 Subject: [PATCH 080/319] Cache regular points generation (#4698) * Cache regular points generation * Move func to util * Just apply lru_cache to the function once * What's new --- docs/src/whatsnew/latest.rst | 4 +++- lib/iris/coords.py | 14 ++++++++------ lib/iris/util.py | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 7 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 8f3bee23df..3c62bb324a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -81,7 +81,9 @@ This document explains the changes made to Iris for this release 🚀 Performance Enhancements =========================== -#. N/A +#. `@wjbenfold`_ added caching to the calculation of the points array in a + :class:`~iris.coords.DimCoord` created using + :meth:`~iris.coords.DimCoord.from_regular`. (:pull:`4698`) 🔥 Deprecations diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 3862c9057b..0a1aecb983 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -12,6 +12,7 @@ from collections import namedtuple from collections.abc import Container, Iterator import copy +from functools import lru_cache from itertools import chain, zip_longest import operator import warnings @@ -2523,6 +2524,10 @@ def _xml_id_extra(self, unique_value): return unique_value +_regular_points = lru_cache(iris.util.regular_points) +"""Caching version of iris.util.regular_points""" + + class DimCoord(Coord): """ A coordinate that is 1D, and numeric, with values that have a strict monotonic ordering. Missing values are not @@ -2570,12 +2575,9 @@ def from_regular( bounds values will be defined. Defaults to False. """ - points = (zeroth + step) + step * np.arange(count, dtype=np.float32) - _, regular = iris.util.points_step(points) - if not regular: - points = (zeroth + step) + step * np.arange( - count, dtype=np.float64 - ) + # Use lru_cache because this is done repeatedly with the same arguments + # (particularly in field-based file loading). + points = _regular_points(zeroth, step, count).copy() points.flags.writeable = False if with_bounds: diff --git a/lib/iris/util.py b/lib/iris/util.py index 53cd78724e..ded72d0f23 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -1281,6 +1281,32 @@ def regular_step(coord): return avdiff.astype(coord.points.dtype) +def regular_points(zeroth, step, count): + """Make an array of regular points. + + Create an array of `count` points from `zeroth` + `step`, adding `step` each + time. In float32 if this gives a sufficiently regular array (tested with + points_step) and float64 if not. + + Parameters + ---------- + zeroth : number + The value *prior* to the first point value. + + step : number + The numeric difference between successive point values. + + count : number + The number of point values. + + """ + points = (zeroth + step) + step * np.arange(count, dtype=np.float32) + _, regular = iris.util.points_step(points) + if not regular: + points = (zeroth + step) + step * np.arange(count, dtype=np.float64) + return points + + def points_step(points): """Determine whether `points` has a regular step. From c151b108758281cba837ad183845662ebb8abf3a Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Fri, 22 Apr 2022 11:53:57 +0100 Subject: [PATCH 081/319] Git ignore the benchmarks data folder (#4699) * Git ignore the benchmarks data folder * Update path and description --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b9fa92139d..1791caf3f3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,8 +26,9 @@ pip-cache .tox .pytest_cache -# asv environments, results +# asv data, environments, results .asv +benchmarks/.data #Translations *.mo From 6c80bc4a517741f966faa64306105e32f0117a64 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Fri, 22 Apr 2022 17:07:40 +0100 Subject: [PATCH 082/319] Catch `AttributeError` from removing `CFReader._dataset` when it doesn't exist (#4646) --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/fileformats/cf.py | 4 +++- lib/iris/tests/test_cf.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 3c62bb324a..b0be0e88de 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -71,6 +71,10 @@ This document explains the changes made to Iris for this release coordinate's direction is reversed. (:issue:`3249`, :issue:`423`, :issue:`4078`, :issue:`3756`, :pull:`4466`) +#. `@wjbenfold`_ prevented an ``AttributeError`` being logged to ``stderr`` when + a :class:`~iris.fileformats.cf.CFReader` that fails to initialise is garbage + collected. (:issue:`3312`, :pull:`4646`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index b22fbd3b51..a3a23dc323 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -1044,6 +1044,7 @@ class CFReader: CFGroup = CFGroup def __init__(self, filename, warn=False, monotonic=False): + self._dataset = None self._filename = os.path.expanduser(filename) #: Collection of CF-netCDF variables associated with this netCDF file @@ -1295,7 +1296,8 @@ def _reset(self): def __del__(self): # Explicitly close dataset to prevent file remaining open. - self._dataset.close() + if self._dataset is not None: + self._dataset.close() def _getncattr(dataset, attr, default=None): diff --git a/lib/iris/tests/test_cf.py b/lib/iris/tests/test_cf.py index 89fa2d20c6..fa5e2a008d 100644 --- a/lib/iris/tests/test_cf.py +++ b/lib/iris/tests/test_cf.py @@ -11,6 +11,8 @@ # import iris tests first so that some things can be initialised before importing anything else import iris.tests as tests # isort:skip +import contextlib +import io from unittest import mock import iris @@ -267,6 +269,34 @@ def test_variable_attribute_touch_pass_0(self): ), ) + def test_destructor(self): + """Test the destructor when reading the dataset fails. + Related to issue #3312: previously, the `CFReader` would + always call `close()` on its `_dataset` attribute, even if it + didn't exist because opening the dataset had failed. + """ + with self.temp_filename(suffix=".nc") as fn: + + with open(fn, "wb+") as fh: + + fh.write( + b"\x89HDF\r\n\x1a\nBroken file with correct signature" + ) + fh.flush() + + with io.StringIO() as buf: + with contextlib.redirect_stderr(buf): + try: + _ = cf.CFReader(fn) + except OSError: + pass + try: + _ = iris.load_cubes(fn) + except OSError: + pass + buf.seek(0) + self.assertStringEqual("", buf.read()) + @tests.skip_data class TestLoad(tests.IrisTest): From 9308806fc0d6e52d35197d05c9179506c826f196 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 25 Apr 2022 10:14:07 +0100 Subject: [PATCH 083/319] Implement full Mercator parameter support (#4609) * Changes to coord_systems * Finish feature * Define both standard_parallel and scale_factor... but leave one None * Initial test changes * Test updates and additions * New expected test outputs * What's new and test data version bump * Review fix * Edit the right file --- .cirrus.yml | 2 +- docs/src/whatsnew/latest.rst | 4 ++ lib/iris/coord_systems.py | 28 ++++++++- .../fileformats/_nc_load_rules/helpers.py | 15 +++-- lib/iris/fileformats/netcdf.py | 8 ++- .../tests/results/coord_systems/Mercator.xml | 2 +- lib/iris/tests/results/netcdf/netcdf_merc.cml | 8 +-- .../results/netcdf/netcdf_merc_false.cml | 8 +-- .../netcdf/netcdf_merc_scale_factor.cml | 22 +++++++ .../netcdf/Saver/write/mercator.cdl | 2 +- .../Saver/write/mercator_no_ellipsoid.cdl | 2 +- lib/iris/tests/test_netcdf.py | 14 ++++- .../tests/unit/coord_systems/test_Mercator.py | 58 +++++++++++++++++++ .../actions/test__grid_mappings.py | 33 ++--------- .../test_build_mercator_coordinate_system.py | 52 ++++++++++++++++- .../test_has_supported_mercator_parameters.py | 28 ++++++++- 16 files changed, 233 insertions(+), 53 deletions(-) create mode 100644 lib/iris/tests/results/netcdf/netcdf_merc_scale_factor.cml diff --git a/.cirrus.yml b/.cirrus.yml index 534f9f895c..ea1a978b65 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -38,7 +38,7 @@ env: # Conda packages to be installed. CONDA_CACHE_PACKAGES: "nox pip" # Git commit hash for iris test data. - IRIS_TEST_DATA_VERSION: "2.7" + IRIS_TEST_DATA_VERSION: "2.8" # Base directory for the iris-test-data. IRIS_TEST_DATA_DIR: ${HOME}/iris-test-data diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index b0be0e88de..0627aaced9 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -40,6 +40,10 @@ This document explains the changes made to Iris for this release #. `@pp-mo`_ fixed cube arithmetic operation for cubes with meshes. (:issue:`4454`, :pull:`4651`) +#. `@wjbenfold`_ added support for CF-compliant treatment of + ``standard_parallel`` and ``scale_factor_at_projection_origin`` to + :class:`~iris.coord_system.Mercator`. (:issue:`3844`, :pull:`4609`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 311ed35f44..510aafcb48 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -1083,6 +1083,7 @@ def __init__( longitude_of_projection_origin=None, ellipsoid=None, standard_parallel=None, + scale_factor_at_projection_origin=None, false_easting=None, false_northing=None, ): @@ -1100,12 +1101,18 @@ def __init__( * standard_parallel: The latitude where the scale is 1. Defaults to 0.0 . + * scale_factor_at_projection_origin: + Scale factor at natural origin. Defaults to unused. + * false_easting: X offset from the planar origin in metres. Defaults to 0.0. * false_northing: Y offset from the planar origin in metres. Defaults to 0.0. + Note: Only one of ``standard_parallel`` and + ``scale_factor_at_projection_origin`` should be included. + """ #: True longitude of planar origin in degrees. self.longitude_of_projection_origin = _arg_default( @@ -1115,8 +1122,24 @@ def __init__( #: Ellipsoid definition (:class:`GeogCS` or None). self.ellipsoid = ellipsoid + # Initialise to None, then set based on arguments #: The latitude where the scale is 1. - self.standard_parallel = _arg_default(standard_parallel, 0) + self.standard_parallel = None + # The scale factor at the origin of the projection + self.scale_factor_at_projection_origin = None + if scale_factor_at_projection_origin is None: + self.standard_parallel = _arg_default(standard_parallel, 0) + else: + if standard_parallel is None: + self.scale_factor_at_projection_origin = _arg_default( + scale_factor_at_projection_origin, 0 + ) + else: + raise ValueError( + "It does not make sense to provide both " + '"scale_factor_at_projection_origin" and ' + '"standard_parallel".' + ) #: X offset from the planar origin in metres. self.false_easting = _arg_default(false_easting, 0) @@ -1130,6 +1153,8 @@ def __repr__(self): "{self.longitude_of_projection_origin!r}, " "ellipsoid={self.ellipsoid!r}, " "standard_parallel={self.standard_parallel!r}, " + "scale_factor_at_projection_origin=" + "{self.scale_factor_at_projection_origin!r}, " "false_easting={self.false_easting!r}, " "false_northing={self.false_northing!r})" ) @@ -1142,6 +1167,7 @@ def as_cartopy_crs(self): central_longitude=self.longitude_of_projection_origin, globe=globe, latitude_true_scale=self.standard_parallel, + scale_factor=self.scale_factor_at_projection_origin, false_easting=self.false_easting, false_northing=self.false_northing, ) diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 198daeceea..954250b4a2 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -445,8 +445,9 @@ def build_mercator_coordinate_system(engine, cf_grid_var): ) false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None) false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None) - # Iris currently only supports Mercator projections with specific - # scale_factor_at_projection_origin. This is checked elsewhere. + scale_factor_at_projection_origin = getattr( + cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None + ) ellipsoid = None if ( @@ -460,6 +461,7 @@ def build_mercator_coordinate_system(engine, cf_grid_var): longitude_of_projection_origin, ellipsoid=ellipsoid, standard_parallel=standard_parallel, + scale_factor_at_projection_origin=scale_factor_at_projection_origin, false_easting=false_easting, false_northing=false_northing, ) @@ -1251,17 +1253,20 @@ def has_supported_mercator_parameters(engine, cf_name): is_valid = True cf_grid_var = engine.cf_var.cf_group[cf_name] + standard_parallel = getattr( + cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None + ) scale_factor_at_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None ) if ( scale_factor_at_projection_origin is not None - and scale_factor_at_projection_origin != 1 + and standard_parallel is not None ): warnings.warn( - "Scale factors other than 1.0 not yet supported for " - "Mercator projections" + "It does not make sense to provide both " + '"scale_factor_at_projection_origin" and "standard_parallel".' ) is_valid = False diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 72ffe8ddb5..4818f621c8 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -2563,7 +2563,13 @@ def add_ellipsoid(ellipsoid): ) cf_var_grid.false_easting = cs.false_easting cf_var_grid.false_northing = cs.false_northing - cf_var_grid.scale_factor_at_projection_origin = 1.0 + # Only one of these should be set + if cs.standard_parallel is not None: + cf_var_grid.standard_parallel = cs.standard_parallel + elif cs.scale_factor_at_projection_origin is not None: + cf_var_grid.scale_factor_at_projection_origin = ( + cs.scale_factor_at_projection_origin + ) # lcc elif isinstance(cs, iris.coord_systems.LambertConformal): diff --git a/lib/iris/tests/results/coord_systems/Mercator.xml b/lib/iris/tests/results/coord_systems/Mercator.xml index db3ccffec7..4ea768b41e 100644 --- a/lib/iris/tests/results/coord_systems/Mercator.xml +++ b/lib/iris/tests/results/coord_systems/Mercator.xml @@ -1,2 +1,2 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_merc.cml b/lib/iris/tests/results/netcdf/netcdf_merc.cml index 5e17400158..831a8fdaa7 100644 --- a/lib/iris/tests/results/netcdf/netcdf_merc.cml +++ b/lib/iris/tests/results/netcdf/netcdf_merc.cml @@ -53,15 +53,15 @@ 45.5158, 45.9993]]" shape="(192, 192)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="lon"/> - - + - - + diff --git a/lib/iris/tests/results/netcdf/netcdf_merc_false.cml b/lib/iris/tests/results/netcdf/netcdf_merc_false.cml index d916f5f753..1e50aa6e65 100644 --- a/lib/iris/tests/results/netcdf/netcdf_merc_false.cml +++ b/lib/iris/tests/results/netcdf/netcdf_merc_false.cml @@ -6,17 +6,17 @@ - - + - - + diff --git a/lib/iris/tests/results/netcdf/netcdf_merc_scale_factor.cml b/lib/iris/tests/results/netcdf/netcdf_merc_scale_factor.cml new file mode 100644 index 0000000000..c9ad4ca33f --- /dev/null +++ b/lib/iris/tests/results/netcdf/netcdf_merc_scale_factor.cml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator.cdl b/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator.cdl index 1559cd2bff..ea9a1c283b 100644 --- a/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator.cdl +++ b/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator.cdl @@ -13,7 +13,7 @@ variables: mercator:longitude_of_projection_origin = 49. ; mercator:false_easting = 0. ; mercator:false_northing = 0. ; - mercator:scale_factor_at_projection_origin = 1. ; + mercator:standard_parallel = 0. ; int64 projection_y_coordinate(projection_y_coordinate) ; projection_y_coordinate:axis = "Y" ; projection_y_coordinate:units = "m" ; diff --git a/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator_no_ellipsoid.cdl b/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator_no_ellipsoid.cdl index 8db60ca952..73b692ed63 100644 --- a/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator_no_ellipsoid.cdl +++ b/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/mercator_no_ellipsoid.cdl @@ -10,7 +10,7 @@ variables: mercator:longitude_of_projection_origin = 49. ; mercator:false_easting = 0. ; mercator:false_northing = 0. ; - mercator:scale_factor_at_projection_origin = 1. ; + mercator:standard_parallel = 0. ; int64 projection_y_coordinate(projection_y_coordinate) ; projection_y_coordinate:axis = "Y" ; projection_y_coordinate:units = "m" ; diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py index 8cdbe27257..4d130c0f0e 100644 --- a/lib/iris/tests/test_netcdf.py +++ b/lib/iris/tests/test_netcdf.py @@ -218,9 +218,9 @@ def test_load_merc_grid(self): ) self.assertCML(cube, ("netcdf", "netcdf_merc.cml")) - def test_load_merc_false_en_grid(self): + def test_load_complex_merc_grid(self): # Test loading a single CF-netCDF file with a Mercator grid_mapping that - # includes false easting and northing + # includes false easting and northing and a standard parallel cube = iris.load_cube( tests.get_data_path( ("NetCDF", "mercator", "false_east_north_merc.nc") @@ -228,6 +228,16 @@ def test_load_merc_false_en_grid(self): ) self.assertCML(cube, ("netcdf", "netcdf_merc_false.cml")) + def test_load_merc_grid_non_unit_scale_factor(self): + # Test loading a single CF-netCDF file with a Mercator grid_mapping that + # includes a non-unit scale factor at projection origin + cube = iris.load_cube( + tests.get_data_path( + ("NetCDF", "mercator", "non_unit_scale_factor_merc.nc") + ) + ) + self.assertCML(cube, ("netcdf", "netcdf_merc_scale_factor.cml")) + def test_load_stereographic_grid(self): # Test loading a single CF-netCDF file with a stereographic # grid_mapping. diff --git a/lib/iris/tests/unit/coord_systems/test_Mercator.py b/lib/iris/tests/unit/coord_systems/test_Mercator.py index 8a37a8fcc5..ba04c77d57 100644 --- a/lib/iris/tests/unit/coord_systems/test_Mercator.py +++ b/lib/iris/tests/unit/coord_systems/test_Mercator.py @@ -30,6 +30,7 @@ def test_repr(self): "ellipsoid=GeogCS(semi_major_axis=6377563.396, " "semi_minor_axis=6356256.909), " "standard_parallel=0.0, " + "scale_factor_at_projection_origin=None, " "false_easting=0.0, false_northing=0.0)" ) self.assertEqual(expected, repr(self.tm)) @@ -49,6 +50,13 @@ def test_set_optional_args(self): self.assertEqualAndKind(crs.false_easting, 13.0) self.assertEqualAndKind(crs.false_northing, 12.0) + def test_set_optional_scale_factor_alternative(self): + # Check that setting the optional (non-ellipse) args works. + crs = Mercator( + scale_factor_at_projection_origin=1.3, + ) + self.assertEqualAndKind(crs.scale_factor_at_projection_origin, 1.3) + def _check_crs_defaults(self, crs): # Check for property defaults when no kwargs options were set. # NOTE: except ellipsoid, which is done elsewhere. @@ -56,6 +64,7 @@ def _check_crs_defaults(self, crs): self.assertEqualAndKind(crs.standard_parallel, 0.0) self.assertEqualAndKind(crs.false_easting, 0.0) self.assertEqualAndKind(crs.false_northing, 0.0) + self.assertEqualAndKind(crs.scale_factor_at_projection_origin, None) def test_no_optional_args(self): # Check expected defaults with no optional args. @@ -67,6 +76,7 @@ def test_optional_args_None(self): crs = Mercator( longitude_of_projection_origin=None, standard_parallel=None, + scale_factor_at_projection_origin=None, false_easting=None, false_northing=None, ) @@ -117,6 +127,31 @@ def test_extra_kwargs(self): res = merc_cs.as_cartopy_crs() self.assertEqual(res, expected) + def test_extra_kwargs_scale_factor_alternative(self): + # Check that a projection with non-default values is correctly + # converted to a cartopy CRS. + scale_factor_at_projection_origin = 1.3 + ellipsoid = GeogCS( + semi_major_axis=6377563.396, semi_minor_axis=6356256.909 + ) + + merc_cs = Mercator( + ellipsoid=ellipsoid, + scale_factor_at_projection_origin=scale_factor_at_projection_origin, + ) + + expected = ccrs.Mercator( + globe=ccrs.Globe( + semimajor_axis=6377563.396, + semiminor_axis=6356256.909, + ellipse=None, + ), + scale_factor=scale_factor_at_projection_origin, + ) + + res = merc_cs.as_cartopy_crs() + self.assertEqual(res, expected) + class Test_as_cartopy_projection(tests.IrisTest): def test_simple(self): @@ -159,6 +194,29 @@ def test_extra_kwargs(self): res = merc_cs.as_cartopy_projection() self.assertEqual(res, expected) + def test_extra_kwargs_scale_factor_alternative(self): + ellipsoid = GeogCS( + semi_major_axis=6377563.396, semi_minor_axis=6356256.909 + ) + scale_factor_at_projection_origin = 1.3 + + merc_cs = Mercator( + ellipsoid=ellipsoid, + scale_factor_at_projection_origin=scale_factor_at_projection_origin, + ) + + expected = ccrs.Mercator( + globe=ccrs.Globe( + semimajor_axis=6377563.396, + semiminor_axis=6356256.909, + ellipse=None, + ), + scale_factor=scale_factor_at_projection_origin, + ) + + res = merc_cs.as_cartopy_projection() + self.assertEqual(res, expected) + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py index a2ecdf1490..1bf9226092 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py @@ -445,7 +445,7 @@ def test_mapping_rotated(self): # # All non-latlon coordinate systems ... # These all have projection-x/y coordinates with units of metres. - # They all work the same way, except that Mercator/Stereographic have + # They all work the same way, except that Stereographic has # parameter checking routines that can fail. # NOTE: various mapping types *require* certain addtional properties # - without which an error will occur during translation. @@ -490,37 +490,12 @@ def test_mapping_mercator(self): ) self.check_result(result, cube_cstype=ics.Mercator) - def test_mapping_mercator__fail_unsupported(self): - # Provide a mercator grid-mapping with a non-unity scale factor, which - # we cannot handle. - # Result : fails to convert into a coord-system, and emits a warning. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(mercator) --(FAILED check has_supported_mercator_parameters) - # 003 : fc_provides_coordinate_(projection_y) - # 004 : fc_provides_coordinate_(projection_x) - # 005 : fc_build_coordinate_(projection_y)(FAILED projected coord with non-projected cs) - # 006 : fc_build_coordinate_(projection_x)(FAILED projected coord with non-projected cs) - # Notes: - # * grid-mapping identified : NONE - # * dim-coords identified : proj-x and -y - # * coords built : NONE (no dim or aux coords: cube has no coords) - warning = "not yet supported for Mercator" - result = self.run_testcase( - warning=warning, - mapping_type_name=hh.CF_GRID_MAPPING_MERCATOR, - mapping_scalefactor=2.0, - ) - self.check_result(result, cube_no_cs=True, cube_no_xycoords=True) - def test_mapping_stereographic(self): result = self.run_testcase(mapping_type_name=hh.CF_GRID_MAPPING_STEREO) self.check_result(result, cube_cstype=ics.Stereographic) def test_mapping_stereographic__fail_unsupported(self): - # As for 'test_mapping_mercator__fail_unsupported', provide a non-unity - # scale factor, which we cannot handle. + # Provide a non-unity scale factor, which we cannot handle. # Result : fails to convert into a coord-system, and emits a warning. # # Rules Triggered: @@ -531,7 +506,9 @@ def test_mapping_stereographic__fail_unsupported(self): # 005 : fc_build_coordinate_(projection_y)(FAILED projected coord with non-projected cs) # 006 : fc_build_coordinate_(projection_x)(FAILED projected coord with non-projected cs) # Notes: - # as for 'mercator__fail_unsupported', above + # * grid-mapping identified : NONE + # * dim-coords identified : proj-x and -y + # * coords built : NONE (no dim or aux coords: cube has no coords) warning = "not yet supported for stereographic" result = self.run_testcase( warning=warning, diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py index 2be5477cb7..ab61d3b1b2 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py @@ -29,6 +29,7 @@ def test_valid(self): longitude_of_projection_origin=-90, semi_major_axis=6377563.396, semi_minor_axis=6356256.909, + standard_parallel=10, ) cs = build_mercator_coordinate_system(None, cf_grid_var) @@ -40,6 +41,7 @@ def test_valid(self): ellipsoid=iris.coord_systems.GeogCS( cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis ), + standard_parallel=(cf_grid_var.standard_parallel), ) self.assertEqual(cs, expected) @@ -49,6 +51,7 @@ def test_inverse_flattening(self): longitude_of_projection_origin=-90, semi_major_axis=6377563.396, inverse_flattening=299.3249646, + standard_parallel=10, ) cs = build_mercator_coordinate_system(None, cf_grid_var) @@ -61,6 +64,7 @@ def test_inverse_flattening(self): cf_grid_var.semi_major_axis, inverse_flattening=cf_grid_var.inverse_flattening, ), + standard_parallel=(cf_grid_var.standard_parallel), ) self.assertEqual(cs, expected) @@ -69,6 +73,7 @@ def test_longitude_missing(self): spec=[], semi_major_axis=6377563.396, inverse_flattening=299.3249646, + standard_parallel=10, ) cs = build_mercator_coordinate_system(None, cf_grid_var) @@ -77,7 +82,52 @@ def test_longitude_missing(self): ellipsoid=iris.coord_systems.GeogCS( cf_grid_var.semi_major_axis, inverse_flattening=cf_grid_var.inverse_flattening, - ) + ), + standard_parallel=(cf_grid_var.standard_parallel), + ) + self.assertEqual(cs, expected) + + def test_standard_parallel_missing(self): + cf_grid_var = mock.Mock( + spec=[], + longitude_of_projection_origin=-90, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + + cs = build_mercator_coordinate_system(None, cf_grid_var) + + expected = Mercator( + longitude_of_projection_origin=( + cf_grid_var.longitude_of_projection_origin + ), + ellipsoid=iris.coord_systems.GeogCS( + cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis + ), + ) + self.assertEqual(cs, expected) + + def test_scale_factor_at_projection_origin(self): + cf_grid_var = mock.Mock( + spec=[], + longitude_of_projection_origin=-90, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + scale_factor_at_projection_origin=1.3, + ) + + cs = build_mercator_coordinate_system(None, cf_grid_var) + + expected = Mercator( + longitude_of_projection_origin=( + cf_grid_var.longitude_of_projection_origin + ), + ellipsoid=iris.coord_systems.GeogCS( + cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis + ), + scale_factor_at_projection_origin=( + cf_grid_var.scale_factor_at_projection_origin + ), ) self.assertEqual(cs, expected) diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py index 1b9857c0be..bb94adc72e 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py @@ -79,8 +79,25 @@ def test_valid_standard_parallel(self): self.assertTrue(is_valid) - def test_invalid_scale_factor(self): - # Iris does not yet support scale factors other than one for + def test_valid_scale_factor(self): + cf_name = "mercator" + cf_grid_var = mock.Mock( + spec=[], + longitude_of_projection_origin=0, + false_easting=0, + false_northing=0, + scale_factor_at_projection_origin=0.9, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + is_valid = has_supported_mercator_parameters(engine, cf_name) + + self.assertTrue(is_valid) + + def test_invalid_scale_factor_and_standard_parallel(self): + # Scale factor and standard parallel cannot both be specified for # Mercator projections cf_name = "mercator" cf_grid_var = mock.Mock( @@ -89,6 +106,7 @@ def test_invalid_scale_factor(self): false_easting=0, false_northing=0, scale_factor_at_projection_origin=0.9, + standard_parallel=20, semi_major_axis=6377563.396, semi_minor_axis=6356256.909, ) @@ -100,7 +118,11 @@ def test_invalid_scale_factor(self): self.assertFalse(is_valid) self.assertEqual(len(warns), 1) - self.assertRegex(str(warns[0]), "Scale factor") + self.assertRegex( + str(warns[0]), + "both " + '"scale_factor_at_projection_origin" and "standard_parallel"', + ) if __name__ == "__main__": From 2d578e912839ac0da469cc90ec3d28c5a50bdae9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 10:20:05 +0100 Subject: [PATCH 084/319] Updated environment lockfiles (#4706) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 9482fff357..f3f1200d91 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -28,7 +28,7 @@ https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h7f98852_0.tar.bz2#5c214edc675a7fb7cbb34b1d854e5141 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c @@ -72,7 +72,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-haf5c9bc_3.tar.bz2#3fece7479fccc385e5e4f898277b7983 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-haf5c9bc_4.tar.bz2#8c336a182a644e58f5ddd5a08a670489 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 @@ -92,7 +92,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_3.tar.bz2#ce5af4269e1b874696380f6ace9a72d7 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_4.tar.bz2#f3d459b87692be9d6f4aaa5fd69a6b82 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.2-h4ff8645_0.tar.bz2#9d4b7a876d5531fdbb682ed526f8b847 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 @@ -127,7 +127,7 @@ https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/noarch/locket-0.2.0-py_2.tar.bz2#709e8671651c7ec3d1ad07800339ff1d +https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/nose-1.3.7-py_1006.tar.bz2#382019d5f8e9362ef6f60a8d4e7bce8f https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 @@ -182,17 +182,17 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.0-pyhd8ed1ab_0.tar.bz2#a47051950a34bef40d8369a5f320b78d -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.32.0-py38h0a891b7_0.tar.bz2#a6ad9659120d21c3ab44a1c96b1ebee1 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.1-pyhd8ed1ab_0.tar.bz2#5fee12b7b8acb170c47d425d3b069f3c +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.1-py38h0a891b7_0.tar.bz2#e1418611bc0977d91f91bad7608b5b56 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.0-h40b6f09_0.tar.bz2#017b20e7e98860f0bfa7492ce16390a7 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.1-pyhd8ed1ab_0.tar.bz2#40b3f446c61729cdd21ed9d85627df6e -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h6c62de6_1006.tar.bz2#829b1209dfadd431a11048d6eeaf5bef +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_1.tar.bz2#0cdb1150994e0c449b25d6f92b69eda2 https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38hbc0797c_2.tar.bz2#7e4c695d10aa5e4576e87fb00a9d2511 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38h5b5ac8f_3.tar.bz2#338813a88e4e857b0a7581fa596124b5 https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe @@ -214,7 +214,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.ta https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_3.tar.bz2#a08562889b985d021550e22443cf0fce -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.0-pyhd8ed1ab_0.tar.bz2#9113b4e4fa2fa4a7f129c71a6f319475 +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.18.1-py38h578d9bd_1.tar.bz2#026355c0547c2ae491721278f8c90200 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 From 2312432e70d33e1984af29aa2deb783f1fe161bc Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 25 Apr 2022 10:35:43 +0100 Subject: [PATCH 085/319] Add evertrol to whatsnew of #4646 given #4705 (#4709) --- docs/src/whatsnew/latest.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 0627aaced9..c0433fe9ff 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -75,9 +75,9 @@ This document explains the changes made to Iris for this release coordinate's direction is reversed. (:issue:`3249`, :issue:`423`, :issue:`4078`, :issue:`3756`, :pull:`4466`) -#. `@wjbenfold`_ prevented an ``AttributeError`` being logged to ``stderr`` when - a :class:`~iris.fileformats.cf.CFReader` that fails to initialise is garbage - collected. (:issue:`3312`, :pull:`4646`) +#. `@wjbenfold`_ and `@evertrol`_ prevented an ``AttributeError`` being logged + to ``stderr`` when a :class:`~iris.fileformats.cf.CFReader` that fails to + initialise is garbage collected. (:issue:`3312`, :pull:`4646`) 💣 Incompatible Changes @@ -149,7 +149,7 @@ This document explains the changes made to Iris for this release Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: - +.. _@evertrol: https://github.com/evertrol .. comment From 1d5838b098e70c45362d6665982f8dff4041f92b Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 25 Apr 2022 15:11:07 +0100 Subject: [PATCH 086/319] Add `iris.analysis.MAX_RUN` aggregator (#4676) * Failing tests * Start with the tests * Draft implementation * Test results * Account for masked data and multiple axes * Testing, bug fixes * Reduce duplication * Update .cml and fix test failures * Don't use map_complete_blocks * Use dask versions of functions * Test and fix masked data handling * Use da.from_array instead of as_lazy_data * Test chunking, fix dask.array import * Restore _lazy_data.py as we're not using those changes --- lib/iris/analysis/__init__.py | 78 ++++- lib/iris/cube.py | 5 + .../tests/results/analysis/max_run_bar_2d.cml | 18 + .../analysis/max_run_bar_2d_masked.cml | 18 + .../tests/results/analysis/max_run_foo_1d.cml | 12 + .../tests/results/analysis/max_run_foo_2d.cml | 17 + lib/iris/tests/test_analysis.py | 100 ++++++ lib/iris/tests/unit/analysis/test_MAX_RUN.py | 313 ++++++++++++++++++ 8 files changed, 558 insertions(+), 3 deletions(-) create mode 100644 lib/iris/tests/results/analysis/max_run_bar_2d.cml create mode 100644 lib/iris/tests/results/analysis/max_run_bar_2d_masked.cml create mode 100644 lib/iris/tests/results/analysis/max_run_foo_1d.cml create mode 100644 lib/iris/tests/results/analysis/max_run_foo_2d.cml create mode 100755 lib/iris/tests/unit/analysis/test_MAX_RUN.py diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index f9eb1a412a..d06622089f 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -1043,9 +1043,10 @@ def update_metadata(self, cube, coords, **kwargs): coord_names.append(coord.name()) # Add a cell method. - method_name = self.cell_method.format(**kwargs) - cell_method = iris.coords.CellMethod(method_name, coord_names) - cube.add_cell_method(cell_method) + if self.cell_method is not None: + method_name = self.cell_method.format(**kwargs) + cell_method = iris.coords.CellMethod(method_name, coord_names) + cube.add_cell_method(cell_method) class WeightedAggregator(Aggregator): @@ -1472,6 +1473,46 @@ def _proportion(array, function, axis, **kwargs): return result +def _lazy_max_run(array, axis=-1, **kwargs): + """ + Lazily perform the calculation of maximum run lengths along the given axis + """ + array = iris._lazy_data.as_lazy_data(array) + func = kwargs.pop("function", None) + if not callable(func): + emsg = "function must be a callable. Got {}." + raise TypeError(emsg.format(type(func))) + bool_array = da.ma.getdata(func(array)) + bool_array = da.logical_and( + bool_array, da.logical_not(da.ma.getmaskarray(array)) + ) + padding = [(0, 0)] * array.ndim + padding[axis] = (0, 1) + ones_zeros = da.pad(bool_array, padding).astype(int) + cum_sum = da.cumsum(ones_zeros, axis=axis) + run_totals = da.where(ones_zeros == 0, cum_sum, 0) + stepped_run_lengths = da.reductions.cumreduction( + np.maximum.accumulate, + np.maximum, + np.NINF, + run_totals, + axis=axis, + dtype=cum_sum.dtype, + out=None, + method="sequential", + preop=None, + ) + run_lengths = da.diff(stepped_run_lengths, axis=axis) + result = da.max(run_lengths, axis=axis) + + # Check whether to reduce to a scalar result, as per the behaviour + # of other aggregators. + if result.shape == (1,): + result = da.squeeze(result) + + return result + + def _rms(array, axis, **kwargs): # XXX due to the current limitations in `da.average` (see below), maintain # an explicit non-lazy aggregation function for now. @@ -1662,6 +1703,37 @@ def interp_order(length): """ +MAX_RUN = Aggregator( + None, + iris._lazy_data.non_lazy(_lazy_max_run), + units_func=lambda units: 1, + lazy_func=_build_dask_mdtol_function(_lazy_max_run), +) +""" +An :class:`~iris.analysis.Aggregator` instance that finds the longest run of +:class:`~iris.cube.Cube` data occurrences that satisfy a particular criterion, +as defined by a user supplied *function*, along the given axis. + +**Required** kwargs associated with the use of this aggregator: + +* function (callable): + A function which converts an array of data values into a corresponding array + of True/False values. + +**For example**: + +The longest run of days with precipitation exceeding 10 (in cube data units) at +each grid location could be calculated with:: + + result = precip_cube.collapsed('time', iris.analysis.MAX_RUN, + function=lambda values: values > 10) + +This aggregator handles masked data, which it treats as interrupting a run. + +""" +MAX_RUN.name = lambda: "max_run" + + GMEAN = Aggregator("geometric_mean", scipy.stats.mstats.gmean) """ An :class:`~iris.analysis.Aggregator` instance that calculates the diff --git a/lib/iris/cube.py b/lib/iris/cube.py index b456bd9663..cd9a846c13 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -3713,6 +3713,10 @@ def collapsed(self, coords, aggregator, **kwargs): for coord in coords: dims_to_collapse.update(self.coord_dims(coord)) + if aggregator.name() == "max_run" and len(dims_to_collapse) > 1: + msg = "Not possible to calculate runs over more than one dimension" + raise ValueError(msg) + if not dims_to_collapse: msg = ( "Cannot collapse a dimension which does not describe any " @@ -3818,6 +3822,7 @@ def collapsed(self, coords, aggregator, **kwargs): data_result = aggregator.aggregate( unrolled_data, axis=-1, **kwargs ) + aggregator.update_metadata( collapsed_cube, coords, axis=collapse_axis, **kwargs ) diff --git a/lib/iris/tests/results/analysis/max_run_bar_2d.cml b/lib/iris/tests/results/analysis/max_run_bar_2d.cml new file mode 100644 index 0000000000..32a8a377be --- /dev/null +++ b/lib/iris/tests/results/analysis/max_run_bar_2d.cml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/max_run_bar_2d_masked.cml b/lib/iris/tests/results/analysis/max_run_bar_2d_masked.cml new file mode 100644 index 0000000000..32a8a377be --- /dev/null +++ b/lib/iris/tests/results/analysis/max_run_bar_2d_masked.cml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/max_run_foo_1d.cml b/lib/iris/tests/results/analysis/max_run_foo_1d.cml new file mode 100644 index 0000000000..b2a3bcef56 --- /dev/null +++ b/lib/iris/tests/results/analysis/max_run_foo_1d.cml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/max_run_foo_2d.cml b/lib/iris/tests/results/analysis/max_run_foo_2d.cml new file mode 100644 index 0000000000..fb8448136f --- /dev/null +++ b/lib/iris/tests/results/analysis/max_run_foo_2d.cml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/test_analysis.py b/lib/iris/tests/test_analysis.py index d5a810d2fa..a7a7e3a0e2 100644 --- a/lib/iris/tests/test_analysis.py +++ b/lib/iris/tests/test_analysis.py @@ -9,6 +9,7 @@ import iris.tests as tests # isort:skip import cf_units +import dask.array as da import numpy as np import numpy.ma as ma @@ -19,6 +20,7 @@ import iris.coords import iris.cube import iris.tests.stock +import iris.util class TestAnalysisCubeCoordComparison(tests.IrisTest): @@ -931,6 +933,104 @@ def test_count_2d(self): gt6, ("analysis", "count_foo_bar_2d.cml"), checksum=False ) + def test_max_run_1d(self): + cube = tests.stock.simple_1d() + # [ 0 1 2 3 4 5 6 7 8 9 10] + result = cube.collapsed( + "foo", + iris.analysis.MAX_RUN, + function=lambda val: np.isin(val, [0, 1, 4, 5, 6, 8, 9]), + ) + self.assertArrayEqual(result.data, np.array(3)) + self.assertEqual(result.units, 1) + self.assertTupleEqual(result.cell_methods, ()) + self.assertCML( + result, ("analysis", "max_run_foo_1d.cml"), checksum=False + ) + + def test_max_run_lazy(self): + cube = tests.stock.simple_1d() + # [ 0 1 2 3 4 5 6 7 8 9 10] + # Make data lazy + cube.data = da.from_array(cube.data) + result = cube.collapsed( + "foo", + iris.analysis.MAX_RUN, + function=lambda val: np.isin(val, [0, 1, 4, 5, 6, 8, 9]), + ) + self.assertTrue(result.has_lazy_data()) + # Realise data + _ = result.data + self.assertArrayEqual(result.data, np.array(3)) + self.assertEqual(result.units, 1) + self.assertTupleEqual(result.cell_methods, ()) + self.assertCML( + result, ("analysis", "max_run_foo_1d.cml"), checksum=False + ) + + def test_max_run_2d(self): + cube = tests.stock.simple_2d() + # [[ 0 1 2 3] + # [ 4 5 6 7] + # [ 8 9 10 11]] + foo_result = cube.collapsed( + "foo", + iris.analysis.MAX_RUN, + function=lambda val: np.isin(val, [0, 3, 4, 5, 7, 9, 11]), + ) + self.assertArrayEqual( + foo_result.data, np.array([1, 2, 1], dtype=np.float32) + ) + self.assertEqual(foo_result.units, 1) + self.assertTupleEqual(foo_result.cell_methods, ()) + self.assertCML( + foo_result, ("analysis", "max_run_foo_2d.cml"), checksum=False + ) + + bar_result = cube.collapsed( + "bar", + iris.analysis.MAX_RUN, + function=lambda val: np.isin(val, [0, 3, 4, 5, 7, 9, 11]), + ) + self.assertArrayEqual( + bar_result.data, np.array([2, 2, 0, 3], dtype=np.float32) + ) + self.assertEqual(bar_result.units, 1) + self.assertTupleEqual(bar_result.cell_methods, ()) + self.assertCML( + bar_result, ("analysis", "max_run_bar_2d.cml"), checksum=False + ) + + with self.assertRaises(ValueError): + _ = cube.collapsed( + ("foo", "bar"), + iris.analysis.MAX_RUN, + function=lambda val: np.isin(val, [0, 3, 4, 5, 7, 9, 11]), + ) + + def test_max_run_masked(self): + cube = tests.stock.simple_2d() + # [[ 0 1 2 3] + # [ 4 5 6 7] + # [ 8 9 10 11]] + iris.util.mask_cube(cube, np.isin(cube.data, [0, 2, 3, 5, 7, 11])) + # [[-- 1 -- --] + # [ 4 -- 6 --] + # [ 8 9 10 --]] + result = cube.collapsed( + "bar", + iris.analysis.MAX_RUN, + function=lambda val: np.isin(val, [0, 1, 4, 5, 6, 9, 10, 11]), + ) + self.assertArrayEqual( + result.data, np.array([1, 1, 2, 0], dtype=np.float32) + ) + self.assertEqual(result.units, 1) + self.assertTupleEqual(result.cell_methods, ()) + self.assertCML( + result, ("analysis", "max_run_bar_2d_masked.cml"), checksum=False + ) + def test_weighted_sum_consistency(self): # weighted sum with unit weights should be the same as a sum cube = tests.stock.simple_1d() diff --git a/lib/iris/tests/unit/analysis/test_MAX_RUN.py b/lib/iris/tests/unit/analysis/test_MAX_RUN.py new file mode 100755 index 0000000000..00de383f7a --- /dev/null +++ b/lib/iris/tests/unit/analysis/test_MAX_RUN.py @@ -0,0 +1,313 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :data:`iris.analysis.MAX_RUN` aggregator.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import dask.array as da +import numpy as np +import numpy.ma as ma + +from iris._lazy_data import as_concrete_data, is_lazy_data +from iris.analysis import MAX_RUN + + +def bool_func(x): + return x == 1 + + +class UnmaskedTest(tests.IrisTest): + def setUp(self): + """ + Set up 1d and 2d unmasked data arrays for max run testing. + + Uses 1 and 3 rather than 1 and 0 to check that lambda is being applied. + """ + + self.data_1ds = [ + (np.array([3, 1, 1, 3, 3, 3]), 2), # One run + (np.array([3, 1, 1, 3, 1, 3]), 2), # Two runs + (np.array([3, 3, 3, 3, 3, 3]), 0), # No run + (np.array([3, 3, 1, 3, 3, 3]), 1), # Max run of 1 + (np.array([1, 1, 1, 3, 1, 3]), 3), # Run to start + (np.array([3, 1, 3, 1, 1, 1]), 3), # Run to end + (np.array([1, 1, 1, 1, 1, 1]), 6), # All run + ] + + self.data_2d_axis0 = np.array( + [ + [3, 1, 1, 3, 3, 3], # One run + [3, 1, 1, 3, 1, 3], # Two runs + [3, 3, 3, 3, 3, 3], # No run + [3, 3, 1, 3, 3, 3], # Max run of 1 + [1, 1, 1, 3, 1, 3], # Run to start + [3, 1, 3, 1, 1, 1], # Run to end + [1, 1, 1, 1, 1, 1], # All run + ] + ).T + self.expected_2d_axis0 = np.array([2, 2, 0, 1, 3, 3, 6]) + + self.data_2d_axis1 = self.data_2d_axis0.T + self.expected_2d_axis1 = self.expected_2d_axis0 + + +class MaskedTest(tests.IrisTest): + def setUp(self): + """ + Set up 1d and 2d unmasked data arrays for max run testing. + + Uses 1 and 3 rather than 1 and 0 to check that lambda is being applied. + """ + + self.data_1ds = [ + ( + ma.masked_array( + np.array([1, 1, 1, 3, 1, 3]), np.array([0, 0, 0, 0, 0, 0]) + ), + 3, + ), # No mask + ( + ma.masked_array( + np.array([1, 1, 1, 3, 1, 3]), np.array([0, 0, 0, 0, 1, 1]) + ), + 3, + ), # Mask misses run + ( + ma.masked_array( + np.array([1, 1, 1, 3, 1, 3]), np.array([1, 1, 1, 0, 0, 0]) + ), + 1, + ), # Mask max run + ( + ma.masked_array( + np.array([1, 1, 1, 3, 1, 3]), np.array([0, 0, 1, 0, 0, 0]) + ), + 2, + ), # Partially mask run + ( + ma.masked_array( + np.array([3, 1, 1, 1, 1, 3]), np.array([0, 0, 1, 0, 0, 0]) + ), + 2, + ), # Mask interrupts run + ( + ma.masked_array( + np.array([1, 1, 1, 3, 1, 3]), np.array([1, 1, 1, 1, 1, 1]) + ), + 0, + ), # All mask + ( + ma.masked_array( + np.array([1, 1, 1, 3, 1, 3]), np.array([1, 1, 1, 1, 0, 1]) + ), + 1, + ), # All mask or run + ] + + self.data_2d_axis0 = ma.masked_array( + np.array( + [ + [1, 1, 1, 3, 1, 3], + [1, 1, 1, 3, 1, 3], + [1, 1, 1, 3, 1, 3], + [1, 1, 1, 3, 1, 3], + [1, 1, 1, 3, 1, 3], + [1, 1, 1, 3, 1, 3], + ] + ), + np.array( + [ + [0, 0, 0, 0, 0, 0], # No mask + [0, 0, 0, 0, 1, 1], # Mask misses run + [1, 1, 1, 0, 0, 0], # Mask max run + [0, 0, 1, 0, 0, 0], # Partially mask run + [1, 1, 1, 1, 1, 1], # All mask + [1, 1, 1, 1, 0, 1], # All mask or run + ] + ), + ).T + + self.expected_2d_axis0 = np.array([3, 3, 1, 2, 0, 1]) + + self.data_2d_axis1 = self.data_2d_axis0.T + self.expected_2d_axis1 = self.expected_2d_axis0 + + +class RealMixin: + def run_func(self, *args, **kwargs): + return MAX_RUN.call_func(*args, **kwargs) + + def check_array(self, result, expected): + self.assertArrayEqual(result, expected) + + +class LazyMixin: + def run_func(self, *args, **kwargs): + return MAX_RUN.lazy_func(*args, **kwargs) + + def check_array(self, result, expected, expected_chunks): + self.assertTrue(is_lazy_data(result)) + self.assertTupleEqual(result.chunks, expected_chunks) + result = as_concrete_data(result) + self.assertArrayEqual(result, expected) + + +class TestBasic(UnmaskedTest, RealMixin): + def test_1d(self): + for data, expected in self.data_1ds: + result = self.run_func( + data, + axis=0, + function=bool_func, + ) + self.check_array(result, expected) + + def test_2d_axis0(self): + result = self.run_func( + self.data_2d_axis0, + axis=0, + function=bool_func, + ) + self.check_array(result, self.expected_2d_axis0) + + def test_2d_axis1(self): + result = self.run_func( + self.data_2d_axis1, + axis=1, + function=bool_func, + ) + self.check_array(result, self.expected_2d_axis1) + + +class TestLazy(UnmaskedTest, LazyMixin): + def test_1d(self): + for data, expected in self.data_1ds: + data = da.from_array(data) + result = self.run_func( + data, + axis=0, + function=bool_func, + ) + self.check_array(result, expected, ()) + + def test_2d_axis0(self): + data = da.from_array(self.data_2d_axis0) + result = self.run_func( + data, + axis=0, + function=bool_func, + ) + self.check_array( + result, self.expected_2d_axis0, ((len(self.expected_2d_axis0),),) + ) + + def test_2d_axis1(self): + data = da.from_array(self.data_2d_axis1) + result = self.run_func( + data, + axis=1, + function=bool_func, + ) + self.check_array( + result, self.expected_2d_axis1, ((len(self.expected_2d_axis1),),) + ) + + +class TestLazyChunked(UnmaskedTest, LazyMixin): + def test_1d(self): + for data, expected in self.data_1ds: + data = da.from_array(data, chunks=(1,)) + result = self.run_func( + data, + axis=0, + function=bool_func, + ) + self.check_array(result, expected, ()) + + def test_2d_axis0_chunk0(self): + data = da.from_array(self.data_2d_axis0, chunks=(1, -1)) + result = self.run_func( + data, + axis=0, + function=bool_func, + ) + self.check_array( + result, self.expected_2d_axis0, ((len(self.expected_2d_axis0),),) + ) + + def test_2d_axis0_chunk1(self): + data = da.from_array(self.data_2d_axis0, chunks=(-1, 1)) + result = self.run_func( + data, + axis=0, + function=bool_func, + ) + expected_chunks = (tuple([1] * len(self.expected_2d_axis0)),) + self.check_array(result, self.expected_2d_axis0, expected_chunks) + + def test_2d_axis1_chunk0(self): + data = da.from_array(self.data_2d_axis1, chunks=(1, -1)) + result = self.run_func( + data, + axis=1, + function=bool_func, + ) + expected_chunks = (tuple([1] * len(self.expected_2d_axis1)),) + self.check_array(result, self.expected_2d_axis1, expected_chunks) + + def test_2d_axis1_chunk1(self): + data = da.from_array(self.data_2d_axis1, chunks=(-1, 1)) + result = self.run_func( + data, + axis=1, + function=bool_func, + ) + self.check_array( + result, self.expected_2d_axis1, ((len(self.expected_2d_axis1),),) + ) + + +class TestMasked(MaskedTest, RealMixin): + def test_1d(self): + for data, expected in self.data_1ds: + result = self.run_func( + data, + axis=0, + function=bool_func, + ) + self.check_array(result, expected) + + def test_2d_axis0(self): + result = self.run_func( + self.data_2d_axis0, + axis=0, + function=bool_func, + ) + self.check_array(result, self.expected_2d_axis0) + + def test_2d_axis1(self): + result = self.run_func( + self.data_2d_axis1, + axis=1, + function=bool_func, + ) + self.check_array(result, self.expected_2d_axis1) + + +class Test_name(tests.IrisTest): + def test(self): + self.assertEqual(MAX_RUN.name(), "max_run") + + +class Test_cell_method(tests.IrisTest): + def test(self): + self.assertIsNone(MAX_RUN.cell_method) + + +if __name__ == "__main__": + tests.main() From 2f9c8d0dab250383848c410283fbd83456401fad Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 26 Apr 2022 11:34:22 +0100 Subject: [PATCH 087/319] credit Will's epic review (#4711) --- docs/src/whatsnew/latest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index c0433fe9ff..8c3a6455d7 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -34,7 +34,7 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`) -#. `@rcomer`_ implemented lazy aggregation for the +#. `@rcomer`_ and `@wjbenfold`_ (reviewer) implemented lazy aggregation for the :obj:`iris.analysis.PERCENTILE` aggregator. (:pull:`3901`) #. `@pp-mo`_ fixed cube arithmetic operation for cubes with meshes. From 766e9e758ed8a48461deb9069e385f72f7a2c7c6 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 26 Apr 2022 16:32:57 +0100 Subject: [PATCH 088/319] add bullet (#4715) --- .github/workflows/benchmark.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6237f6ffff..35dd3ee282 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -93,8 +93,11 @@ jobs: fi title="Performance Shift(s): \`$commit\`" body=" - Benchmark comparison has identified performance shifts at commit \ - $commit (#$pr_number). Please review the report below and \ + Benchmark comparison has identified performance shifts at + + * commit $commit (#$pr_number). + + Please review the report below and \ take corrective/congratulatory action as appropriate \ :slightly_smiling_face: From df06c47ec341b3d05105a4fe9daf37b24e652d08 Mon Sep 17 00:00:00 2001 From: Manuel Schlund <32543114+schlunma@users.noreply.github.com> Date: Thu, 28 Apr 2022 15:13:52 +0200 Subject: [PATCH 089/319] Weighted `aggregated_by` (#4589) * First suggestion for weighted aggregated_by * Added lazy weighted aggregation * Added first tests for weighted aggregated_by * Added further tests and allowed returned kwarg * Added unit tests for weighted aggregated_by * Moved _get_aggregator_fn to iris.analyis * Reused 'returned' keyword * Added test for lazy output if 'returned' is used * Removed checksum=False from tests * Expanded test for lazy aggregation since PERCENTILE is lazy now * Added schlunma to common links --- docs/src/common_links.inc | 1 + docs/src/whatsnew/latest.rst | 5 + lib/iris/analysis/__init__.py | 38 + lib/iris/cube.py | 106 ++- .../results/analysis/aggregated_by/easy.cml | 2 +- .../analysis/aggregated_by/multi_missing.cml | 2 +- .../analysis/aggregated_by/single_missing.cml | 2 +- .../analysis/aggregated_by/weighted_easy.cml | 23 + .../analysis/aggregated_by/weighted_multi.cml | 41 + .../aggregated_by/weighted_multi_missing.cml | 41 + .../aggregated_by/weighted_multi_shared.cml | 63 ++ .../aggregated_by/weighted_single.cml | 36 + .../aggregated_by/weighted_single_missing.cml | 36 + .../aggregated_by/weighted_single_shared.cml | 46 ++ .../weighted_single_shared_circular.cml | 47 ++ lib/iris/tests/test_aggregate_by.py | 741 ++++++++++++++++-- lib/iris/tests/test_analysis.py | 38 + lib/iris/tests/test_lazy_aggregate_by.py | 48 ++ lib/iris/tests/unit/cube/test_Cube.py | 253 +++++- 19 files changed, 1489 insertions(+), 80 deletions(-) create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_easy.cml create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_multi.cml create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_multi_missing.cml create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_multi_shared.cml create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_single.cml create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_single_missing.cml create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared.cml create mode 100644 lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared_circular.cml create mode 100644 lib/iris/tests/test_lazy_aggregate_by.py diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index e80a597976..6f0ec83a3b 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -63,6 +63,7 @@ .. _@QuLogic: https://github.com/QuLogic .. _@rcomer: https://github.com/rcomer .. _@rhattersley: https://github.com/rhattersley +.. _@schlunma: https://github.com/schlunma .. _@stephenworsley: https://github.com/stephenworsley .. _@tkknight: https://github.com/tkknight .. _@trexfeathers: https://github.com/trexfeathers diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 8c3a6455d7..1c64610f89 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -31,6 +31,11 @@ This document explains the changes made to Iris for this release ✨ Features =========== +#. `@schlunma`_ added weighted aggregation over "group coordinates": + :meth:`~iris.cube.Cube.aggregated_by` now accepts the keyword `weights` if a + :class:`~iris.analysis.WeightedAggregator` is used. (:issue:`4581`, + :pull:`4589`) + #. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`) diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index d06622089f..63b9173e6c 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -80,6 +80,7 @@ "WPERCENTILE", "WeightedAggregator", "clear_phenomenon_identity", + "create_weighted_aggregator_fn", ) @@ -1155,6 +1156,43 @@ def post_process(self, collapsed_cube, data_result, coords, **kwargs): return result +def create_weighted_aggregator_fn(aggregator_fn, axis, **kwargs): + """Return an aggregator function that can explicitely handle weights. + + Args: + + * aggregator_fn (callable): + An aggregator function, i.e., a callable that takes arguments ``data``, + ``axis`` and ``**kwargs`` and returns an array. Examples: + :meth:`Aggregator.aggregate`, :meth:`Aggregator.lazy_aggregate`. + This function should accept the keyword argument ``weights``. + * axis (int): + Axis to aggregate over. This argument is directly passed to + ``aggregator_fn``. + + Kwargs: + + * Arbitrary keyword arguments passed to ``aggregator_fn``. Should not + include ``weights`` (this will be removed if present). + + Returns: + A function that takes two arguments ``data_arr`` and ``weights`` (both + should be an array of the same shape) and returns an array. + + """ + kwargs_copy = dict(kwargs) + kwargs_copy.pop("weights", None) + aggregator_fn = functools.partial(aggregator_fn, axis=axis, **kwargs_copy) + + def new_aggregator_fn(data_arr, weights): + """Weighted aggregation.""" + if weights is None: + return aggregator_fn(data_arr) + return aggregator_fn(data_arr, weights=weights) + + return new_aggregator_fn + + def _build_dask_mdtol_function(dask_stats_function): """ Make a wrapped dask statistic function that supports the 'mdtol' keyword. diff --git a/lib/iris/cube.py b/lib/iris/cube.py index cd9a846c13..94aa6ff4f1 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -3844,6 +3844,15 @@ def aggregated_by(self, coords, aggregator, **kwargs): common value group identified over all the group-by coordinates is collapsed using the provided aggregator. + Weighted aggregations (:class:`iris.analysis.WeightedAggregator`) may + also be supplied. These include :data:`~iris.analysis.MEAN` and + sum :data:`~iris.analysis.SUM`. + + Weighted aggregations support an optional *weights* keyword argument. + If set, this should be supplied as an array of weights whose shape + matches the cube or as 1D array whose length matches the dimension over + which is aggregated. + Args: * coords (list of coord names or :class:`iris.coords.Coord` instances): @@ -3897,14 +3906,6 @@ def aggregated_by(self, coords, aggregator, **kwargs): groupby_coords = [] dimension_to_groupby = None - # We can't handle weights - if isinstance( - aggregator, iris.analysis.WeightedAggregator - ) and aggregator.uses_weighting(**kwargs): - raise ValueError( - "Invalid Aggregation, aggregated_by() cannot use" " weights." - ) - coords = self._as_list_of_coords(coords) for coord in sorted(coords, key=lambda coord: coord.metadata): if coord.ndim > 1: @@ -3927,6 +3928,31 @@ def aggregated_by(self, coords, aggregator, **kwargs): raise iris.exceptions.CoordinateCollapseError(msg) groupby_coords.append(coord) + # Check shape of weights. These must either match the shape of the cube + # or be 1D (in this case, their length must be equal to the length of the + # dimension we are aggregating over). + weights = kwargs.get("weights") + return_weights = kwargs.get("returned", False) + if weights is not None: + if weights.ndim == 1: + if len(weights) != self.shape[dimension_to_groupby]: + raise ValueError( + f"1D weights must have the same length as the dimension " + f"that is aggregated, got {len(weights):d}, expected " + f"{self.shape[dimension_to_groupby]:d}" + ) + weights = iris.util.broadcast_to_shape( + weights, + self.shape, + (dimension_to_groupby,), + ) + if weights.shape != self.shape: + raise ValueError( + f"Weights must either be 1D or have the same shape as the " + f"cube, got shape {weights.shape} for weights, " + f"{self.shape} for cube" + ) + # Determine the other coordinates that share the same group-by # coordinate dimension. shared_coords = list( @@ -3972,16 +3998,41 @@ def aggregated_by(self, coords, aggregator, **kwargs): back_slice = (slice(None, None),) * ( len(data_shape) - dimension_to_groupby - 1 ) + + # Create cube and weights slices groupby_subcubes = map( lambda groupby_slice: self[ front_slice + (groupby_slice,) + back_slice ].lazy_data(), groupby.group(), ) - agg = partial( + if weights is not None: + groupby_subweights = map( + lambda groupby_slice: weights[ + front_slice + (groupby_slice,) + back_slice + ], + groupby.group(), + ) + else: + groupby_subweights = (None for _ in range(len(groupby))) + + agg = iris.analysis.create_weighted_aggregator_fn( aggregator.lazy_aggregate, axis=dimension_to_groupby, **kwargs ) - result = list(map(agg, groupby_subcubes)) + result = list(map(agg, groupby_subcubes, groupby_subweights)) + + # If weights are returned, "result" is a list of tuples (each tuple + # contains two elements; the first is the aggregated data, the + # second is the aggregated weights). Convert these to two lists + # (one for the aggregated data and one for the aggregated weights) + # before combining the different slices. + if return_weights: + result, weights_result = list(zip(*result)) + aggregateby_weights = da.stack( + weights_result, axis=dimension_to_groupby + ) + else: + aggregateby_weights = None aggregateby_data = da.stack(result, axis=dimension_to_groupby) else: cube_slice = [slice(None, None)] * len(data_shape) @@ -3990,13 +4041,23 @@ def aggregated_by(self, coords, aggregator, **kwargs): # sub-cube. cube_slice[dimension_to_groupby] = groupby_slice groupby_sub_cube = self[tuple(cube_slice)] + + # Slice the weights + if weights is not None: + groupby_sub_weights = weights[tuple(cube_slice)] + kwargs["weights"] = groupby_sub_weights + # Perform the aggregation over the group-by sub-cube and - # repatriate the aggregated data into the aggregate-by - # cube data. - cube_slice[dimension_to_groupby] = i + # repatriate the aggregated data into the aggregate-by cube + # data. If weights are also returned, handle them separately. result = aggregator.aggregate( groupby_sub_cube.data, axis=dimension_to_groupby, **kwargs ) + if return_weights: + weights_result = result[1] + result = result[0] + else: + weights_result = None # Determine aggregation result data type for the aggregate-by # cube data on first pass. @@ -4009,7 +4070,20 @@ def aggregated_by(self, coords, aggregator, **kwargs): aggregateby_data = np.zeros( data_shape, dtype=result.dtype ) + if weights_result is not None: + aggregateby_weights = np.zeros( + data_shape, dtype=weights_result.dtype + ) + else: + aggregateby_weights = None + cube_slice[dimension_to_groupby] = i aggregateby_data[tuple(cube_slice)] = result + if weights_result is not None: + aggregateby_weights[tuple(cube_slice)] = weights_result + + # Restore original weights. + if weights is not None: + kwargs["weights"] = weights # Add the aggregation meta data to the aggregate-by cube. aggregator.update_metadata( @@ -4034,8 +4108,12 @@ def aggregated_by(self, coords, aggregator, **kwargs): ) # Attach the aggregate-by data into the aggregate-by cube. + if aggregateby_weights is None: + data_result = aggregateby_data + else: + data_result = (aggregateby_data, aggregateby_weights) aggregateby_cube = aggregator.post_process( - aggregateby_cube, aggregateby_data, coords, **kwargs + aggregateby_cube, data_result, coords, **kwargs ) return aggregateby_cube diff --git a/lib/iris/tests/results/analysis/aggregated_by/easy.cml b/lib/iris/tests/results/analysis/aggregated_by/easy.cml index c4edb9484f..d02c3f12d1 100644 --- a/lib/iris/tests/results/analysis/aggregated_by/easy.cml +++ b/lib/iris/tests/results/analysis/aggregated_by/easy.cml @@ -18,6 +18,6 @@ - + diff --git a/lib/iris/tests/results/analysis/aggregated_by/multi_missing.cml b/lib/iris/tests/results/analysis/aggregated_by/multi_missing.cml index 2f8f1e73d7..dc9bdd0df8 100644 --- a/lib/iris/tests/results/analysis/aggregated_by/multi_missing.cml +++ b/lib/iris/tests/results/analysis/aggregated_by/multi_missing.cml @@ -36,6 +36,6 @@ - + diff --git a/lib/iris/tests/results/analysis/aggregated_by/single_missing.cml b/lib/iris/tests/results/analysis/aggregated_by/single_missing.cml index e6b95e3cbc..51e1ae4ff1 100644 --- a/lib/iris/tests/results/analysis/aggregated_by/single_missing.cml +++ b/lib/iris/tests/results/analysis/aggregated_by/single_missing.cml @@ -31,6 +31,6 @@ - + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_easy.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_easy.cml new file mode 100644 index 0000000000..8c434479c9 --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_easy.cml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_multi.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_multi.cml new file mode 100644 index 0000000000..cca744ff87 --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_multi.cml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_multi_missing.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_multi_missing.cml new file mode 100644 index 0000000000..8c11bdb505 --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_multi_missing.cml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_multi_shared.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_multi_shared.cml new file mode 100644 index 0000000000..ab7a7195fd --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_multi_shared.cml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_single.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_single.cml new file mode 100644 index 0000000000..d5bb9775fe --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_single.cml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_single_missing.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_single_missing.cml new file mode 100644 index 0000000000..f7d57a9828 --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_single_missing.cml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared.cml new file mode 100644 index 0000000000..50a2c44a98 --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared.cml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared_circular.cml b/lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared_circular.cml new file mode 100644 index 0000000000..657fb43414 --- /dev/null +++ b/lib/iris/tests/results/analysis/aggregated_by/weighted_single_shared_circular.cml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/test_aggregate_by.py b/lib/iris/tests/test_aggregate_by.py index 4e479f40f7..90bf0e5d4e 100644 --- a/lib/iris/tests/test_aggregate_by.py +++ b/lib/iris/tests/test_aggregate_by.py @@ -138,6 +138,101 @@ def setUp(self): self.cube_multi.add_dim_coord(coord_lon.copy(), 1) self.cube_multi.add_dim_coord(coord_lat.copy(), 2) + # + # masked cubes to test handling of masks + # + mask_single = np.vstack( + ( + np.array([[[0, 1, 0], [1, 0, 1], [0, 1, 0]]]).repeat( + 26, axis=0 + ), + np.zeros([10, 3, 3]), + ) + ) + self.cube_single_masked = self.cube_single.copy( + ma.array(self.cube_single.data, mask=mask_single) + ) + mask_multi = np.vstack( + ( + np.array([[[0, 1, 0], [1, 0, 1], [0, 1, 0]]]).repeat( + 16, axis=0 + ), + np.ones([2, 3, 3]), + np.zeros([2, 3, 3]), + ) + ) + self.cube_multi_masked = self.cube_multi.copy( + ma.array(self.cube_multi.data, mask=mask_multi) + ) + + # + # simple cubes for further tests + # + data_easy = np.array( + [[6, 10, 12, 18], [8, 12, 14, 20], [18, 12, 10, 6]], + dtype=np.float32, + ) + self.cube_easy = iris.cube.Cube( + data_easy, long_name="temperature", units="kelvin" + ) + + llcs = iris.coord_systems.GeogCS(6371229) + self.cube_easy.add_aux_coord( + iris.coords.AuxCoord( + np.array([0, 0, 10], dtype=np.float32), + "latitude", + units="degrees", + coord_system=llcs, + ), + 0, + ) + self.cube_easy.add_aux_coord( + iris.coords.AuxCoord( + np.array([0, 0, 10, 10], dtype=np.float32), + "longitude", + units="degrees", + coord_system=llcs, + ), + 1, + ) + + data_easy_weighted = np.array( + [[3, 5, 7, 9], [0, 2, 4, 6]], + dtype=np.float32, + ) + self.cube_easy_weighted = iris.cube.Cube( + data_easy_weighted, long_name="temperature", units="kelvin" + ) + llcs = iris.coord_systems.GeogCS(6371229) + self.cube_easy_weighted.add_aux_coord( + iris.coords.AuxCoord( + np.array([0, 10], dtype=np.float32), + "latitude", + units="degrees", + coord_system=llcs, + ), + 0, + ) + self.cube_easy_weighted.add_aux_coord( + iris.coords.AuxCoord( + np.array([0, 0, 10, 10], dtype=np.float32), + "longitude", + units="degrees", + coord_system=llcs, + ), + 1, + ) + + # + # weights for weighted aggregate-by + # + self.weights_single = np.ones_like(z_points, dtype=np.float64) + self.weights_single[2] = 0.0 + self.weights_single[4:6] = 0.0 + + self.weights_multi = np.ones_like(z1_points, dtype=np.float64) + self.weights_multi[1:4] = 0.0 + # # expected data results # @@ -166,6 +261,31 @@ def setUp(self): ], dtype=np.float64, ) + self.weighted_single_expected = np.array( + [ + [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], + [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]], + [[3.0, 6.0, 9.0], [12.0, 15.0, 18.0], [21.0, 24.0, 27.0]], + [[7.5, 15.0, 22.5], [30.0, 37.5, 45.0], [52.5, 60.0, 67.5]], + [[12.0, 24.0, 36.0], [48.0, 60.0, 72.0], [84.0, 96.0, 108.0]], + [ + [17.5, 35.0, 52.5], + [70.0, 87.5, 105.0], + [122.5, 140.0, 157.5], + ], + [ + [24.0, 48.0, 72.0], + [96.0, 120.0, 144.0], + [168.0, 192.0, 216.0], + ], + [ + [31.5, 63.0, 94.5], + [126.0, 157.5, 189.0], + [220.5, 252.0, 283.5], + ], + ], + dtype=np.float64, + ) row1 = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] row2 = [ @@ -229,6 +349,28 @@ def setUp(self): ], dtype=np.float64, ) + self.weighted_multi_expected = np.array( + [ + [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]], + [[4.0, 8.0, 12.0], [16.0, 20.0, 24.0], [28.0, 32.0, 36.0]], + [[14.0, 28.0, 42.0], [56.0, 70.0, 84.0], [98.0, 112.0, 126.0]], + [[7.0, 14.0, 21.0], [28.0, 35.0, 42.0], [49.0, 56.0, 63.0]], + [[9.0, 18.0, 27.0], [36.0, 45.0, 54.0], [63.0, 72.0, 81.0]], + [[10.5, 21.0, 31.5], [42.0, 52.5, 63.0], [73.5, 84.0, 94.5]], + [[13.0, 26.0, 39.0], [52.0, 65.0, 78.0], [91.0, 104.0, 117.0]], + [ + [15.0, 30.0, 45.0], + [60.0, 75.0, 90.0], + [105.0, 120.0, 135.0], + ], + [ + [16.5, 33.0, 49.5], + [66.0, 82.5, 99.0], + [115.5, 132.0, 148.5], + ], + ], + dtype=np.float64, + ) def test_single(self): # mean group-by with single coordinate name. @@ -271,6 +413,34 @@ def test_single(self): aggregateby_cube.data, self.single_rms_expected ) + def test_weighted_single(self): + # weighted mean group-by with single coordinate name. + aggregateby_cube = self.cube_single.aggregated_by( + "height", + iris.analysis.MEAN, + weights=self.weights_single, + ) + + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_single.cml"), + ) + + # weighted mean group-by with single coordinate. + aggregateby_cube = self.cube_single.aggregated_by( + self.coord_z_single, + iris.analysis.MEAN, + weights=self.weights_single, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_single.cml"), + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + self.weighted_single_expected, + ) + def test_single_shared(self): z2_points = np.arange(36, dtype=np.int32) coord_z2 = iris.coords.AuxCoord( @@ -300,6 +470,38 @@ def test_single_shared(self): aggregateby_cube.data, self.single_expected ) + def test_weighted_single_shared(self): + z2_points = np.arange(36, dtype=np.int32) + coord_z2 = iris.coords.AuxCoord( + z2_points, long_name="wibble", units="1" + ) + self.cube_single.add_aux_coord(coord_z2, 0) + + # weighted group-by with single coordinate name on shared axis. + aggregateby_cube = self.cube_single.aggregated_by( + "height", + iris.analysis.MEAN, + weights=self.weights_single, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_single_shared.cml"), + ) + + # weighted group-by with single coordinate on shared axis. + aggregateby_cube = self.cube_single.aggregated_by( + self.coord_z_single, + iris.analysis.MEAN, + weights=self.weights_single, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_single_shared.cml"), + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, self.weighted_single_expected + ) + def test_single_shared_circular(self): points = np.arange(36) * 10.0 circ_coord = iris.coords.DimCoord( @@ -329,6 +531,48 @@ def test_single_shared_circular(self): aggregateby_cube.data, self.single_expected ) + def test_weighted_single_shared_circular(self): + points = np.arange(36) * 10.0 + circ_coord = iris.coords.DimCoord( + points, long_name="circ_height", units="degrees", circular=True + ) + self.cube_single.add_aux_coord(circ_coord, 0) + + # weighted group-by with single coordinate name on shared axis. + aggregateby_cube = self.cube_single.aggregated_by( + "height", + iris.analysis.MEAN, + weights=self.weights_single, + ) + self.assertCML( + aggregateby_cube, + ( + "analysis", + "aggregated_by", + "weighted_single_shared_circular.cml", + ), + ) + + # weighted group-by with single coordinate on shared axis. + coord = self.cube_single.coords("height") + aggregateby_cube = self.cube_single.aggregated_by( + coord, + iris.analysis.MEAN, + weights=self.weights_single, + ) + self.assertCML( + aggregateby_cube, + ( + "analysis", + "aggregated_by", + "weighted_single_shared_circular.cml", + ), + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + self.weighted_single_expected, + ) + def test_multi(self): # group-by with multiple coordinate names. aggregateby_cube = self.cube_multi.aggregated_by( @@ -366,6 +610,55 @@ def test_multi(self): aggregateby_cube.data, self.multi_expected ) + def test_weighted_multi(self): + # weighted group-by with multiple coordinate names. + aggregateby_cube = self.cube_multi.aggregated_by( + ["height", "level"], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi.cml"), + ) + + # weighted group-by with multiple coordinate names (different order). + aggregateby_cube = self.cube_multi.aggregated_by( + ["level", "height"], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi.cml"), + ) + + # weighted group-by with multiple coordinates. + aggregateby_cube = self.cube_multi.aggregated_by( + [self.coord_z1_multi, self.coord_z2_multi], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi.cml"), + ) + + # weighted group-by with multiple coordinates (different order). + aggregateby_cube = self.cube_multi.aggregated_by( + [self.coord_z2_multi, self.coord_z1_multi], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi.cml"), + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + self.weighted_multi_expected, + ) + def test_multi_shared(self): z3_points = np.arange(20, dtype=np.int32) coord_z3 = iris.coords.AuxCoord( @@ -416,50 +709,91 @@ def test_multi_shared(self): aggregateby_cube.data, self.multi_expected ) - def test_easy(self): - data = np.array( - [[6, 10, 12, 18], [8, 12, 14, 20], [18, 12, 10, 6]], - dtype=np.float32, + def test_weighted_multi_shared(self): + z3_points = np.arange(20, dtype=np.int32) + coord_z3 = iris.coords.AuxCoord( + z3_points, long_name="sigma", units="1" + ) + z4_points = np.arange(19, -1, -1, dtype=np.int32) + coord_z4 = iris.coords.AuxCoord( + z4_points, long_name="gamma", units="1" ) - cube = iris.cube.Cube(data, long_name="temperature", units="kelvin") - llcs = iris.coord_systems.GeogCS(6371229) - cube.add_aux_coord( - iris.coords.AuxCoord( - np.array([0, 0, 10], dtype=np.float32), - "latitude", - units="degrees", - coord_system=llcs, - ), - 0, + self.cube_multi.add_aux_coord(coord_z3, 0) + self.cube_multi.add_aux_coord(coord_z4, 0) + + # weighted group-by with multiple coordinate names on shared axis. + aggregateby_cube = self.cube_multi.aggregated_by( + ["height", "level"], + iris.analysis.MEAN, + weights=self.weights_multi, ) - cube.add_aux_coord( - iris.coords.AuxCoord( - np.array([0, 0, 10, 10], dtype=np.float32), - "longitude", - units="degrees", - coord_system=llcs, - ), - 1, + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi_shared.cml"), ) + # weighted group-by with multiple coordinate names on shared axis + # (different order). + aggregateby_cube = self.cube_multi.aggregated_by( + ["level", "height"], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi_shared.cml"), + ) + + # weighted group-by with multiple coordinates on shared axis. + aggregateby_cube = self.cube_multi.aggregated_by( + [self.coord_z1_multi, self.coord_z2_multi], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi_shared.cml"), + ) + + # weighted group-by with multiple coordinates on shared axis (different + # order). + aggregateby_cube = self.cube_multi.aggregated_by( + [self.coord_z2_multi, self.coord_z1_multi], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi_shared.cml"), + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + self.weighted_multi_expected, + ) + + def test_easy(self): # # Easy mean aggregate test by each coordinate. # - aggregateby_cube = cube.aggregated_by("longitude", iris.analysis.MEAN) + aggregateby_cube = self.cube_easy.aggregated_by( + "longitude", iris.analysis.MEAN + ) np.testing.assert_almost_equal( aggregateby_cube.data, np.array( [[8.0, 15.0], [10.0, 17.0], [15.0, 8.0]], dtype=np.float32 ), ) + self.assertCML( aggregateby_cube, ("analysis", "aggregated_by", "easy.cml"), - checksum=False, ) - aggregateby_cube = cube.aggregated_by("latitude", iris.analysis.MEAN) + aggregateby_cube = self.cube_easy.aggregated_by( + "latitude", iris.analysis.MEAN + ) np.testing.assert_almost_equal( aggregateby_cube.data, np.array( @@ -471,7 +805,9 @@ def test_easy(self): # # Easy max aggregate test by each coordinate. # - aggregateby_cube = cube.aggregated_by("longitude", iris.analysis.MAX) + aggregateby_cube = self.cube_easy.aggregated_by( + "longitude", iris.analysis.MAX + ) np.testing.assert_almost_equal( aggregateby_cube.data, np.array( @@ -479,7 +815,9 @@ def test_easy(self): ), ) - aggregateby_cube = cube.aggregated_by("latitude", iris.analysis.MAX) + aggregateby_cube = self.cube_easy.aggregated_by( + "latitude", iris.analysis.MAX + ) np.testing.assert_almost_equal( aggregateby_cube.data, np.array( @@ -491,7 +829,9 @@ def test_easy(self): # # Easy sum aggregate test by each coordinate. # - aggregateby_cube = cube.aggregated_by("longitude", iris.analysis.SUM) + aggregateby_cube = self.cube_easy.aggregated_by( + "longitude", iris.analysis.SUM + ) np.testing.assert_almost_equal( aggregateby_cube.data, np.array( @@ -499,7 +839,9 @@ def test_easy(self): ), ) - aggregateby_cube = cube.aggregated_by("latitude", iris.analysis.SUM) + aggregateby_cube = self.cube_easy.aggregated_by( + "latitude", iris.analysis.SUM + ) np.testing.assert_almost_equal( aggregateby_cube.data, np.array( @@ -511,7 +853,7 @@ def test_easy(self): # # Easy percentile aggregate test by each coordinate. # - aggregateby_cube = cube.aggregated_by( + aggregateby_cube = self.cube_easy.aggregated_by( "longitude", iris.analysis.PERCENTILE, percent=25 ) np.testing.assert_almost_equal( @@ -521,7 +863,7 @@ def test_easy(self): ), ) - aggregateby_cube = cube.aggregated_by( + aggregateby_cube = self.cube_easy.aggregated_by( "latitude", iris.analysis.PERCENTILE, percent=25 ) np.testing.assert_almost_equal( @@ -535,7 +877,9 @@ def test_easy(self): # # Easy root mean square aggregate test by each coordinate. # - aggregateby_cube = cube.aggregated_by("longitude", iris.analysis.RMS) + aggregateby_cube = self.cube_easy.aggregated_by( + "longitude", iris.analysis.RMS + ) row = [ list(np.sqrt([68.0, 234.0])), list(np.sqrt([104.0, 298.0])), @@ -545,7 +889,9 @@ def test_easy(self): aggregateby_cube.data, np.array(row, dtype=np.float32) ) - aggregateby_cube = cube.aggregated_by("latitude", iris.analysis.RMS) + aggregateby_cube = self.cube_easy.aggregated_by( + "latitude", iris.analysis.RMS + ) row = [ list(np.sqrt([50.0, 122.0, 170.0, 362.0])), [18.0, 12.0, 10.0, 6.0], @@ -554,17 +900,109 @@ def test_easy(self): aggregateby_cube.data, np.array(row, dtype=np.float32) ) + def test_weighted_easy(self): + # Use different weights for lat and lon to avoid division by zero. + lon_weights = np.array( + [[1, 0, 1, 1], [9, 1, 2, 0]], + dtype=np.float32, + ) + lat_weights = np.array([2.0, 2.0]) + + # + # Easy weighted mean aggregate test by each coordinate. + # + aggregateby_cube = self.cube_easy_weighted.aggregated_by( + "longitude", iris.analysis.MEAN, weights=lon_weights + ) + + np.testing.assert_almost_equal( + aggregateby_cube.data, + np.array([[3.0, 8.0], [0.2, 4.0]], dtype=np.float32), + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_easy.cml"), + ) + + aggregateby_cube = self.cube_easy_weighted.aggregated_by( + "latitude", + iris.analysis.MEAN, + weights=lat_weights, + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + np.array( + [[3.0, 5.0, 7.0, 9.0], [0.0, 2.0, 4.0, 6.0]], + dtype=np.float32, + ), + ) + + # + # Easy weighted sum aggregate test by each coordinate. + # + aggregateby_cube = self.cube_easy_weighted.aggregated_by( + "longitude", iris.analysis.SUM, weights=lon_weights + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + np.array([[3.0, 16.0], [2.0, 8.0]], dtype=np.float32), + ) + + aggregateby_cube = self.cube_easy_weighted.aggregated_by( + "latitude", + iris.analysis.SUM, + weights=lat_weights, + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + np.array( + [[6.0, 10.0, 14.0, 18.0], [0.0, 4.0, 8.0, 12.0]], + dtype=np.float32, + ), + ) + + # + # Easy weighted percentile aggregate test for longitude. + # Note: Not possible for latitude since at least two values for each + # category are necessary. + # + aggregateby_cube = self.cube_easy_weighted.aggregated_by( + "longitude", + iris.analysis.WPERCENTILE, + percent=50, + weights=lon_weights, + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + np.array([[3.0, 8.0], [0.2, 4.0]], dtype=np.float32), + ) + + # + # Easy weighted root mean square aggregate test by each coordinate. + # + aggregateby_cube = self.cube_easy_weighted.aggregated_by( + "longitude", iris.analysis.RMS, weights=lon_weights + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + np.array( + [[3.0, np.sqrt(65.0)], [np.sqrt(0.4), 4.0]], dtype=np.float32 + ), + ) + + aggregateby_cube = self.cube_easy_weighted.aggregated_by( + "latitude", iris.analysis.RMS, weights=lat_weights + ) + np.testing.assert_almost_equal( + aggregateby_cube.data, + np.array( + [[3.0, 5.0, 7.0, 9.0], [0.0, 2.0, 4.0, 6.0]], + dtype=np.float32, + ), + ) + def test_single_missing(self): # aggregation correctly handles masked data - mask = np.vstack( - ( - np.array([[[0, 1, 0], [1, 0, 1], [0, 1, 0]]]).repeat( - 26, axis=0 - ), - np.zeros([10, 3, 3]), - ) - ) - self.cube_single.data = ma.array(self.cube_single.data, mask=mask) single_expected = ma.masked_invalid( [ [ @@ -609,30 +1047,81 @@ def test_single_missing(self): ], ] ) - aggregateby_cube = self.cube_single.aggregated_by( + aggregateby_cube = self.cube_single_masked.aggregated_by( "height", iris.analysis.MEAN ) + self.assertCML( aggregateby_cube, ("analysis", "aggregated_by", "single_missing.cml"), - checksum=False, ) self.assertMaskedArrayAlmostEqual( aggregateby_cube.data, single_expected ) + def test_weighted_single_missing(self): + # weighted aggregation correctly handles masked data + weighted_single_expected = ma.masked_invalid( + [ + [ + [0.0, np.nan, 0.0], + [np.nan, 0.0, np.nan], + [0.0, np.nan, 0.0], + ], + [ + [1.0, np.nan, 3.0], + [np.nan, 5.0, np.nan], + [7.0, np.nan, 9.0], + ], + [ + [3.0, np.nan, 9.0], + [np.nan, 15.0, np.nan], + [21.0, np.nan, 27.0], + ], + [ + [7.5, np.nan, 22.5], + [np.nan, 37.5, np.nan], + [52.5, np.nan, 67.5], + ], + [ + [12.0, np.nan, 36.0], + [np.nan, 60.0, np.nan], + [84.0, np.nan, 108.0], + ], + [ + [17.5, np.nan, 52.5], + [np.nan, 87.5, np.nan], + [122.5, np.nan, 157.5], + ], + [ + [24.0, 53.0, 72.0], + [106.0, 120.0, 159.0], + [168.0, 212.0, 216.0], + ], + [ + [31.5, 63.0, 94.5], + [126.0, 157.5, 189.0], + [220.5, 252.0, 283.5], + ], + ] + ) + aggregateby_cube = self.cube_single_masked.aggregated_by( + "height", + iris.analysis.MEAN, + weights=self.weights_single, + ) + + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_single_missing.cml"), + ) + self.assertMaskedArrayAlmostEqual( + aggregateby_cube.data, + weighted_single_expected, + ) + def test_multi_missing(self): # aggregation correctly handles masked data - mask = np.vstack( - ( - np.array([[[0, 1, 0], [1, 0, 1], [0, 1, 0]]]).repeat( - 16, axis=0 - ), - np.ones([2, 3, 3]), - np.zeros([2, 3, 3]), - ) - ) - self.cube_multi.data = ma.array(self.cube_multi.data, mask=mask) multi_expected = ma.masked_invalid( [ [ @@ -682,32 +1171,160 @@ def test_multi_missing(self): ], ] ) - aggregateby_cube = self.cube_multi.aggregated_by( + aggregateby_cube = self.cube_multi_masked.aggregated_by( ["height", "level"], iris.analysis.MEAN ) + self.assertCML( aggregateby_cube, ("analysis", "aggregated_by", "multi_missing.cml"), - checksum=False, ) self.assertMaskedArrayAlmostEqual( aggregateby_cube.data, multi_expected ) - def test_returned_weights(self): + def test_weighted_multi_missing(self): + # weighted aggregation correctly handles masked data + weighted_multi_expected = ma.masked_invalid( + [ + [ + [0.0, np.nan, 0.0], + [np.nan, 0.0, np.nan], + [0.0, np.nan, 0.0], + ], + [ + [4.0, np.nan, 12.0], + [np.nan, 20.0, np.nan], + [28.0, np.nan, 36.0], + ], + [ + [14.0, 37.0, 42.0], + [74.0, 70.0, 111.0], + [98.0, 148.0, 126.0], + ], + [ + [7.0, np.nan, 21.0], + [np.nan, 35.0, np.nan], + [49.0, np.nan, 63.0], + ], + [ + [9.0, np.nan, 27.0], + [np.nan, 45.0, np.nan], + [63.0, np.nan, 81.0], + ], + [ + [10.5, np.nan, 31.5], + [np.nan, 52.5, np.nan], + [73.5, np.nan, 94.5], + ], + [ + [13.0, np.nan, 39.0], + [np.nan, 65.0, np.nan], + [91.0, np.nan, 117.0], + ], + [ + [15.0, np.nan, 45.0], + [np.nan, 75.0, np.nan], + [105.0, np.nan, 135.0], + ], + [ + [np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan], + [np.nan, np.nan, np.nan], + ], + ] + ) + aggregateby_cube = self.cube_multi_masked.aggregated_by( + ["height", "level"], + iris.analysis.MEAN, + weights=self.weights_multi, + ) + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi_missing.cml"), + ) + self.assertMaskedArrayAlmostEqual( + aggregateby_cube.data, + weighted_multi_expected, + ) + + def test_returned_true_single(self): + aggregateby_output = self.cube_single.aggregated_by( + "height", + iris.analysis.MEAN, + returned=True, + weights=self.weights_single, + ) + self.assertTrue(isinstance(aggregateby_output, tuple)) + + aggregateby_cube = aggregateby_output[0] + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_single.cml"), + ) + + aggregateby_weights = aggregateby_output[1] + expected_weights = np.array( + [ + [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[4.0, 4.0, 4.0], [4.0, 4.0, 4.0], [4.0, 4.0, 4.0]], + [[5.0, 5.0, 5.0], [5.0, 5.0, 5.0], [5.0, 5.0, 5.0]], + [[6.0, 6.0, 6.0], [6.0, 6.0, 6.0], [6.0, 6.0, 6.0]], + [[7.0, 7.0, 7.0], [7.0, 7.0, 7.0], [7.0, 7.0, 7.0]], + [[8.0, 8.0, 8.0], [8.0, 8.0, 8.0], [8.0, 8.0, 8.0]], + ] + ) + np.testing.assert_almost_equal(aggregateby_weights, expected_weights) + + def test_returned_true_multi(self): + aggregateby_output = self.cube_multi.aggregated_by( + ["height", "level"], + iris.analysis.MEAN, + returned=True, + weights=self.weights_multi, + ) + self.assertTrue(isinstance(aggregateby_output, tuple)) + + aggregateby_cube = aggregateby_output[0] + self.assertCML( + aggregateby_cube, + ("analysis", "aggregated_by", "weighted_multi.cml"), + ) + + aggregateby_weights = aggregateby_output[1] + expected_weights = np.array( + [ + [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[3.0, 3.0, 3.0], [3.0, 3.0, 3.0], [3.0, 3.0, 3.0]], + [[3.0, 3.0, 3.0], [3.0, 3.0, 3.0], [3.0, 3.0, 3.0]], + [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[2.0, 2.0, 2.0], [2.0, 2.0, 2.0], [2.0, 2.0, 2.0]], + [[3.0, 3.0, 3.0], [3.0, 3.0, 3.0], [3.0, 3.0, 3.0]], + [[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]], + [[2.0, 2.0, 2.0], [2.0, 2.0, 2.0], [2.0, 2.0, 2.0]], + ] + ) + np.testing.assert_almost_equal(aggregateby_weights, expected_weights) + + def test_returned_fails_with_non_weighted_aggregator(self): self.assertRaises( - ValueError, + TypeError, self.cube_single.aggregated_by, "height", - iris.analysis.MEAN, + iris.analysis.MAX, returned=True, ) + + def test_weights_fail_with_non_weighted_aggregator(self): self.assertRaises( - ValueError, + TypeError, self.cube_single.aggregated_by, "height", - iris.analysis.MEAN, - weights=[1, 2, 3, 4, 5], + iris.analysis.MAX, + weights=self.weights_single, ) diff --git a/lib/iris/tests/test_analysis.py b/lib/iris/tests/test_analysis.py index a7a7e3a0e2..e95978a371 100644 --- a/lib/iris/tests/test_analysis.py +++ b/lib/iris/tests/test_analysis.py @@ -1662,5 +1662,43 @@ def test_mean_with_weights(self): self.assertArrayAlmostEqual(expected_result, res_cube.data) +class TestCreateWeightedAggregatorFn(tests.IrisTest): + @staticmethod + def aggregator_fn(data, axis, **kwargs): + return (data, axis, kwargs) + + def test_no_weights_supplied(self): + aggregator_fn = iris.analysis.create_weighted_aggregator_fn( + self.aggregator_fn, 42, test_kwarg="test" + ) + output = aggregator_fn("dummy_array", None) + self.assertEqual(len(output), 3) + self.assertEqual(output[0], "dummy_array") + self.assertEqual(output[1], 42) + self.assertEqual(output[2], {"test_kwarg": "test"}) + + def test_weights_supplied(self): + aggregator_fn = iris.analysis.create_weighted_aggregator_fn( + self.aggregator_fn, 42, test_kwarg="test" + ) + output = aggregator_fn("dummy_array", "w") + self.assertEqual(len(output), 3) + self.assertEqual(output[0], "dummy_array") + self.assertEqual(output[1], 42) + self.assertEqual(output[2], {"test_kwarg": "test", "weights": "w"}) + + def test_weights_in_kwargs(self): + kwargs = {"test_kwarg": "test", "weights": "ignored"} + aggregator_fn = iris.analysis.create_weighted_aggregator_fn( + self.aggregator_fn, 42, **kwargs + ) + output = aggregator_fn("dummy_array", "w") + self.assertEqual(len(output), 3) + self.assertEqual(output[0], "dummy_array") + self.assertEqual(output[1], 42) + self.assertEqual(output[2], {"test_kwarg": "test", "weights": "w"}) + self.assertEqual(kwargs, {"test_kwarg": "test", "weights": "ignored"}) + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/test_lazy_aggregate_by.py b/lib/iris/tests/test_lazy_aggregate_by.py new file mode 100644 index 0000000000..d1ebc9a36a --- /dev/null +++ b/lib/iris/tests/test_lazy_aggregate_by.py @@ -0,0 +1,48 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +import unittest + +from iris._lazy_data import as_lazy_data +from iris.tests import test_aggregate_by + + +# Simply redo the tests of test_aggregate_by.py with lazy data +class TestLazyAggregateBy(test_aggregate_by.TestAggregateBy): + def setUp(self): + super().setUp() + + self.cube_single.data = as_lazy_data(self.cube_single.data) + self.cube_multi.data = as_lazy_data(self.cube_multi.data) + self.cube_single_masked.data = as_lazy_data( + self.cube_single_masked.data + ) + self.cube_multi_masked.data = as_lazy_data(self.cube_multi_masked.data) + self.cube_easy.data = as_lazy_data(self.cube_easy.data) + self.cube_easy_weighted.data = as_lazy_data( + self.cube_easy_weighted.data + ) + + assert self.cube_single.has_lazy_data() + assert self.cube_multi.has_lazy_data() + assert self.cube_single_masked.has_lazy_data() + assert self.cube_multi_masked.has_lazy_data() + assert self.cube_easy.has_lazy_data() + assert self.cube_easy_weighted.has_lazy_data() + + def tearDown(self): + super().tearDown() + + # Note: weighted easy cube is not expected to have lazy data since + # WPERCENTILE is not lazy. + assert self.cube_single.has_lazy_data() + assert self.cube_multi.has_lazy_data() + assert self.cube_single_masked.has_lazy_data() + assert self.cube_multi_masked.has_lazy_data() + assert self.cube_easy.has_lazy_data() + + +if __name__ == "__main__": + unittest.main() diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 7d56b505bd..4954e7a82a 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -18,7 +18,7 @@ from iris._lazy_data import as_lazy_data import iris.analysis -from iris.analysis import MEAN, Aggregator, WeightedAggregator +from iris.analysis import MEAN, SUM, Aggregator, WeightedAggregator import iris.aux_factory from iris.aux_factory import HybridHeightFactory from iris.common.metadata import BaseMetadata @@ -725,6 +725,23 @@ def setUp(self): self.mock_agg.lazy_func = None self.mock_agg.post_process = mock.Mock(side_effect=lambda x, y, z: x) + self.mock_weighted_agg = mock.Mock(spec=WeightedAggregator) + self.mock_weighted_agg.cell_method = [] + + def mock_weighted_aggregate(*_, **kwargs): + if kwargs.get("returned", False): + return (mock.Mock(dtype="object"), mock.Mock(dtype="object")) + return mock.Mock(dtype="object") + + self.mock_weighted_agg.aggregate = mock.Mock( + side_effect=mock_weighted_aggregate + ) + self.mock_weighted_agg.aggregate_shape = mock.Mock(return_value=()) + self.mock_weighted_agg.lazy_func = None + self.mock_weighted_agg.post_process = mock.Mock( + side_effect=lambda x, y, z, **kwargs: y + ) + self.ancillary_variable = AncillaryVariable( [0, 1, 2, 3], long_name="foo" ) @@ -732,6 +749,9 @@ def setUp(self): self.cell_measure = CellMeasure([0, 1, 2, 3], long_name="bar") self.cube.add_cell_measure(self.cell_measure, 0) + self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) + self.val_weights = np.ones_like(self.cube.data, dtype=np.float32) + def test_2d_coord_simple_agg(self): # For 2d coords, slices of aggregated coord should be the same as # aggregated slices. @@ -855,6 +875,136 @@ def test_ancillary_variables_and_cell_measures_removed(self): self.assertEqual(cube_agg.ancillary_variables(), []) self.assertEqual(cube_agg.cell_measures(), []) + def test_1d_weights(self): + self.cube.aggregated_by( + "simple_agg", self.mock_weighted_agg, weights=self.simple_weights + ) + + self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 2) + + # A simple mock.assert_called_with does not work due to ValueError: The + # truth value of an array with more than one element is ambiguous. Use + # a.any() or a.all() + call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] + np.testing.assert_array_equal( + call_1.args[0], + np.array( + [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], + ] + ), + ) + self.assertEqual(call_1.kwargs["axis"], 0) + np.testing.assert_array_almost_equal( + call_1.kwargs["weights"], + np.array( + [ + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ] + ), + ) + + call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] + np.testing.assert_array_equal( + call_2.args[0], + np.array( + [ + [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32], + [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], + ] + ), + ) + self.assertEqual(call_2.kwargs["axis"], 0) + np.testing.assert_array_almost_equal( + call_2.kwargs["weights"], + np.array( + [ + [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], + [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], + ] + ), + ) + + def test_2d_weights(self): + self.cube.aggregated_by( + "val", self.mock_weighted_agg, weights=self.val_weights + ) + + self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 3) + + # A simple mock.assert_called_with does not work due to ValueError: The + # truth value of an array with more than one element is ambiguous. Use + # a.any() or a.all() + call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] + np.testing.assert_array_equal( + call_1.args[0], + np.array( + [ + [0, 1, 2, 6, 7, 9], + [11, 12, 13, 17, 18, 20], + [22, 23, 24, 28, 29, 31], + [33, 34, 35, 39, 40, 42], + ] + ), + ) + self.assertEqual(call_1.kwargs["axis"], 1) + np.testing.assert_array_almost_equal( + call_1.kwargs["weights"], np.ones((4, 6)) + ) + + call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] + np.testing.assert_array_equal( + call_2.args[0], + np.array([[3, 4, 10], [14, 15, 21], [25, 26, 32], [36, 37, 43]]), + ) + self.assertEqual(call_2.kwargs["axis"], 1) + np.testing.assert_array_almost_equal( + call_2.kwargs["weights"], np.ones((4, 3)) + ) + + call_3 = self.mock_weighted_agg.aggregate.mock_calls[2] + np.testing.assert_array_equal( + call_3.args[0], np.array([[5, 8], [16, 19], [27, 30], [38, 41]]) + ) + self.assertEqual(call_3.kwargs["axis"], 1) + np.testing.assert_array_almost_equal( + call_3.kwargs["weights"], np.ones((4, 2)) + ) + + def test_returned(self): + output = self.cube.aggregated_by( + "simple_agg", self.mock_weighted_agg, returned=True + ) + + self.assertTrue(isinstance(output, tuple)) + self.assertEqual(len(output), 2) + self.assertEqual(output[0].shape, (2, 11)) + self.assertEqual(output[1].shape, (2, 11)) + + def test_fail_1d_weights_wrong_len(self): + wrong_weights = np.array([1.0, 2.0]) + msg = ( + r"1D weights must have the same length as the dimension that is " + r"aggregated, got 2, expected 11" + ) + with self.assertRaisesRegex(ValueError, msg): + self.cube.aggregated_by( + "val", self.mock_weighted_agg, weights=wrong_weights + ) + + def test_fail_weights_wrong_shape(self): + wrong_weights = np.ones((42, 1)) + msg = ( + r"Weights must either be 1D or have the same shape as the cube, " + r"got shape \(42, 1\) for weights, \(4, 11\) for cube" + ) + with self.assertRaisesRegex(ValueError, msg): + self.cube.aggregated_by( + "val", self.mock_weighted_agg, weights=wrong_weights + ) + class Test_aggregated_by__lazy(tests.IrisTest): def setUp(self): @@ -905,6 +1055,9 @@ def setUp(self): self.cube.add_aux_coord(val_coord, 1) self.cube.add_aux_coord(label_coord, 1) + self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) + self.val_weights = 2.0 * np.ones(self.cube.shape, dtype=np.float32) + def test_agg_by_label__lazy(self): # Aggregate a cube on a string coordinate label where label # and val entries are not in step; the resulting cube has a val @@ -963,6 +1116,104 @@ def test_single_string_aggregation__lazy(self): self.assertArrayEqual(result.data, means) self.assertFalse(result.has_lazy_data()) + def test_1d_weights__lazy(self): + self.assertTrue(self.cube.has_lazy_data()) + + cube_agg = self.cube.aggregated_by( + "simple_agg", SUM, weights=self.simple_weights + ) + + self.assertTrue(self.cube.has_lazy_data()) + self.assertTrue(cube_agg.has_lazy_data()) + self.assertEqual(cube_agg.shape, (2, 11)) + + row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + row_1 = [ + 110.0, + 114.0, + 118.0, + 122.0, + 126.0, + 130.0, + 134.0, + 138.0, + 142.0, + 146.0, + 150.0, + ] + np.testing.assert_array_almost_equal( + cube_agg.data, np.array([row_0, row_1]) + ) + + def test_2d_weights__lazy(self): + self.assertTrue(self.cube.has_lazy_data()) + + cube_agg = self.cube.aggregated_by( + "val", SUM, weights=self.val_weights + ) + + self.assertTrue(self.cube.has_lazy_data()) + self.assertTrue(cube_agg.has_lazy_data()) + + self.assertEqual(cube_agg.shape, (4, 3)) + np.testing.assert_array_almost_equal( + cube_agg.data, + np.array( + [ + [50.0, 34.0, 26.0], + [182.0, 100.0, 70.0], + [314.0, 166.0, 114.0], + [446.0, 232.0, 158.0], + ] + ), + ) + + def test_returned__lazy(self): + self.assertTrue(self.cube.has_lazy_data()) + + output = self.cube.aggregated_by( + "simple_agg", SUM, weights=self.simple_weights, returned=True + ) + + self.assertTrue(self.cube.has_lazy_data()) + + self.assertTrue(isinstance(output, tuple)) + self.assertEqual(len(output), 2) + + cube = output[0] + self.assertTrue(isinstance(cube, Cube)) + self.assertTrue(cube.has_lazy_data()) + self.assertEqual(cube.shape, (2, 11)) + row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + row_1 = [ + 110.0, + 114.0, + 118.0, + 122.0, + 126.0, + 130.0, + 134.0, + 138.0, + 142.0, + 146.0, + 150.0, + ] + np.testing.assert_array_almost_equal( + cube.data, np.array([row_0, row_1]) + ) + + weights = output[1] + self.assertEqual(weights.shape, (2, 11)) + np.testing.assert_array_almost_equal( + weights, + np.array( + [ + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0], + ] + ), + ) + class Test_rolling_window(tests.IrisTest): def setUp(self): From d31d6f5b6c1af5353b217da2b59409f11179d90b Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 28 Apr 2022 15:17:57 +0100 Subject: [PATCH 090/319] Addaux speedup (#4718) * Attempt faster add-aux-fact * Added whatsnew draft. * Put PR ref into whatsnew. * Review changes. --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/cube.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 1c64610f89..b29f3a6379 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -98,6 +98,10 @@ This document explains the changes made to Iris for this release :class:`~iris.coords.DimCoord` created using :meth:`~iris.coords.DimCoord.from_regular`. (:pull:`4698`) +#. `@pp-mo`_ made :meth:`~iris.cube.Cube.add_aux_factory` faster. + (:pull:`4718`) + + 🔥 Deprecations =============== diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 94aa6ff4f1..b91555968c 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -1188,7 +1188,16 @@ def add_aux_factory(self, aux_factory): "Factory must be a subclass of " "iris.aux_factory.AuxCoordFactory." ) - cube_coords = self.coords() + + # Get all 'real' coords (i.e. not derived ones) : use private data + # rather than cube.coords(), as that is quite slow. + def coordsonly(coords_and_dims): + return [coord for coord, dims in coords_and_dims] + + cube_coords = coordsonly(self._dim_coords_and_dims) + coordsonly( + self._aux_coords_and_dims + ) + for dependency in aux_factory.dependencies: ref_coord = aux_factory.dependencies[dependency] if ref_coord is not None and ref_coord not in cube_coords: From e3f3bb2d2027aab569db57b7368e0fd1447b03e5 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 28 Apr 2022 16:21:47 +0100 Subject: [PATCH 091/319] Remove time_return benchmarks. (#4717) --- benchmarks/benchmarks/aux_factory.py | 4 ---- benchmarks/benchmarks/coords.py | 4 ---- benchmarks/benchmarks/cube.py | 7 ------- benchmarks/benchmarks/experimental/ugrid/__init__.py | 4 ---- 4 files changed, 19 deletions(-) diff --git a/benchmarks/benchmarks/aux_factory.py b/benchmarks/benchmarks/aux_factory.py index 45bfa1b515..4cc4f6c70a 100644 --- a/benchmarks/benchmarks/aux_factory.py +++ b/benchmarks/benchmarks/aux_factory.py @@ -44,10 +44,6 @@ def time_create(self): specified in the subclass.""" self.create() - def time_return(self): - """Return an instance of the benchmarked factory.""" - self.factory - class HybridHeightFactory(FactoryCommon): def setup(self): diff --git a/benchmarks/benchmarks/coords.py b/benchmarks/benchmarks/coords.py index 5cea1e1e2e..3107dcf077 100644 --- a/benchmarks/benchmarks/coords.py +++ b/benchmarks/benchmarks/coords.py @@ -51,10 +51,6 @@ def time_create(self): specified in the subclass.""" self.create() - def time_return(self): - """Return an instance of the benchmarked coord.""" - self.component - class DimCoord(CoordCommon): def setup(self): diff --git a/benchmarks/benchmarks/cube.py b/benchmarks/benchmarks/cube.py index 8a12391684..5889ce872b 100644 --- a/benchmarks/benchmarks/cube.py +++ b/benchmarks/benchmarks/cube.py @@ -68,10 +68,6 @@ def time_add(self): general_cube_copy = general_cube.copy(data=data_2d) self.add_method(general_cube_copy, *self.add_args) - def time_return(self): - """Return a cube that includes an instance of the benchmarked component.""" - self.cube - class Cube: def time_basic(self): @@ -206,9 +202,6 @@ def time_add(self, n_faces): def time_remove(self, n_faces): self.cube.remove_coord(self.mesh_coord) - def time_return(self, n_faces): - _ = self.cube - class Merge: def setup(self): diff --git a/benchmarks/benchmarks/experimental/ugrid/__init__.py b/benchmarks/benchmarks/experimental/ugrid/__init__.py index 3e5f1ae440..2f9bb04e35 100644 --- a/benchmarks/benchmarks/experimental/ugrid/__init__.py +++ b/benchmarks/benchmarks/experimental/ugrid/__init__.py @@ -47,10 +47,6 @@ def time_create(self, *params): specified in the subclass.""" self.create() - def time_return(self, *params): - """Return an instance of the benchmarked object.""" - _ = self.object - class Connectivity(UGridCommon): def setup(self, n_faces): From 1d0dcf5525ea98fc9d75db84edbcba3c5ef9c8a1 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:32:59 +0100 Subject: [PATCH 092/319] Credit reviewer in what's new contributions. (#4705) * Credit reviewer in what's new contributions. * Correct example link in what's new docs. Co-authored-by: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Co-authored-by: Will Benfold <69585101+wjbenfold@users.noreply.github.com> --- .../documenting/whats_new_contributions.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/developers_guide/documenting/whats_new_contributions.rst b/docs/src/developers_guide/documenting/whats_new_contributions.rst index c9dbe04b71..1a820ab24e 100644 --- a/docs/src/developers_guide/documenting/whats_new_contributions.rst +++ b/docs/src/developers_guide/documenting/whats_new_contributions.rst @@ -69,6 +69,9 @@ The required content, in order, is as follows: user name. Link the name to their GitHub profile. E.g. ```@tkknight `_ changed...`` + * Bigger changes take a lot of effort to review, too! Make sure you credit + the reviewer(s) where appropriate. + * The new/changed behaviour * Context to the change. Possible examples include: what this fixes, why @@ -82,8 +85,9 @@ The required content, in order, is as follows: For example:: - #. `@tkknight `_ changed changed argument ``x`` - to be optional in :class:`~iris.module.class` and + #. `@tkknight `_ and + `@trexfeathers `_ (reviewer) changed + argument ``x`` to be optional in :class:`~iris.module.class` and :meth:`iris.module.method`. This allows greater flexibility as requested in :issue:`9999`. (:pull:`1111`, :pull:`9999`) From 3f3604fb48849495621c9f0bcc7c9dbec47880f9 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Fri, 29 Apr 2022 17:02:20 +0100 Subject: [PATCH 093/319] Change Cell equality to equate nans (fixes #4681) (#4701) * nans in cells satisfy equality * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * address review comments Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 6 ++++ lib/iris/coords.py | 13 +++++++++ .../tests/integration/merge/test_merge.py | 29 ++++++++++++++++++- lib/iris/tests/unit/coords/test_Cell.py | 8 +++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index b29f3a6379..383d029ccf 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -84,6 +84,12 @@ 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`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 0a1aecb983..d176a0bb20 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -1353,14 +1353,27 @@ def __eq__(self, other): 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] ) diff --git a/lib/iris/tests/integration/merge/test_merge.py b/lib/iris/tests/integration/merge/test_merge.py index f5f92a7a7d..8c221d7201 100644 --- a/lib/iris/tests/integration/merge/test_merge.py +++ b/lib/iris/tests/integration/merge/test_merge.py @@ -12,7 +12,9 @@ # before importing anything else. import iris.tests as tests # isort:skip -from iris.coords import DimCoord +import numpy as np + +from iris.coords import AuxCoord, DimCoord from iris.cube import Cube, CubeList @@ -33,5 +35,30 @@ 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() diff --git a/lib/iris/tests/unit/coords/test_Cell.py b/lib/iris/tests/unit/coords/test_Cell.py index 650f9ded6c..a161d7df9a 100644 --- a/lib/iris/tests/unit/coords/test_Cell.py +++ b/lib/iris/tests/unit/coords/test_Cell.py @@ -146,6 +146,14 @@ 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_contains_point(tests.IrisTest): def test_datetimelike_bounded_cell(self): From d565844f22a400e08e395a9e3f453dc13b5edd86 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Fri, 29 Apr 2022 17:48:39 +0100 Subject: [PATCH 094/319] Netcdf saving: workaround known netcdf bug. (#4407) * Netcdf saving: workaround known netcdf bug. * Fix netcdf dim renaming for anonymous dims. * Fixes to test-mesh generation to preserve save-load roundtrips. * Added tests for all the different places where code was fixed. * Tidy some formatting and comments. * Fix rebase error? * Review changes. --- lib/iris/fileformats/netcdf.py | 63 ++++++-- lib/iris/tests/stock/mesh.py | 6 +- .../unit/fileformats/netcdf/test_save.py | 147 +++++++++++++++++- 3 files changed, 201 insertions(+), 15 deletions(-) diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 4818f621c8..d4b7c84c44 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -1376,6 +1376,8 @@ def _create_cf_dimensions( unlimited_dim_names.append(dim_name) for dim_name in dimension_names: + # NOTE: these dim-names have been chosen by _get_dim_names, and + # were already checked+fixed to avoid any name collisions. if dim_name not in self._dataset.dimensions: if dim_name in unlimited_dim_names: size = None @@ -1468,6 +1470,10 @@ def _add_mesh(self, cube_or_mesh): last_dim = f"{cf_mesh_name}_{loc_from}_N_{loc_to}s" # Create if it does not already exist. if last_dim not in self._dataset.dimensions: + while last_dim in self._dataset.variables: + # Also avoid collision with variable names. + # See '_get_dim_names' for reason. + last_dim = self._increment_name(last_dim) length = conn.shape[1 - conn.location_axis] self._dataset.createDimension(last_dim, length) @@ -1869,8 +1875,19 @@ def record_dimension(names_list, dim_name, length, matching_coords=[]): assert dim_name is not None # Ensure it is a valid variable name. dim_name = self.cf_valid_var_name(dim_name) - # Disambiguate if it matches an existing one. - while dim_name in self._existing_dim: + # Disambiguate if it has the same name as an existing + # dimension. + # NOTE: *OR* if it matches the name of an existing file + # variable. Because there is a bug ... + # See https://github.com/Unidata/netcdf-c/issues/1772 + # N.B. the workarounds here *ONLY* function because the + # caller (write) will not create any more variables + # in between choosing dim names (here), and creating + # the new dims (via '_create_cf_dimensions'). + while ( + dim_name in self._existing_dim + or dim_name in self._dataset.variables + ): dim_name = self._increment_name(dim_name) # Record the new dimension. @@ -1915,9 +1932,15 @@ def record_dimension(names_list, dim_name, length, matching_coords=[]): dim_name = self._get_coord_variable_name( cube, coord ) + # Disambiguate if it has the same name as an + # existing dimension. + # OR if it matches an existing file variable name. + # NOTE: check against variable names is needed + # because of a netcdf bug ... see note in the + # mesh dimensions block above. while ( dim_name in self._existing_dim - or dim_name in self._name_coord_map.names + or dim_name in self._dataset.variables ): dim_name = self._increment_name(dim_name) @@ -1925,16 +1948,18 @@ def record_dimension(names_list, dim_name, length, matching_coords=[]): # No CF-netCDF coordinates describe this data dimension. # Make up a new, distinct dimension name dim_name = f"dim{dim}" - if dim_name in self._existing_dim: - # Increment name if conflicted with one already existing. - if self._existing_dim[dim_name] != cube.shape[dim]: - while ( - dim_name in self._existing_dim - and self._existing_dim[dim_name] - != cube.shape[dim] - or dim_name in self._name_coord_map.names - ): - dim_name = self._increment_name(dim_name) + # Increment name if conflicted with one already existing + # (or planned) + # NOTE: check against variable names is needed because + # of a netcdf bug ... see note in the mesh dimensions + # block above. + while ( + dim_name in self._existing_dim + and ( + self._existing_dim[dim_name] != cube.shape[dim] + ) + ) or dim_name in self._dataset.variables: + dim_name = self._increment_name(dim_name) # Record the dimension. record_dimension( @@ -2065,6 +2090,12 @@ def _create_cf_bounds(self, coord, cf_var, cf_name): if bounds_dimension_name not in self._dataset.dimensions: # Create the bounds dimension with the appropriate extent. + while bounds_dimension_name in self._dataset.variables: + # Also avoid collision with variable names. + # See '_get_dim_names' for reason. + bounds_dimension_name = self._increment_name( + bounds_dimension_name + ) self._dataset.createDimension(bounds_dimension_name, n_bounds) boundsvar_name = "{}_{}".format(cf_name, varname_extra) @@ -2345,6 +2376,12 @@ def _create_generic_cf_array_var( # Determine whether to create the string length dimension. if string_dimension_name not in self._dataset.dimensions: + while string_dimension_name in self._dataset.variables: + # Also avoid collision with variable names. + # See '_get_dim_names' for reason. + string_dimension_name = self._increment_name( + string_dimension_name + ) self._dataset.createDimension( string_dimension_name, string_dimension_depth ) diff --git a/lib/iris/tests/stock/mesh.py b/lib/iris/tests/stock/mesh.py index ca15ee1c97..da226a3790 100644 --- a/lib/iris/tests/stock/mesh.py +++ b/lib/iris/tests/stock/mesh.py @@ -61,7 +61,11 @@ def sample_mesh(n_nodes=None, n_faces=None, n_edges=None, lazy_values=False): units="degrees_east", long_name="long-name", var_name="var-name", - attributes={"a": 1, "b": "c"}, + attributes={ + # N.B. cast this so that a save-load roundtrip preserves it + "a": np.int64(1), + "b": "c", + }, ) node_y = AuxCoord(1200 + arr.arange(n_nodes), standard_name="latitude") diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_save.py b/lib/iris/tests/unit/fileformats/netcdf/test_save.py index 830d8c5e52..669a3c4137 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_save.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_save.py @@ -9,16 +9,21 @@ # importing anything else. import iris.tests as tests # isort:skip +from pathlib import Path +from shutil import rmtree +from tempfile import mkdtemp from unittest import mock import netCDF4 as nc import numpy as np import iris -from iris.coords import DimCoord +from iris.coords import AuxCoord, DimCoord from iris.cube import Cube, CubeList +from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD from iris.fileformats.netcdf import CF_CONVENTIONS_VERSION, save from iris.tests.stock import lat_lon_cube +from iris.tests.stock.mesh import sample_mesh_cube class Test_conventions(tests.IrisTest): @@ -211,5 +216,145 @@ def test_multi_wrong_length(self): save(cubes, "dummy.nc", fill_value=fill_values) +class Test_HdfSaveBug(tests.IrisTest): + """ + Check for a known problem with netcdf4. + + If you create dimension with the same name as an existing variable, there + is a specific problem, relating to HDF so limited to netcdf-4 formats. + See : https://github.com/Unidata/netcdf-c/issues/1772 + + In all these testcases, a straightforward translation to the file would be + able to save [cube_2, cube_1], but *not* [cube_1, cube_2], + because the latter creates a dim of the same name as the 'cube_1' data + variable. + + Here, we are testing the specific workarounds in Iris netcdf save which + avoids that problem. + Unfortunately, owing to the complexity of the iris.fileformats.netcdf.Saver + code, there are several separate places where this had to be fixed. + + N.B. we also check that the data (mostly) survives a save-load roundtrip. + To identify the read-back cubes with the originals, we use var-names, + which works because the save code opts to adjust dimension names _instead_. + + """ + + def _check_save_and_reload(self, cubes): + tempdir = Path(mkdtemp()) + filepath = tempdir / "tmp.nc" + try: + # Save the given cubes. + save(cubes, filepath) + + # Load them back for roundtrip testing. + with PARSE_UGRID_ON_LOAD.context(): + new_cubes = iris.load(str(filepath)) + + # There should definitely still be the same number of cubes. + self.assertEqual(len(new_cubes), len(cubes)) + + # Get results in the input order, matching by var_names. + result = [new_cubes.extract_cube(cube.var_name) for cube in cubes] + + # Check that input + output match cube-for-cube. + # NB in this codeblock, before we destroy the temporary file. + for cube_in, cube_out in zip(cubes, result): + # Using special tolerant equivalence-check. + self.assertSameCubes(cube_in, cube_out) + + finally: + rmtree(tempdir) + + # Return result cubes for any additional checks. + return result + + def assertSameCubes(self, cube1, cube2): + """ + A special tolerant cube compare. + + Ignore any 'Conventions' attributes. + Ignore all var-names. + + """ + + def clean_cube(cube): + cube = cube.copy() # dont modify the original + # Remove any 'Conventions' attributes + cube.attributes.pop("Conventions", None) + # Remove var-names (as original mesh components wouldn't have them) + cube.var_name = None + for coord in cube.coords(): + coord.var_name = None + mesh = cube.mesh + if mesh: + mesh.var_name = None + for component in mesh.coords() + mesh.connectivities(): + component.var_name = None + + return cube + + self.assertEqual(clean_cube(cube1), clean_cube(cube2)) + + def test_dimcoord_varname_collision(self): + cube_2 = Cube([0, 1], var_name="cube_2") + x_dim = DimCoord([0, 1], long_name="dim_x", var_name="dimco_name") + cube_2.add_dim_coord(x_dim, 0) + # First cube has a varname which collides with the dimcoord. + cube_1 = Cube([0, 1], long_name="cube_1", var_name="dimco_name") + # Test save + loadback + reload_1, reload_2 = self._check_save_and_reload([cube_1, cube_2]) + # As re-loaded, the coord will have a different varname. + self.assertEqual(reload_2.coord("dim_x").var_name, "dimco_name_0") + + def test_anonymous_dim_varname_collision(self): + # Second cube is going to name an anonymous dim. + cube_2 = Cube([0, 1], var_name="cube_2") + # First cube has a varname which collides with the dim-name. + cube_1 = Cube([0, 1], long_name="cube_1", var_name="dim0") + # Add a dimcoord to prevent the *first* cube having an anonymous dim. + x_dim = DimCoord([0, 1], long_name="dim_x", var_name="dimco_name") + cube_1.add_dim_coord(x_dim, 0) + # Test save + loadback + self._check_save_and_reload([cube_1, cube_2]) + + def test_bounds_dim_varname_collision(self): + cube_2 = Cube([0, 1], var_name="cube_2") + x_dim = DimCoord([0, 1], long_name="dim_x", var_name="dimco_name") + x_dim.guess_bounds() + cube_2.add_dim_coord(x_dim, 0) + # First cube has a varname which collides with the bounds dimension. + cube_1 = Cube([0], long_name="cube_1", var_name="bnds") + # Test save + loadback + self._check_save_and_reload([cube_1, cube_2]) + + def test_string_dim_varname_collision(self): + cube_2 = Cube([0, 1], var_name="cube_2") + # NOTE: it *should* be possible for a cube with string data to cause + # this collision, but cubes with string data are currently not working. + # See : https://github.com/SciTools/iris/issues/4412 + x_dim = AuxCoord( + ["this", "that"], long_name="dim_x", var_name="string_auxco" + ) + cube_2.add_aux_coord(x_dim, 0) + cube_1 = Cube([0], long_name="cube_1", var_name="string4") + # Test save + loadback + self._check_save_and_reload([cube_1, cube_2]) + + def test_mesh_location_dim_varname_collision(self): + cube_2 = sample_mesh_cube() + cube_2.var_name = "cube_2" # Make it identifiable + cube_1 = Cube([0], long_name="cube_1", var_name="Mesh2d_node") + # Test save + loadback + self._check_save_and_reload([cube_1, cube_2]) + + def test_connectivity_dim_varname_collision(self): + cube_2 = sample_mesh_cube() + cube_2.var_name = "cube_2" # Make it identifiable + cube_1 = Cube([0], long_name="cube_1", var_name="Mesh_2d_face_N_nodes") + # Test save + loadback + self._check_save_and_reload([cube_1, cube_2]) + + if __name__ == "__main__": tests.main() From 3fd832eb70c6495d0356a97f54b55f4fb93310ed Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 3 May 2022 09:01:18 +0100 Subject: [PATCH 095/319] Updated environment lockfiles (#4724) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 38 ++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index f3f1200d91..0518a475ae 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -9,15 +9,15 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed3 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_15.tar.bz2#7c2c0cd937b707aa46f3369d693a1f29 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_15.tar.bz2#4ffda9c4352880439042fbb8a9863e8c +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_16.tar.bz2#ff034874d96195a5c5be34200689b5b7 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_16.tar.bz2#8cfd1cd3273ff187be91b868ddf9a636 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_15.tar.bz2#23b9afaf5975e1db0f59d1251425f5d5 -https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_15.tar.bz2#5b7bff215ad9f30325dbe86dd983cb49 -https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-1_gnu.tar.bz2#561e277319a41d4f24f5c05a9ef63c04 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_16.tar.bz2#27974aad841c189854df09426b1b9fac +https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_16.tar.bz2#e935fb0c92c6ffb63c736a2012604d72 +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_15.tar.bz2#8041e476e66fcde71f0aa3d614e0efcf +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_16.tar.bz2#71feb63a30085cbce51847d5ef1f769d https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a @@ -72,7 +72,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.28-haf5c9bc_4.tar.bz2#8c336a182a644e58f5ddd5a08a670489 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_0.tar.bz2#69c2d220ca1b41a881928a91a6cbbd93 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 @@ -92,8 +92,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.28-h28c427c_4.tar.bz2#f3d459b87692be9d6f4aaa5fd69a6b82 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.2-h4ff8645_0.tar.bz2#9d4b7a876d5531fdbb682ed526f8b847 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_0.tar.bz2#3314d5f8d6c831514363625cb28c8ff1 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.3-h4ff8645_0.tar.bz2#987ea6cb07fd358ad52dd1c938367967 https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 @@ -103,7 +103,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.1-hd4edc92_1.tar.bz2#ebebb56f78dd7518dcc94d6c26aa2249 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.82.0-h7bff187_0.tar.bz2#fa26f833ca8796ad44f9561c5402013d +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.0-h7bff187_0.tar.bz2#e2d939fa77fe69cd50f751961f17786a https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b @@ -116,7 +116,7 @@ https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#eb https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f -https://conda.anaconda.org/conda-forge/linux-64/curl-7.82.0-h7bff187_0.tar.bz2#631777dcd00a2714cf0c415ffe40c480 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.0-h7bff187_0.tar.bz2#81e39fb3ae82be7e8d2dd7046f393588 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 @@ -157,14 +157,14 @@ https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_1.tar.bz2#8fb11f3e7218a51d26c4516fc6f53fea +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_1.tar.bz2#21862e22238907d485e460f9c2e9ae4d https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_1.tar.bz2#34c284cb94bd8c5118ccfe6d6fc3dd3f https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h319fa22_1.tar.bz2#7583fbaea3648f692c0c019254bc196c +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h1d589f8_2.tar.bz2#6aa7762a9c8d2280e284dc3df9b2fea8 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h99721a1_2.tar.bz2#e6ed43e96f813b184fe9bb677476e56d https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.2-py38h9776b28_0.tar.bz2#bd527d652ba06fb2aae61640bcf7c435 @@ -182,8 +182,8 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.1-pyhd8ed1ab_0.tar.bz2#5fee12b7b8acb170c47d425d3b069f3c -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.1-py38h0a891b7_0.tar.bz2#e1418611bc0977d91f91bad7608b5b56 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.2-pyhd8ed1ab_0.tar.bz2#6d57147688f325bb6eb7bbdac6ec662a +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.0-h40b6f09_0.tar.bz2#017b20e7e98860f0bfa7492ce16390a7 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.1-pyhd8ed1ab_0.tar.bz2#40b3f446c61729cdd21ed9d85627df6e https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 @@ -191,8 +191,8 @@ https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_1.tar.bz2#0cdb1150994e0c449b25d6f92b69eda2 https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff -https://conda.anaconda.org/conda-forge/noarch/pygments-2.11.2-pyhd8ed1ab_0.tar.bz2#caef60540e2239e27bf62569a5015e3b -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.0-py38h5b5ac8f_3.tar.bz2#338813a88e4e857b0a7581fa596124b5 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe @@ -202,7 +202,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 -https://conda.anaconda.org/conda-forge/noarch/identify-2.4.12-pyhd8ed1ab_0.tar.bz2#9f7b07b3f40905fcf600aacf63ae0c41 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.0-pyhd8ed1ab_0.tar.bz2#030048290bb28d917f82830d6e01d111 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 From 66205e78af9c19d5cb0fa2c25806a7de0182e3ec Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Tue, 3 May 2022 10:03:13 +0100 Subject: [PATCH 096/319] MAX_RUN into docs (#4726) --- docs/src/whatsnew/latest.rst | 3 +++ lib/iris/analysis/__init__.py | 1 + 2 files changed, 4 insertions(+) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 383d029ccf..e611110ebe 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -49,6 +49,9 @@ This document explains the changes made to Iris for this release ``standard_parallel`` and ``scale_factor_at_projection_origin`` to :class:`~iris.coord_system.Mercator`. (:issue:`3844`, :pull:`4609`) +#. `@wjbenfold`_ and `@stephenworsley`_ (reviewer) added a maximum run length + aggregator (:class:`~iris.analysis.MAX_RUN`). (:pull:`4676`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 63b9173e6c..cf035d568f 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -64,6 +64,7 @@ "HMEAN", "Linear", "MAX", + "MAX_RUN", "MEAN", "MEDIAN", "MIN", From 7db0bdb6acad80537ac33d3b051155dabfeb5e39 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Tue, 3 May 2022 12:21:45 +0100 Subject: [PATCH 097/319] Datum support in Iris (#4704) * Support in helpers * coord_systems changes * More additions * Revert some FUTURE guards and condense datum application * Fixing tests and improving * More additions * Tests pass * transform_points test * Review fixes * In datums, unknown means None * Fix test * Suppress FutureWarning in the tests that were failing due to it * Review fixes * What's new * Extra test result --- docs/src/whatsnew/latest.rst | 10 +- lib/iris/__init__.py | 21 +- lib/iris/coord_systems.py | 136 +++++++------ .../fileformats/_nc_load_rules/helpers.py | 137 +++++--------- lib/iris/fileformats/netcdf.py | 2 + lib/iris/tests/integration/test_Datums.py | 53 ++++++ lib/iris/tests/integration/test_netcdf.py | 179 +++++++++++++++++- lib/iris/tests/test_coordsystem.py | 32 +++- .../nc_load_rules/actions/__init__.py | 19 +- .../unit/fileformats/netcdf/test_Saver.py | 4 +- requirements/ci/py38.yml | 1 + 11 files changed, 427 insertions(+), 167 deletions(-) create mode 100755 lib/iris/tests/integration/test_Datums.py diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index e611110ebe..d69c7576f4 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -37,7 +37,7 @@ This document explains the changes made to Iris for this release :pull:`4589`) #. `@wjbenfold`_ added support for ``false_easting`` and ``false_northing`` to - :class:`~iris.coord_system.Mercator`. (:issue:`3107`, :pull:`4524`) + :class:`~iris.coord_systems.Mercator`. (:issue:`3107`, :pull:`4524`) #. `@rcomer`_ and `@wjbenfold`_ (reviewer) implemented lazy aggregation for the :obj:`iris.analysis.PERCENTILE` aggregator. (:pull:`3901`) @@ -47,7 +47,13 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ added support for CF-compliant treatment of ``standard_parallel`` and ``scale_factor_at_projection_origin`` to - :class:`~iris.coord_system.Mercator`. (:issue:`3844`, :pull:`4609`) + :class:`~iris.coord_systems.Mercator`. (:issue:`3844`, :pull:`4609`) + +#. `@wjbenfold`_ added support datums associated with coordinate systems (e.g. + :class:`~iris.coord_systems.GeogCS` other subclasses of + :class:`~iris.coord_systems.CoordSystem`). Loading of datum information from + a netCDF file only happens when the :obj:`iris.FUTURE.datum_support` flag is + set. (:issue:`4619`, :pull:`4704`) #. `@wjbenfold`_ and `@stephenworsley`_ (reviewer) added a maximum run length aggregator (:class:`~iris.analysis.MAX_RUN`). (:pull:`4676`) diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 3e847acad7..7442ff1173 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -136,7 +136,7 @@ def callback(cube, field, filename): class Future(threading.local): """Run-time configuration controller.""" - def __init__(self): + def __init__(self, datum_support=False): """ A container for run-time options controls. @@ -151,22 +151,24 @@ def __init__(self): .. note:: iris.FUTURE.example_future_flag does not exist. It is provided - as an example because there are currently no flags in - iris.Future. + as an example. """ - # The flag 'example_future_flag' is provided as a future reference - # for the structure of this class. + # The flag 'example_future_flag' is provided as a reference for the + # structure of this class. + # + # Note that self.__dict__ is used explicitly due to the manner in which + # __setattr__ is overridden. # # self.__dict__['example_future_flag'] = example_future_flag - pass + self.__dict__["datum_support"] = datum_support def __repr__(self): # msg = ('Future(example_future_flag={})') # return msg.format(self.example_future_flag) - msg = "Future()" - return msg.format() + msg = "Future(datum_support={})" + return msg.format(self.datum_support) # deprecated_options = {'example_future_flag': 'warning',} deprecated_options = {} @@ -211,8 +213,7 @@ def context(self, **kwargs): .. note:: iris.FUTURE.example_future_flag does not exist and is - provided only as an example since there are currently no - flags in Future. + provided only as an example. """ # Save the current context diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 510aafcb48..1f755998b1 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -122,6 +122,12 @@ def as_cartopy_projection(self): pass +_short_datum_names = { + "OSGB 1936": "OSGB36", + "WGS 84": "WGS84", +} + + class GeogCS(CoordSystem): """ A geographic (ellipsoidal) coordinate system, defined by the shape of @@ -139,29 +145,28 @@ def __init__( longitude_of_prime_meridian=None, ): """ - Creates a new GeogCS. - - Kwargs: + Create a new GeogCS. + Parameters + ---------- * semi_major_axis, semi_minor_axis: - Axes of ellipsoid, in metres. At least one must be given - (see note below). - + Axes of ellipsoid, in metres. At least one must be given (see note + below). * inverse_flattening: - Can be omitted if both axes given (see note below). - Defaults to 0.0 . - + Can be omitted if both axes given (see note below). Default 0.0 * longitude_of_prime_meridian: - Specifies the prime meridian on the ellipsoid, in degrees. - Defaults to 0.0 . + Specifies the prime meridian on the ellipsoid, in degrees. Default 0.0 + * datum: + Name of the datum used to modify the ellipsoid. Default None + Notes + ----- If just semi_major_axis is set, with no semi_minor_axis or inverse_flattening, then a perfect sphere is created from the given radius. - If just two of semi_major_axis, semi_minor_axis, and - inverse_flattening are given the missing element is calculated from the - formula: + If just two of semi_major_axis, semi_minor_axis, and inverse_flattening + are given the missing element is calculated from the formula: :math:`flattening = (major - minor) / major` Currently, Iris will not allow over-specification (all three ellipsoid @@ -194,58 +199,34 @@ def __init__( ): raise ValueError("Ellipsoid is overspecified") - # Perfect sphere (semi_major_axis only)? (1 0 0) - elif semi_major_axis is not None and ( - semi_minor_axis is None and not inverse_flattening + # We didn't get enough to specify an ellipse. + if semi_major_axis is None and ( + semi_minor_axis is None or inverse_flattening is None ): - semi_minor_axis = semi_major_axis - inverse_flattening = 0.0 + raise ValueError("Insufficient ellipsoid specification") - # Calculate semi_major_axis? (0 1 1) - elif semi_major_axis is None and ( - semi_minor_axis is not None and inverse_flattening is not None - ): + # Making a globe needs a semi_major_axis and a semi_minor_axis + if semi_major_axis is None: semi_major_axis = -semi_minor_axis / ( (1.0 - inverse_flattening) / inverse_flattening ) - - # Calculate semi_minor_axis? (1 0 1) - elif semi_minor_axis is None and ( - semi_major_axis is not None and inverse_flattening is not None - ): + if semi_minor_axis is None and inverse_flattening: semi_minor_axis = semi_major_axis - ( (1.0 / inverse_flattening) * semi_major_axis ) - # Calculate inverse_flattening? (1 1 0) - elif inverse_flattening is None and ( - semi_major_axis is not None and semi_minor_axis is not None - ): - if semi_major_axis == semi_minor_axis: - inverse_flattening = 0.0 - else: - inverse_flattening = 1.0 / ( - (semi_major_axis - semi_minor_axis) / semi_major_axis - ) - - # We didn't get enough to specify an ellipse. - else: - raise ValueError("Insufficient ellipsoid specification") - - #: Major radius of the ellipsoid in metres. - self.semi_major_axis = float(semi_major_axis) - - #: Minor radius of the ellipsoid in metres. - self.semi_minor_axis = float(semi_minor_axis) - - #: :math:`1/f` where :math:`f = (a-b)/a`. - self.inverse_flattening = float(inverse_flattening) - #: Describes 'zero' on the ellipsoid in degrees. self.longitude_of_prime_meridian = _arg_default( longitude_of_prime_meridian, 0 ) + globe = ccrs.Globe( + ellipse=None, + semimajor_axis=semi_major_axis, + semiminor_axis=semi_minor_axis, + ) + self._crs = ccrs.Geodetic(globe) + def _pretty_attrs(self): attrs = [("semi_major_axis", self.semi_major_axis)] if self.semi_major_axis != self.semi_minor_axis: @@ -257,6 +238,14 @@ def _pretty_attrs(self): self.longitude_of_prime_meridian, ) ) + # An unknown crs datum will be treated as None + if self.datum is not None and self.datum != "unknown": + attrs.append( + ( + "datum", + self.datum, + ) + ) return attrs def __repr__(self): @@ -294,7 +283,7 @@ def xml_element(self, doc): return CoordSystem.xml_element(self, doc, attrs) def as_cartopy_crs(self): - return ccrs.Geodetic(self.as_cartopy_globe()) + return self._crs def as_cartopy_projection(self): return ccrs.PlateCarree( @@ -303,13 +292,38 @@ def as_cartopy_projection(self): ) def as_cartopy_globe(self): - # Explicitly set `ellipse` to None as a workaround for - # Cartopy setting WGS84 as the default. - return ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, + return self._crs.globe + + def __getattr__(self, name): + if name == "semi_major_axis": + return self._crs.ellipsoid.semi_major_metre + if name == "semi_minor_axis": + return self._crs.ellipsoid.semi_minor_metre + if name == "inverse_flattening": + return self._crs.ellipsoid.inverse_flattening + if name == "datum": + datum = self._crs.datum.name + # An unknown crs datum will be treated as None + if datum == "unknown": + return None + return datum + return getattr(super(), name) + + @classmethod + def from_datum(cls, datum, longitude_of_prime_meridian=None): + + short_datum = _short_datum_names.get(datum, datum) + + # Cartopy doesn't actually enact datums unless they're provided without + # ellipsoid axes, so only provide the datum + crs = super().__new__(cls) + crs._crs = ccrs.Geodetic(ccrs.Globe(short_datum, ellipse=None)) + + #: Describes 'zero' on the ellipsoid in degrees. + crs.longitude_of_prime_meridian = _arg_default( + longitude_of_prime_meridian, 0 ) + return crs class RotatedGeogCS(CoordSystem): @@ -1110,6 +1124,10 @@ def __init__( * false_northing: Y offset from the planar origin in metres. Defaults to 0.0. + * datum: + If given, specifies the datumof the coordinate system. Only + respected if iris.Future.daum_support is set. + Note: Only one of ``standard_parallel`` and ``scale_factor_at_projection_origin`` should be included. diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 954250b4a2..65ab3d8b5b 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -19,7 +19,9 @@ import cf_units import numpy as np import numpy.ma as ma +import pyproj +import iris import iris.aux_factory from iris.common.mixin import _get_valid_standard_name import iris.coord_systems @@ -131,6 +133,8 @@ CF_ATTR_BOUNDS = "bounds" CF_ATTR_CALENDAR = "calendar" CF_ATTR_CLIMATOLOGY = "climatology" +CF_ATTR_GRID_CRS_WKT = "crs_wkt" +CF_ATTR_GRID_DATUM = "horizontal_datum_name" CF_ATTR_GRID_INVERSE_FLATTENING = "inverse_flattening" CF_ATTR_GRID_EARTH_RADIUS = "earth_radius" CF_ATTR_GRID_MAPPING_NAME = "grid_mapping_name" @@ -233,7 +237,10 @@ def build_cube_metadata(engine): ################################################################################ def _get_ellipsoid(cf_grid_var): - """Return the ellipsoid definition.""" + """ + Return a :class:`iris.coord_systems.GeogCS` using the relevant properties of + `cf_grid_var`. Returns None if no relevant properties are specified. + """ major = getattr(cf_grid_var, CF_ATTR_GRID_SEMI_MAJOR_AXIS, None) minor = getattr(cf_grid_var, CF_ATTR_GRID_SEMI_MINOR_AXIS, None) inverse_flattening = getattr( @@ -248,21 +255,51 @@ def _get_ellipsoid(cf_grid_var): if major is None and minor is None and inverse_flattening is None: major = getattr(cf_grid_var, CF_ATTR_GRID_EARTH_RADIUS, None) - return major, minor, inverse_flattening + datum = getattr(cf_grid_var, CF_ATTR_GRID_DATUM, None) + # Check crs_wkt if no datum + if datum is None: + crs_wkt = getattr(cf_grid_var, CF_ATTR_GRID_CRS_WKT, None) + if crs_wkt is not None: + proj_crs = pyproj.crs.CRS.from_wkt(crs_wkt) + if proj_crs.datum is not None: + datum = proj_crs.datum.name + + # An unknown crs datum will be treated as None + if datum == "unknown": + datum = None + + if not iris.FUTURE.datum_support: + wmsg = ( + "Ignoring a datum in netCDF load for consistency with existing " + "behaviour. In a future version of Iris, this datum will be " + "applied. To apply the datum when loading, use the " + "iris.FUTURE.datum_support flag." + ) + warnings.warn(wmsg, FutureWarning) + datum = None + + if datum is not None: + return iris.coord_systems.GeogCS.from_datum(datum) + elif major is None and minor is None and inverse_flattening is None: + return None + else: + return iris.coord_systems.GeogCS(major, minor, inverse_flattening) ################################################################################ def build_coordinate_system(engine, cf_grid_var): """Create a coordinate system from the CF-netCDF grid mapping variable.""" - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) - - return iris.coord_systems.GeogCS(major, minor, inverse_flattening) + coord_system = _get_ellipsoid(cf_grid_var) + if coord_system is None: + raise ValueError("No ellipsoid specified") + else: + return coord_system ################################################################################ def build_rotated_coordinate_system(engine, cf_grid_var): """Create a rotated coordinate system from the CF-netCDF grid mapping variable.""" - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) north_pole_latitude = getattr( cf_grid_var, CF_ATTR_GRID_NORTH_POLE_LAT, 90.0 @@ -277,14 +314,6 @@ def build_rotated_coordinate_system(engine, cf_grid_var): cf_grid_var, CF_ATTR_GRID_NORTH_POLE_GRID_LON, 0.0 ) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - rcs = iris.coord_systems.RotatedGeogCS( north_pole_latitude, north_pole_longitude, @@ -302,7 +331,7 @@ def build_transverse_mercator_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) latitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None @@ -327,14 +356,6 @@ def build_transverse_mercator_coordinate_system(engine, cf_grid_var): cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None ) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.TransverseMercator( latitude_of_projection_origin, longitude_of_central_meridian, @@ -354,7 +375,7 @@ def build_lambert_conformal_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) latitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None @@ -368,14 +389,6 @@ def build_lambert_conformal_coordinate_system(engine, cf_grid_var): cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None ) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.LambertConformal( latitude_of_projection_origin, longitude_of_central_meridian, @@ -395,7 +408,7 @@ def build_stereographic_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) latitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None @@ -408,14 +421,6 @@ def build_stereographic_coordinate_system(engine, cf_grid_var): # Iris currently only supports Stereographic projections with a scale # factor of 1.0. This is checked elsewhere. - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.Stereographic( latitude_of_projection_origin, longitude_of_projection_origin, @@ -435,7 +440,7 @@ def build_mercator_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) longitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LON_OF_PROJ_ORIGIN, None @@ -449,14 +454,6 @@ def build_mercator_coordinate_system(engine, cf_grid_var): cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None ) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.Mercator( longitude_of_projection_origin, ellipsoid=ellipsoid, @@ -476,7 +473,7 @@ def build_lambert_azimuthal_equal_area_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) latitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None @@ -487,14 +484,6 @@ def build_lambert_azimuthal_equal_area_coordinate_system(engine, cf_grid_var): false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None) false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.LambertAzimuthalEqualArea( latitude_of_projection_origin, longitude_of_projection_origin, @@ -513,7 +502,7 @@ def build_albers_equal_area_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) latitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None @@ -527,14 +516,6 @@ def build_albers_equal_area_coordinate_system(engine, cf_grid_var): cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None ) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.AlbersEqualArea( latitude_of_projection_origin, longitude_of_central_meridian, @@ -554,7 +535,7 @@ def build_vertical_perspective_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) latitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None @@ -568,14 +549,6 @@ def build_vertical_perspective_coordinate_system(engine, cf_grid_var): false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None) false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.VerticalPerspective( latitude_of_projection_origin, longitude_of_projection_origin, @@ -595,7 +568,7 @@ def build_geostationary_coordinate_system(engine, cf_grid_var): grid mapping variable. """ - major, minor, inverse_flattening = _get_ellipsoid(cf_grid_var) + ellipsoid = _get_ellipsoid(cf_grid_var) latitude_of_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None @@ -612,14 +585,6 @@ def build_geostationary_coordinate_system(engine, cf_grid_var): cf_grid_var, CF_ATTR_GRID_SWEEP_ANGLE_AXIS, None ) - ellipsoid = None - if ( - major is not None - or minor is not None - or inverse_flattening is not None - ): - ellipsoid = iris.coord_systems.GeogCS(major, minor, inverse_flattening) - cs = iris.coord_systems.Geostationary( latitude_of_projection_origin, longitude_of_projection_origin, diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index d4b7c84c44..7cab939417 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -2556,6 +2556,8 @@ def add_ellipsoid(ellipsoid): else: cf_var_grid.semi_major_axis = semi_major cf_var_grid.semi_minor_axis = semi_minor + if ellipsoid.datum is not None: + cf_var_grid.horizontal_datum_name = ellipsoid.datum # latlon if isinstance(cs, iris.coord_systems.GeogCS): diff --git a/lib/iris/tests/integration/test_Datums.py b/lib/iris/tests/integration/test_Datums.py new file mode 100755 index 0000000000..77b7f28249 --- /dev/null +++ b/lib/iris/tests/integration/test_Datums.py @@ -0,0 +1,53 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for :class:`iris.coord_systems` datum suppport.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import cartopy.crs as ccrs +import numpy as np + +from iris.coord_systems import GeogCS, LambertConformal + + +class TestDatumTransformation(tests.IrisTest): + def setUp(self): + self.x_points = np.array([-1.5]) + self.y_points = np.array([50.5]) + + self.start_crs = ccrs.OSGB(False) + + def test_transform_points_datum(self): + + # Iris version + wgs84 = GeogCS.from_datum("WGS84") + iris_cs = LambertConformal( + central_lat=54, + central_lon=-4, + secant_latitudes=[52, 56], + ellipsoid=wgs84, + ) + iris_cs_as_cartopy = iris_cs.as_cartopy_crs() + + # Cartopy equivalent + cartopy_cs = ccrs.LambertConformal( + central_latitude=54, + central_longitude=-4, + standard_parallels=[52, 56], + globe=ccrs.Globe("WGS84"), + ) + + expected = cartopy_cs.transform_points( + self.start_crs, self.x_points, self.y_points + ) + + actual = iris_cs_as_cartopy.transform_points( + self.start_crs, self.x_points, self.y_points + ) + + self.assertArrayEqual(expected, actual) diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index 2a45561e17..8c913a1043 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -24,7 +24,8 @@ import numpy.ma as ma import iris -from iris.coords import CellMethod +import iris.coord_systems +from iris.coords import CellMethod, DimCoord from iris.cube import Cube, CubeList from iris.fileformats.netcdf import ( CF_CONVENTIONS_VERSION, @@ -32,6 +33,7 @@ UnknownCellMethodWarning, ) import iris.tests.stock as stock +import iris.tests.unit.fileformats.netcdf.test_load_cubes as tlc @tests.skip_data @@ -484,6 +486,12 @@ def test_unknown_method(self): @tests.skip_data class TestCoordSystem(tests.IrisTest): + def setUp(self): + tlc.setUpModule() + + def tearDown(self): + tlc.tearDownModule() + def test_load_laea_grid(self): cube = iris.load_cube( tests.get_data_path( @@ -492,6 +500,175 @@ def test_load_laea_grid(self): ) self.assertCML(cube, ("netcdf", "netcdf_laea.cml")) + datum_cf_var_cdl = """ + netcdf output { + dimensions: + y = 4 ; + x = 3 ; + variables: + float data(y, x) ; + data :standard_name = "toa_brightness_temperature" ; + data :units = "K" ; + data :grid_mapping = "mercator" ; + int mercator ; + mercator:grid_mapping_name = "mercator" ; + mercator:longitude_of_prime_meridian = 0. ; + mercator:earth_radius = 6378169. ; + mercator:horizontal_datum_name = "OSGB36" ; + float y(y) ; + y:axis = "Y" ; + y:units = "m" ; + y:standard_name = "projection_y_coordinate" ; + float x(x) ; + x:axis = "X" ; + x:units = "m" ; + x:standard_name = "projection_x_coordinate" ; + + // global attributes: + :Conventions = "CF-1.7" ; + :standard_name_vocabulary = "CF Standard Name Table v27" ; + + data: + + data = + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9, 10, 11 ; + + mercator = _ ; + + y = 1, 2, 3, 5 ; + + x = -6, -4, -2 ; + + } + """ + + datum_wkt_cdl = """ +netcdf output5 { +dimensions: + y = 4 ; + x = 3 ; +variables: + float data(y, x) ; + data :standard_name = "toa_brightness_temperature" ; + data :units = "K" ; + data :grid_mapping = "mercator" ; + int mercator ; + mercator:grid_mapping_name = "mercator" ; + mercator:longitude_of_prime_meridian = 0. ; + mercator:earth_radius = 6378169. ; + mercator:longitude_of_projection_origin = 0. ; + mercator:false_easting = 0. ; + mercator:false_northing = 0. ; + mercator:scale_factor_at_projection_origin = 1. ; + mercator:crs_wkt = "PROJCRS[\\"unknown\\",BASEGEOGCRS[\\"unknown\\",DATUM[\\"OSGB36\\",ELLIPSOID[\\"unknown\\",6378169,0,LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]],PRIMEM[\\"Greenwich\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8901]]],CONVERSION[\\"unknown\\",METHOD[\\"Mercator (variant B)\\",ID[\\"EPSG\\",9805]],PARAMETER[\\"Latitude of 1st standard parallel\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8823]],PARAMETER[\\"Longitude of natural origin\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8802]],PARAMETER[\\"False easting\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8806]],PARAMETER[\\"False northing\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8807]]],CS[Cartesian,2],AXIS[\\"(E)\\",east,ORDER[1],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]],AXIS[\\"(N)\\",north,ORDER[2],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]]" ; + float y(y) ; + y:axis = "Y" ; + y:units = "m" ; + y:standard_name = "projection_y_coordinate" ; + float x(x) ; + x:axis = "X" ; + x:units = "m" ; + x:standard_name = "projection_x_coordinate" ; + +// global attributes: + :standard_name_vocabulary = "CF Standard Name Table v27" ; + :Conventions = "CF-1.7" ; +data: + + data = + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9, 10, 11 ; + + mercator = _ ; + + y = 1, 2, 3, 5 ; + + x = -6, -4, -2 ; +} + """ + + def test_load_datum_wkt(self): + expected = "OSGB 1936" + nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) + with iris.FUTURE.context(datum_support=True): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertStringEqual(expected, actual) + + def test_no_load_datum_wkt(self): + nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) + with self.assertWarnsRegexp("iris.FUTURE.datum_support"): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertStringEqual(actual, "unknown") + + def test_load_datum_cf_var(self): + expected = "OSGB 1936" + nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) + with iris.FUTURE.context(datum_support=True): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertStringEqual(expected, actual) + + def test_no_load_datum_cf_var(self): + nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) + with self.assertWarnsRegexp("iris.FUTURE.datum_support"): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertStringEqual(actual, "unknown") + + def test_save_datum(self): + expected = "OSGB 1936" + # saved_crs = iris.coord_systems.GeogCS.from_datum(datum="OSGB36") + saved_crs = iris.coord_systems.Mercator( + ellipsoid=iris.coord_systems.GeogCS.from_datum("OSGB36") + ) + + base_cube = stock.realistic_3d() + base_lat_coord = base_cube.coord("grid_latitude") + test_lat_coord = DimCoord( + base_lat_coord.points, + standard_name="projection_y_coordinate", + coord_system=saved_crs, + ) + base_lon_coord = base_cube.coord("grid_longitude") + test_lon_coord = DimCoord( + base_lon_coord.points, + standard_name="projection_x_coordinate", + coord_system=saved_crs, + ) + test_cube = Cube( + base_cube.data, + standard_name=base_cube.standard_name, + units=base_cube.units, + dim_coords_and_dims=( + (base_cube.coord("time"), 0), + (test_lat_coord, 1), + (test_lon_coord, 2), + ), + ) + + with self.temp_filename(suffix=".nc") as filename: + iris.save(test_cube, filename) + with iris.FUTURE.context(datum_support=True): + cube = iris.load_cube(filename) + print(cube) + for coord in cube.coords(): + print(coord) + + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertStringEqual(expected, actual) + def _get_scale_factor_add_offset(cube, datatype): """Utility function used by netCDF data packing tests.""" diff --git a/lib/iris/tests/test_coordsystem.py b/lib/iris/tests/test_coordsystem.py index 4229125969..046d76b15a 100644 --- a/lib/iris/tests/test_coordsystem.py +++ b/lib/iris/tests/test_coordsystem.py @@ -216,7 +216,9 @@ def test_as_cartopy_crs(self): cs = GeogCS(6543210, 6500000) res = cs.as_cartopy_crs() globe = ccrs.Globe( - semimajor_axis=6543210.0, semiminor_axis=6500000.0, ellipse=None + semimajor_axis=6543210.0, + semiminor_axis=6500000.0, + ellipse=None, ) expected = ccrs.Geodetic(globe) self.assertEqual(res, expected) @@ -243,7 +245,10 @@ def test_init(self): class Test_RotatedGeogCS_repr(tests.IrisTest): def test_repr(self): rcs = RotatedGeogCS( - 30, 40, north_pole_grid_longitude=50, ellipsoid=GeogCS(6371229) + 30, + 40, + north_pole_grid_longitude=50, + ellipsoid=GeogCS(6371229), ) expected = ( "RotatedGeogCS(30.0, 40.0, " @@ -263,7 +268,10 @@ def test_repr(self): class Test_RotatedGeogCS_str(tests.IrisTest): def test_str(self): rcs = RotatedGeogCS( - 30, 40, north_pole_grid_longitude=50, ellipsoid=GeogCS(6371229) + 30, + 40, + north_pole_grid_longitude=50, + ellipsoid=GeogCS(6371229), ) expected = ( "RotatedGeogCS(30.0, 40.0, " @@ -488,5 +496,23 @@ def test_south_cutoff(self): self.assertEqual(ccrs.cutoff, 30) +class Test_Datums(tests.IrisTest): + def test_default_none(self): + cs = GeogCS(6543210, 6500000) # Arbitrary radii + cartopy_crs = cs.as_cartopy_crs() + self.assertStringEqual(cartopy_crs.datum.name, "unknown") + + def test_set_persist(self): + cs = GeogCS.from_datum(datum="WGS84") + cartopy_crs = cs.as_cartopy_crs() + self.assertStringEqual( + cartopy_crs.datum.name, "World Geodetic System 1984" + ) + + cs = GeogCS.from_datum(datum="OSGB36") + cartopy_crs = cs.as_cartopy_crs() + self.assertStringEqual(cartopy_crs.datum.name, "OSGB 1936") + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py index 0d3ed932e8..f79aa494f3 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py @@ -11,6 +11,7 @@ import shutil import subprocess import tempfile +import warnings import iris.fileformats._nc_load_rules.engine from iris.fileformats.cf import CFReader @@ -95,10 +96,20 @@ def load_cube_from_cdl(self, cdl_string, cdl_path, nc_path): # Use 'patch' so it is restored after the test. self.patch("iris.fileformats.netcdf.DEBUG", self.debug) - # Call the main translation function to load a single cube. - # _load_cube establishes per-cube facts, activates rules and - # produces an actual cube. - cube = _load_cube(engine, cf, cf_var, nc_path) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message="Ignoring a datum in netCDF load for consistency with existing " + "behaviour. In a future version of Iris, this datum will be " + "applied. To apply the datum when loading, use the " + "iris.FUTURE.datum_support flag.", + category=FutureWarning, + module="iris.fileformats._nc_load_rules.helpers", + ) + # Call the main translation function to load a single cube. + # _load_cube establishes per-cube facts, activates rules and + # produces an actual cube. + cube = _load_cube(engine, cf, cf_var, nc_path) # Also Record, on the cubes, which hybrid coord elements were identified # by the rules operation. diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py index 6b534b52b5..61b37fe477 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py @@ -769,7 +769,7 @@ def check_call(self, coord_name, coord_system, units, expected_units): self.assertEqual(result, expected_units) def test_geogcs_latitude(self): - crs = iris.coord_systems.GeogCS(60, 0) + crs = iris.coord_systems.GeogCS(60, 30) self.check_call( "latitude", coord_system=crs, @@ -778,7 +778,7 @@ def test_geogcs_latitude(self): ) def test_geogcs_longitude(self): - crs = iris.coord_systems.GeogCS(60, 0) + crs = iris.coord_systems.GeogCS(60, 30) self.check_call( "longitude", coord_system=crs, diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 91cd9d3f5f..4382db1f5e 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -18,6 +18,7 @@ dependencies: - netcdf4 - numpy >=1.19 - python-xxhash + - pyproj - scipy # Optional dependencies. From 6d7bde3a2fbfc29fcfeade50af6497d376978a4d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 09:29:17 +0100 Subject: [PATCH 098/319] Bump peter-evans/create-pull-request from 4.0.2 to 4.0.3 (#4733) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.0.2 to 4.0.3. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/bd72e1b7922d417764d27d30768117ad7da78a0e...f094b77505fb89581e68a1163fbd2fffece39da1) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 54d708104e..98cd977804 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -82,7 +82,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@bd72e1b7922d417764d27d30768117ad7da78a0e + uses: peter-evans/create-pull-request@f094b77505fb89581e68a1163fbd2fffece39da1 with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From 1be3a54fc9efe0659d00989bed8933d7919d0a3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 09:30:19 +0100 Subject: [PATCH 099/319] Updated environment lockfiles (#4732) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 0518a475ae..4e98d3ce1a 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: c7823bd2d790451e307ab5cbb450b09ee9460f9e8334dbf40f32d8bab4a2fefa +# input_hash: b230293335ef7a307da7d676affe5b26d439c6c026b2db6f0a4331900ea33f02 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 @@ -18,7 +18,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_16.tar.b https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_16.tar.bz2#71feb63a30085cbce51847d5ef1f769d -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3-h516909a_0.tar.bz2#1378b88874f42ac31b2f8e4f6975cb7b +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3.2-h166bdaf_0.tar.bz2#b7607b7b62dce55c194ad84f99464e5f https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 @@ -49,7 +49,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1n-h166bdaf_0.tar.bz2#cf0ddbd911fa547e6bc4291ca5e17379 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1o-h166bdaf_0.tar.bz2#6172048796b123e542945d998f5150b7 https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 @@ -93,14 +93,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_0.tar.bz2#3314d5f8d6c831514363625cb28c8ff1 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.3-h4ff8645_0.tar.bz2#987ea6cb07fd358ad52dd1c938367967 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.5-h4ff8645_0.tar.bz2#a1448f0c31baec3946d2dcf09f905c9e https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.1-hd4edc92_1.tar.bz2#ebebb56f78dd7518dcc94d6c26aa2249 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.2-hd4edc92_0.tar.bz2#5608a9802071373781ee401786fa4846 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.0-h7bff187_0.tar.bz2#e2d939fa77fe69cd50f751961f17786a @@ -122,7 +122,7 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.3.0-pyhd8ed1ab_0.tar.bz2#960b78685ccbedb992886fc4ce37bf6e -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.1-hcf0ee16_1.tar.bz2#4c41fc47db7d631862f12e5e5c885cb9 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.2-hcf0ee16_0.tar.bz2#79d7fca692d224dc29a72bda90f78a7b https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 @@ -135,7 +135,7 @@ https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.0-h93bde94_1.tar.bz2#cf908994f24ea526afc59f295d5b07c1 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.8-pyhd8ed1ab_0.tar.bz2#7f5738c49fdccd0fc755bfd25a5ea66c -https://conda.anaconda.org/conda-forge/noarch/pyshp-2.2.0-pyhd8ed1ab_0.tar.bz2#2aa546be05be34b8e1744afd327b623f +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 @@ -152,7 +152,7 @@ https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a -https://conda.anaconda.org/conda-forge/noarch/babel-2.9.1-pyh44b312d_0.tar.bz2#74136ed39bfea0832d338df1e58d013e +https://conda.anaconda.org/conda-forge/noarch/babel-2.10.1-pyhd8ed1ab_0.tar.bz2#2ec70a4a964b696170d730466c668f60 https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 @@ -182,10 +182,10 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.4.2-pyhd8ed1ab_0.tar.bz2#6d57147688f325bb6eb7bbdac6ec662a +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.5.0-pyhd8ed1ab_0.tar.bz2#3aef8ad6f9af56117e959a53cb9b9fd1 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.0-h40b6f09_0.tar.bz2#017b20e7e98860f0bfa7492ce16390a7 -https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.1-pyhd8ed1ab_0.tar.bz2#40b3f446c61729cdd21ed9d85627df6e +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_0.tar.bz2#2d307d13155817b5f5da36cc69358fe6 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 @@ -197,14 +197,14 @@ https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8. https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.0-py38h97f7145_6.tar.bz2#c633638d6fc8e7507e383dcb76696ad3 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h97f7145_1.tar.bz2#e0ada9ddff3a730b7dfc976a5ee3f420 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.0-pyhd8ed1ab_0.tar.bz2#030048290bb28d917f82830d6e01d111 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.1-py38hf4fb855_0.tar.bz2#47cf0cab2ae368e1062e75cfbc4277af +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_0.tar.bz2#107af20136422bcabf9f1195f6262117 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 @@ -215,11 +215,11 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h91476 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_3.tar.bz2#a08562889b985d021550e22443cf0fce https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.18.1-py38h578d9bd_1.tar.bz2#026355c0547c2ae491721278f8c90200 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.1-py38h578d9bd_0.tar.bz2#0d78be9cf1c400ba8e3077cf060492f1 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba From 750d9e70c6991b5075b46a2f8b39ec020393933c Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Mon, 9 May 2022 17:31:59 +0100 Subject: [PATCH 100/319] Replace nose with pytest (#4734) * Replace nose with pytest * Reinstate magic processor number * add psutil, fstrings and rename TestMixin --- lib/iris/tests/__init__.py | 4 -- lib/iris/tests/runner/_runner.py | 47 ++++++++------------- lib/iris/tests/system_test.py | 2 +- lib/iris/tests/test_constraints.py | 18 ++++---- requirements/ci/nox.lock/py38-linux-64.lock | 13 +++++- requirements/ci/py38.yml | 4 +- setup.cfg | 3 +- 7 files changed, 43 insertions(+), 48 deletions(-) diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py index c1df4f628b..bf8ad7c81d 100644 --- a/lib/iris/tests/__init__.py +++ b/lib/iris/tests/__init__.py @@ -1218,10 +1218,6 @@ class IrisTest(IrisTest_nometa, metaclass=_TestTimingsMetaclass): class GraphicsTestMixin: - - # nose directive: dispatch tests concurrently. - _multiprocess_can_split_ = True - def setUp(self): # Acquire threading non re-entrant blocking lock to ensure # thread-safe plotting. diff --git a/lib/iris/tests/runner/_runner.py b/lib/iris/tests/runner/_runner.py index 3ef961d000..34a1af1e06 100644 --- a/lib/iris/tests/runner/_runner.py +++ b/lib/iris/tests/runner/_runner.py @@ -10,17 +10,16 @@ # Because this file is imported by setup.py, there may be additional runtime # imports later in the file. -import multiprocessing import os import sys # NOTE: Do not inherit from object as distutils does not like it. class TestRunner: - """Run the Iris tests under nose and multiprocessor for performance""" + """Run the Iris tests under pytest and pytest-xdist for performance""" description = ( - "Run tests under nose and multiprocessor for performance. " + "Run tests under pytest and pytest-xdist for performance. " "Default behaviour is to run all non-gallery tests. " "Specifying one or more test flags will run *only* those " "tests." @@ -70,8 +69,8 @@ def initialize_options(self): self.create_missing = False def finalize_options(self): - # These enviroment variables will be propagated to all the - # processes that nose.run creates. + # These environment variables will be propagated to all the + # processes that pytest-xdist creates. if self.no_data: print("Running tests in no-data mode...") import iris.config @@ -95,25 +94,23 @@ def finalize_options(self): if self.stop: print("Stopping tests after the first error or failure") if self.num_processors is None: - # Choose a magic number that works reasonably well for the default - # number of processes. - self.num_processors = (multiprocessing.cpu_count() + 1) // 4 + 1 + self.num_processors = "auto" else: self.num_processors = int(self.num_processors) def run(self): - import nose + import pytest if hasattr(self, "distribution") and self.distribution.tests_require: self.distribution.fetch_build_eggs(self.distribution.tests_require) tests = [] if self.system_tests: - tests.append("iris.tests.system_test") + tests.append("lib/iris/tests/system_test.py") if self.default_tests: - tests.append("iris.tests") + tests.append("lib/iris/tests") if self.coding_tests: - tests.append("iris.tests.test_coding_standards") + tests.append("lib/iris/tests/test_coding_standards.py") if self.gallery_tests: import iris.config @@ -129,35 +126,25 @@ def run(self): "WARNING: Gallery path %s does not exist." % (gallery_path) ) if not tests: - tests.append("iris.tests") - - regexp_pat = r"--match=^([Tt]est(?![Mm]ixin)|[Ss]ystem)" - - n_processors = max(self.num_processors, 1) + tests.append("lib/iris/tests") args = [ - "", None, - "--processes=%s" % n_processors, - "--verbosity=2", - regexp_pat, - "--process-timeout=180", + "-v", + f"-n={self.num_processors}", ] if self.stop: - args.append("--stop") + args.append("-x") result = True for test in tests: - args[1] = test + args[0] = test print() print( - "Running test discovery on %s with %s processors." - % (test, n_processors) + f"Running test discovery on {test} with {self.num_processors} processors." ) - # run the tests at module level i.e. my_module.tests - # - test must start with test/Test and must not contain the - # word Mixin. - result &= nose.run(argv=args) + retcode = pytest.main(args=args) + result &= retcode.value == 0 if result is False: exit(1) diff --git a/lib/iris/tests/system_test.py b/lib/iris/tests/system_test.py index 36573362dd..00cb541e1c 100644 --- a/lib/iris/tests/system_test.py +++ b/lib/iris/tests/system_test.py @@ -21,7 +21,7 @@ import iris -class SystemInitialTest(tests.IrisTest): +class TestSystemInitial(tests.IrisTest): def test_supported_filetypes(self): nx, ny = 60, 60 data = np.arange(nx * ny, dtype=">f4").reshape(nx, ny) diff --git a/lib/iris/tests/test_constraints.py b/lib/iris/tests/test_constraints.py index 4f9e48fb83..1972cdeb90 100644 --- a/lib/iris/tests/test_constraints.py +++ b/lib/iris/tests/test_constraints.py @@ -91,7 +91,7 @@ def test_cell_different_bounds(self): self.assertEqual(len(sub_list), 0) -class TestMixin: +class ConstraintMixin: """ Mix-in class for attributes & utilities common to the "normal" and "strict" test cases. @@ -134,7 +134,7 @@ def setUp(self): self.lat_gt_45 = iris.Constraint(latitude=lambda c: c > 45) -class RelaxedConstraintMixin(TestMixin): +class RelaxedConstraintMixin(ConstraintMixin): @staticmethod def fixup_sigma_to_be_aux(cubes): # XXX Fix the cubes such that the sigma coordinate is always an AuxCoord. Pending gh issue #18 @@ -296,11 +296,11 @@ def load_match(self, files, constraints): @tests.skip_data -class TestCubeExtract__names(TestMixin, tests.IrisTest): +class TestCubeExtract__names(ConstraintMixin, tests.IrisTest): def setUp(self): fname = iris.sample_data_path("atlantic_profiles.nc") self.cubes = iris.load(fname) - TestMixin.setUp(self) + ConstraintMixin.setUp(self) cube = iris.load_cube(self.theta_path) # Expected names... self.standard_name = "air_potential_temperature" @@ -353,11 +353,11 @@ def test_unknown(self): @tests.skip_data -class TestCubeExtract__name_constraint(TestMixin, tests.IrisTest): +class TestCubeExtract__name_constraint(ConstraintMixin, tests.IrisTest): def setUp(self): fname = iris.sample_data_path("atlantic_profiles.nc") self.cubes = iris.load(fname) - TestMixin.setUp(self) + ConstraintMixin.setUp(self) cube = iris.load_cube(self.theta_path) # Expected names... self.standard_name = "air_potential_temperature" @@ -579,9 +579,9 @@ def test_unknown(self): @tests.skip_data -class TestCubeExtract(TestMixin, tests.IrisTest): +class TestCubeExtract(ConstraintMixin, tests.IrisTest): def setUp(self): - TestMixin.setUp(self) + ConstraintMixin.setUp(self) self.cube = iris.load_cube(self.theta_path) def test_attribute_constraint(self): @@ -644,7 +644,7 @@ def test_non_existent_coordinate(self): @tests.skip_data -class TestConstraints(TestMixin, tests.IrisTest): +class TestConstraints(ConstraintMixin, tests.IrisTest): def test_constraint_expressions(self): rt = repr(self.theta) rl10 = repr(self.level_10) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 4e98d3ce1a..be82d62ffe 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: b230293335ef7a307da7d676affe5b26d439c6c026b2db6f0a4331900ea33f02 +# input_hash: 41315fe97a24272298c496dfe62676b5ef3ce15bd4750681498f42fd4e9ea036 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 @@ -112,6 +112,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 @@ -119,6 +120,7 @@ https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.0-h7bff187_0.tar.bz2#81e39fb3ae82be7e8d2dd7046f393588 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.3.0-pyhd8ed1ab_0.tar.bz2#960b78685ccbedb992886fc4ce37bf6e @@ -126,13 +128,14 @@ https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.2-hcf0ee16 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/noarch/nose-1.3.7-py_1006.tar.bz2#382019d5f8e9362ef6f60a8d4e7bce8f https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.0-h93bde94_1.tar.bz2#cf908994f24ea526afc59f295d5b07c1 +https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.8-pyhd8ed1ab_0.tar.bz2#7f5738c49fdccd0fc755bfd25a5ea66c https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 @@ -148,6 +151,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.ta https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 +https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c @@ -168,7 +172,9 @@ https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h99721a1_2.tar. https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.2-py38h9776b28_0.tar.bz2#bd527d652ba06fb2aae61640bcf7c435 +https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.0-py38h0a891b7_1.tar.bz2#92045570d1da14b3f90621c54f8afb12 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 @@ -194,6 +200,7 @@ https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 @@ -210,6 +217,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea +https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 @@ -217,6 +225,7 @@ https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_3.tar.bz https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 4382db1f5e..62eb936506 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -34,9 +34,11 @@ dependencies: # Test dependencies. - filelock - imagehash >=4.0 - - nose - pillow <7 - pre-commit + - psutil + - pytest + - pytest-xdist - requests # Documentation dependencies. diff --git a/setup.cfg b/setup.cfg index ecdcad85b2..233b9241d6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,10 +75,11 @@ docs = test = filelock imagehash>=4.0 - nose pillow<7 pre-commit requests + pytest + pytest-xdist all = mo_pack nc-time-axis>=1.4 From 4c43fc6e8a692aa637e1becf6415cd4f4f7d6475 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Tue, 10 May 2022 10:09:02 +0100 Subject: [PATCH 101/319] Caching during load (#4716) * Caching * Move _optimum_chunksize docstring * Should be copying coords because they're mutable * Handle failure of datetime comparisons gracefully * Review fixes and more careful checks on mutable objects * coord_system doesn't have 'copy' but __repr__ covers the whole thing (I think) * Review fixes * Revert coord_system changes * Still need to handle tuple issue * What's new * Fix prompted by test failure * Limit cache size * Work around comparable datetime issue --- docs/src/whatsnew/latest.rst | 3 +++ lib/iris/_lazy_data.py | 32 ++++++++++++++++++++++++--- lib/iris/fileformats/pp_load_rules.py | 27 +++++++++++++++++++++- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d69c7576f4..96c212e00f 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -112,6 +112,9 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ added caching to the calculation of the points array in a :class:`~iris.coords.DimCoord` created using :meth:`~iris.coords.DimCoord.from_regular`. (:pull:`4698`) +#. `@wjbenfold`_ introduced caching in :func:`_lazy_data._optimum_chunksize` and + :func:`iris.fileformats.pp_load_rules._epoch_date_hours` to reduce time spent + repeating calculations. (:pull:`4716`) #. `@pp-mo`_ made :meth:`~iris.cube.Cube.add_aux_factory` faster. (:pull:`4718`) diff --git a/lib/iris/_lazy_data.py b/lib/iris/_lazy_data.py index 27f09b2a35..ac7ae34511 100644 --- a/lib/iris/_lazy_data.py +++ b/lib/iris/_lazy_data.py @@ -10,7 +10,7 @@ """ -from functools import wraps +from functools import lru_cache, wraps import dask import dask.array as da @@ -47,7 +47,14 @@ def is_lazy_data(data): return result -def _optimum_chunksize(chunks, shape, limit=None, dtype=np.dtype("f4")): +@lru_cache +def _optimum_chunksize_internals( + chunks, + shape, + limit=None, + dtype=np.dtype("f4"), + dask_array_chunksize=dask.config.get("array.chunk-size"), +): """ Reduce or increase an initial chunk shape to get close to a chosen ideal size, while prioritising the splitting of the earlier (outer) dimensions @@ -86,7 +93,7 @@ def _optimum_chunksize(chunks, shape, limit=None, dtype=np.dtype("f4")): # Set the chunksize limit. if limit is None: # Fetch the default 'optimal' chunksize from the dask config. - limit = dask.config.get("array.chunk-size") + limit = dask_array_chunksize # Convert to bytes limit = dask.utils.parse_bytes(limit) @@ -146,6 +153,25 @@ def _optimum_chunksize(chunks, shape, limit=None, dtype=np.dtype("f4")): return tuple(result) +@wraps(_optimum_chunksize_internals) +def _optimum_chunksize( + chunks, + shape, + limit=None, + dtype=np.dtype("f4"), +): + # By providing dask_array_chunksize as an argument, we make it so that the + # output of _optimum_chunksize_internals depends only on its arguments (and + # thus we can use lru_cache) + return _optimum_chunksize_internals( + tuple(chunks), + tuple(shape), + limit=limit, + dtype=dtype, + dask_array_chunksize=dask.config.get("array.chunk-size"), + ) + + def as_lazy_data(data, chunks=None, asarray=False): """ Convert the input array `data` to a dask array. diff --git a/lib/iris/fileformats/pp_load_rules.py b/lib/iris/fileformats/pp_load_rules.py index 82f40dbf14..c23772f235 100644 --- a/lib/iris/fileformats/pp_load_rules.py +++ b/lib/iris/fileformats/pp_load_rules.py @@ -9,6 +9,7 @@ # SciTools/iris-code-generators:tools/gen_rules.py import calendar +from functools import wraps import cf_units import numpy as np @@ -514,7 +515,7 @@ def _new_coord_and_dims( _HOURS_UNIT = cf_units.Unit("hours") -def _epoch_date_hours(epoch_hours_unit, datetime): +def _epoch_date_hours_internals(epoch_hours_unit, datetime): """ Return an 'hours since epoch' number for a date. @@ -589,6 +590,30 @@ def _epoch_date_hours(epoch_hours_unit, datetime): return epoch_hours +_epoch_date_hours_cache = {} +_epoch_date_hours_cache_max_size = 128 # lru_cache default + + +@wraps(_epoch_date_hours_internals) +def _epoch_date_hours(epoch_hours_unit, datetime): + # Not using functools.lru_cache because it does an equality check that fails + # on datetime objects from different calendars. + + key = (epoch_hours_unit, hash(datetime)) + + if key not in _epoch_date_hours_cache: + _epoch_date_hours_cache[key] = _epoch_date_hours_internals( + epoch_hours_unit, datetime + ) + + # Limit cache size + while len(_epoch_date_hours_cache) > _epoch_date_hours_cache_max_size: + oldest_item = next(iter(_epoch_date_hours_cache)) + _epoch_date_hours_cache.pop(oldest_item, None) + + return _epoch_date_hours_cache[key] + + def _convert_time_coords( lbcode, lbtim, From 9e198369b4dd8aabaa7bb2c5f8d5f7086cbaee3e Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Wed, 11 May 2022 12:46:03 +0100 Subject: [PATCH 102/319] More sensible climatological time points (#4723) * Initial moves to introduce result_coord_func * climatological keyword * Climatology argument to aggregated_by * Fix the tests * Review changes from RC * Test fixes * Further test fixes * Move test code back out after rebase * Different fix for self.coord_dims failure * Add ifmain * Revert inadvertent doctest changes * Review improvements * Fix doctest black broke * Stop new tests being executable, which makes the test runner miss them * Docstrings to comments so they don't obscure test names in results * Apply suggestions from code review Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Review fixes * What's new Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 5 + lib/iris/analysis/__init__.py | 43 +- lib/iris/cube.py | 76 +- lib/iris/tests/unit/cube/test_Cube.py | 541 +---------- .../unit/cube/test_Cube__aggregated_by.py | 845 ++++++++++++++++++ 5 files changed, 933 insertions(+), 577 deletions(-) create mode 100644 lib/iris/tests/unit/cube/test_Cube__aggregated_by.py diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 96c212e00f..6ed6764cfa 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -58,6 +58,11 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ and `@stephenworsley`_ (reviewer) added a maximum run length aggregator (:class:`~iris.analysis.MAX_RUN`). (:pull:`4676`) +#. `@wjbenfold`_ and `@rcomer`_ (reviewer) added a ``climatological`` keyword to + :meth:`~iris.cube.Cube.aggregated_by` that causes the climatological flag to + be set and the point for each cell to equal its first bound, thereby + preserving the time of year. + 🐛 Bugs Fixed ============= diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index cf035d568f..60994fb6c2 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -2209,7 +2209,9 @@ class _Groupby: """ - def __init__(self, groupby_coords, shared_coords=None): + def __init__( + self, groupby_coords, shared_coords=None, climatological=False + ): """ Determine the group slices over the group-by coordinates. @@ -2225,6 +2227,12 @@ def __init__(self, groupby_coords, shared_coords=None): that share the same group-by coordinate axis. The `int` identifies which dimension of the coord is on the group-by coordinate axis. + * climatological (bool): + Indicates whether the output is expected to be climatological. For + any aggregated time coord(s), this causes the climatological flag to + be set and the point for each cell to equal its first bound, thereby + preserving the time of year. + """ #: Group-by and shared coordinates that have been grouped. self.coords = [] @@ -2253,6 +2261,13 @@ def __init__(self, groupby_coords, shared_coords=None): for coord, dim in shared_coords: self._add_shared_coord(coord, dim) + # Aggregation is climatological in nature + self.climatological = climatological + + # Stores mapping from original cube coords to new ones, as metadata may + # not match + self.coord_replacement_mapping = [] + def _add_groupby_coord(self, coord): if coord.ndim != 1: raise iris.exceptions.CoordinateMultiDimError(coord) @@ -2411,6 +2426,9 @@ def _compute_shared_coords(self): # Create new shared bounded coordinates. for coord, dim in self._shared_coords: + climatological_coord = ( + self.climatological and coord.units.is_time_reference() + ) if coord.points.dtype.kind in "SU": if coord.bounds is None: new_points = [] @@ -2449,6 +2467,7 @@ def _compute_shared_coords(self): maxmin_axis = (dim, -1) first_choices = coord.bounds.take(0, -1) last_choices = coord.bounds.take(1, -1) + else: # Derive new coord's bounds from points. item = coord.points @@ -2501,7 +2520,11 @@ def _compute_shared_coords(self): # Now create the new bounded group shared coordinate. try: - new_points = new_bounds.mean(-1) + if climatological_coord: + # Use the first bound as the point + new_points = new_bounds[..., 0] + else: + new_points = new_bounds.mean(-1) except TypeError: msg = ( "The {0!r} coordinate on the collapsing dimension" @@ -2510,17 +2533,19 @@ def _compute_shared_coords(self): raise ValueError(msg) try: - self.coords.append( - coord.copy(points=new_points, bounds=new_bounds) - ) + new_coord = coord.copy(points=new_points, bounds=new_bounds) except ValueError: # non monotonic points/bounds - self.coords.append( - iris.coords.AuxCoord.from_coord(coord).copy( - points=new_points, bounds=new_bounds - ) + new_coord = iris.coords.AuxCoord.from_coord(coord).copy( + points=new_points, bounds=new_bounds ) + if climatological_coord: + new_coord.climatological = True + self.coord_replacement_mapping.append((coord, new_coord)) + + self.coords.append(new_coord) + def __len__(self): """Calculate the number of groups given the group-by coordinates.""" diff --git a/lib/iris/cube.py b/lib/iris/cube.py index b91555968c..386e77bdc5 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -3840,14 +3840,15 @@ def collapsed(self, coords, aggregator, **kwargs): ) return result - def aggregated_by(self, coords, aggregator, **kwargs): + def aggregated_by( + self, coords, aggregator, climatological=False, **kwargs + ): """ - Perform aggregation over the cube given one or more "group - coordinates". + Perform aggregation over the cube given one or more "group coordinates". A "group coordinate" is a coordinate where repeating values represent a - single group, such as a month coordinate on a daily time slice. - Repeated values will form a group even if they are not consecutive. + single group, such as a month coordinate on a daily time slice. Repeated + values will form a group even if they are not consecutive. The group coordinates must all be over the same cube dimension. Each common value group identified over all the group-by coordinates is @@ -3855,30 +3856,37 @@ def aggregated_by(self, coords, aggregator, **kwargs): Weighted aggregations (:class:`iris.analysis.WeightedAggregator`) may also be supplied. These include :data:`~iris.analysis.MEAN` and - sum :data:`~iris.analysis.SUM`. + :data:`~iris.analysis.SUM`. - Weighted aggregations support an optional *weights* keyword argument. - If set, this should be supplied as an array of weights whose shape - matches the cube or as 1D array whose length matches the dimension over - which is aggregated. + Weighted aggregations support an optional *weights* keyword argument. If + set, this should be supplied as an array of weights whose shape matches + the cube or as 1D array whose length matches the dimension over which is + aggregated. - Args: - - * coords (list of coord names or :class:`iris.coords.Coord` instances): + Parameters + ---------- + coords : (list of coord names or :class:`iris.coords.Coord` instances) One or more coordinates over which group aggregation is to be performed. - * aggregator (:class:`iris.analysis.Aggregator`): + aggregator : :class:`iris.analysis.Aggregator` Aggregator to be applied to each group. - - Kwargs: - - * kwargs: + climatological : bool + Indicates whether the output is expected to be climatological. For + any aggregated time coord(s), this causes the climatological flag to + be set and the point for each cell to equal its first bound, thereby + preserving the time of year + + Returns + ------- + :class:`iris.cube.Cube` + + Other Parameters + ---------------- + kwargs: Aggregator and aggregation function keyword arguments. - Returns: - :class:`iris.cube.Cube`. - - For example: + Examples + -------- >>> import iris >>> import iris.analysis @@ -3981,7 +3989,9 @@ def aggregated_by(self, coords, aggregator, **kwargs): # Create the aggregation group-by instance. groupby = iris.analysis._Groupby( - groupby_coords, shared_coords_and_dims + groupby_coords, + shared_coords_and_dims, + climatological=climatological, ) # Create the resulting aggregate-by cube and remove the original @@ -4103,17 +4113,27 @@ def aggregated_by(self, coords, aggregator, **kwargs): dimensions=dimension_to_groupby, dim_coords=True ) or [None] for coord in groupby.coords: + new_coord = coord.copy() + + # The metadata may have changed (e.g. climatology), so check if + # there's a better coord to pass to self.coord_dims + lookup_coord = coord + for ( + cube_coord, + groupby_coord, + ) in groupby.coord_replacement_mapping: + if coord == groupby_coord: + lookup_coord = cube_coord + if ( dim_coord is not None - and dim_coord.metadata == coord.metadata + and dim_coord.metadata == lookup_coord.metadata and isinstance(coord, iris.coords.DimCoord) ): - aggregateby_cube.add_dim_coord( - coord.copy(), dimension_to_groupby - ) + aggregateby_cube.add_dim_coord(new_coord, dimension_to_groupby) else: aggregateby_cube.add_aux_coord( - coord.copy(), self.coord_dims(coord) + new_coord, self.coord_dims(lookup_coord) ) # Attach the aggregate-by data into the aggregate-by cube. diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 4954e7a82a..ea4a200c5c 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -18,7 +18,7 @@ from iris._lazy_data import as_lazy_data import iris.analysis -from iris.analysis import MEAN, SUM, Aggregator, WeightedAggregator +from iris.analysis import MEAN, Aggregator, WeightedAggregator import iris.aux_factory from iris.aux_factory import HybridHeightFactory from iris.common.metadata import BaseMetadata @@ -676,545 +676,6 @@ def test_different_array_attrs_incompatible(self): self.assertFalse(self.test_cube.is_compatible(self.other_cube)) -class Test_aggregated_by(tests.IrisTest): - def setUp(self): - self.cube = Cube(np.arange(44).reshape(4, 11)) - - val_coord = AuxCoord( - [0, 0, 0, 1, 1, 2, 0, 0, 2, 0, 1], long_name="val" - ) - label_coord = AuxCoord( - [ - "alpha", - "alpha", - "beta", - "beta", - "alpha", - "gamma", - "alpha", - "alpha", - "alpha", - "gamma", - "beta", - ], - long_name="label", - units="no_unit", - ) - simple_agg_coord = AuxCoord([1, 1, 2, 2], long_name="simple_agg") - spanning_coord = AuxCoord( - np.arange(44).reshape(4, 11), long_name="spanning" - ) - spanning_label_coord = AuxCoord( - np.arange(1, 441, 10).reshape(4, 11).astype(str), - long_name="span_label", - units="no_unit", - ) - - self.cube.add_aux_coord(simple_agg_coord, 0) - self.cube.add_aux_coord(val_coord, 1) - self.cube.add_aux_coord(label_coord, 1) - self.cube.add_aux_coord(spanning_coord, (0, 1)) - self.cube.add_aux_coord(spanning_label_coord, (0, 1)) - - self.mock_agg = mock.Mock(spec=Aggregator) - self.mock_agg.cell_method = [] - self.mock_agg.aggregate = mock.Mock( - return_value=mock.Mock(dtype="object") - ) - self.mock_agg.aggregate_shape = mock.Mock(return_value=()) - self.mock_agg.lazy_func = None - self.mock_agg.post_process = mock.Mock(side_effect=lambda x, y, z: x) - - self.mock_weighted_agg = mock.Mock(spec=WeightedAggregator) - self.mock_weighted_agg.cell_method = [] - - def mock_weighted_aggregate(*_, **kwargs): - if kwargs.get("returned", False): - return (mock.Mock(dtype="object"), mock.Mock(dtype="object")) - return mock.Mock(dtype="object") - - self.mock_weighted_agg.aggregate = mock.Mock( - side_effect=mock_weighted_aggregate - ) - self.mock_weighted_agg.aggregate_shape = mock.Mock(return_value=()) - self.mock_weighted_agg.lazy_func = None - self.mock_weighted_agg.post_process = mock.Mock( - side_effect=lambda x, y, z, **kwargs: y - ) - - self.ancillary_variable = AncillaryVariable( - [0, 1, 2, 3], long_name="foo" - ) - self.cube.add_ancillary_variable(self.ancillary_variable, 0) - self.cell_measure = CellMeasure([0, 1, 2, 3], long_name="bar") - self.cube.add_cell_measure(self.cell_measure, 0) - - self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) - self.val_weights = np.ones_like(self.cube.data, dtype=np.float32) - - def test_2d_coord_simple_agg(self): - # For 2d coords, slices of aggregated coord should be the same as - # aggregated slices. - res_cube = self.cube.aggregated_by("simple_agg", self.mock_agg) - for res_slice, cube_slice in zip( - res_cube.slices("simple_agg"), self.cube.slices("simple_agg") - ): - cube_slice_agg = cube_slice.aggregated_by( - "simple_agg", self.mock_agg - ) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) - self.assertEqual( - res_slice.coord("span_label"), - cube_slice_agg.coord("span_label"), - ) - - def test_agg_by_label(self): - # Aggregate a cube on a string coordinate label where label - # and val entries are not in step; the resulting cube has a val - # coord of bounded cells and a label coord of single string entries. - res_cube = self.cube.aggregated_by("label", self.mock_agg) - val_coord = AuxCoord( - np.array([1.0, 0.5, 1.0]), - bounds=np.array([[0, 2], [0, 1], [0, 2]]), - long_name="val", - ) - label_coord = AuxCoord( - np.array(["alpha", "beta", "gamma"]), - long_name="label", - units="no_unit", - ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - - def test_agg_by_label_bounded(self): - # Aggregate a cube on a string coordinate label where label - # and val entries are not in step; the resulting cube has a val - # coord of bounded cells and a label coord of single string entries. - val_points = self.cube.coord("val").points - self.cube.coord("val").bounds = np.array( - [val_points - 0.5, val_points + 0.5] - ).T - res_cube = self.cube.aggregated_by("label", self.mock_agg) - val_coord = AuxCoord( - np.array([1.0, 0.5, 1.0]), - bounds=np.array([[-0.5, 2.5], [-0.5, 1.5], [-0.5, 2.5]]), - long_name="val", - ) - label_coord = AuxCoord( - np.array(["alpha", "beta", "gamma"]), - long_name="label", - units="no_unit", - ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - - def test_2d_agg_by_label(self): - res_cube = self.cube.aggregated_by("label", self.mock_agg) - # For 2d coord, slices of aggregated coord should be the same as - # aggregated slices. - for res_slice, cube_slice in zip( - res_cube.slices("val"), self.cube.slices("val") - ): - cube_slice_agg = cube_slice.aggregated_by("label", self.mock_agg) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) - - def test_agg_by_val(self): - # Aggregate a cube on a numeric coordinate val where label - # and val entries are not in step; the resulting cube has a label - # coord with serialised labels from the aggregated cells. - res_cube = self.cube.aggregated_by("val", self.mock_agg) - val_coord = AuxCoord(np.array([0, 1, 2]), long_name="val") - exp0 = "alpha|alpha|beta|alpha|alpha|gamma" - exp1 = "beta|alpha|beta" - exp2 = "gamma|alpha" - label_coord = AuxCoord( - np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" - ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - - def test_2d_agg_by_val(self): - res_cube = self.cube.aggregated_by("val", self.mock_agg) - # For 2d coord, slices of aggregated coord should be the same as - # aggregated slices. - for res_slice, cube_slice in zip( - res_cube.slices("val"), self.cube.slices("val") - ): - cube_slice_agg = cube_slice.aggregated_by("val", self.mock_agg) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) - - def test_single_string_aggregation(self): - aux_coords = [ - (AuxCoord(["a", "b", "a"], long_name="foo"), 0), - (AuxCoord(["a", "a", "a"], long_name="bar"), 0), - ] - cube = iris.cube.Cube( - np.arange(12).reshape(3, 4), aux_coords_and_dims=aux_coords - ) - result = cube.aggregated_by("foo", MEAN) - self.assertEqual(result.shape, (2, 4)) - self.assertEqual( - result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar") - ) - - def test_ancillary_variables_and_cell_measures_kept(self): - cube_agg = self.cube.aggregated_by("val", self.mock_agg) - self.assertEqual( - cube_agg.ancillary_variables(), [self.ancillary_variable] - ) - self.assertEqual(cube_agg.cell_measures(), [self.cell_measure]) - - def test_ancillary_variables_and_cell_measures_removed(self): - cube_agg = self.cube.aggregated_by("simple_agg", self.mock_agg) - self.assertEqual(cube_agg.ancillary_variables(), []) - self.assertEqual(cube_agg.cell_measures(), []) - - def test_1d_weights(self): - self.cube.aggregated_by( - "simple_agg", self.mock_weighted_agg, weights=self.simple_weights - ) - - self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 2) - - # A simple mock.assert_called_with does not work due to ValueError: The - # truth value of an array with more than one element is ambiguous. Use - # a.any() or a.all() - call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] - np.testing.assert_array_equal( - call_1.args[0], - np.array( - [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], - ] - ), - ) - self.assertEqual(call_1.kwargs["axis"], 0) - np.testing.assert_array_almost_equal( - call_1.kwargs["weights"], - np.array( - [ - [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ] - ), - ) - - call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] - np.testing.assert_array_equal( - call_2.args[0], - np.array( - [ - [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32], - [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], - ] - ), - ) - self.assertEqual(call_2.kwargs["axis"], 0) - np.testing.assert_array_almost_equal( - call_2.kwargs["weights"], - np.array( - [ - [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], - [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], - ] - ), - ) - - def test_2d_weights(self): - self.cube.aggregated_by( - "val", self.mock_weighted_agg, weights=self.val_weights - ) - - self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 3) - - # A simple mock.assert_called_with does not work due to ValueError: The - # truth value of an array with more than one element is ambiguous. Use - # a.any() or a.all() - call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] - np.testing.assert_array_equal( - call_1.args[0], - np.array( - [ - [0, 1, 2, 6, 7, 9], - [11, 12, 13, 17, 18, 20], - [22, 23, 24, 28, 29, 31], - [33, 34, 35, 39, 40, 42], - ] - ), - ) - self.assertEqual(call_1.kwargs["axis"], 1) - np.testing.assert_array_almost_equal( - call_1.kwargs["weights"], np.ones((4, 6)) - ) - - call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] - np.testing.assert_array_equal( - call_2.args[0], - np.array([[3, 4, 10], [14, 15, 21], [25, 26, 32], [36, 37, 43]]), - ) - self.assertEqual(call_2.kwargs["axis"], 1) - np.testing.assert_array_almost_equal( - call_2.kwargs["weights"], np.ones((4, 3)) - ) - - call_3 = self.mock_weighted_agg.aggregate.mock_calls[2] - np.testing.assert_array_equal( - call_3.args[0], np.array([[5, 8], [16, 19], [27, 30], [38, 41]]) - ) - self.assertEqual(call_3.kwargs["axis"], 1) - np.testing.assert_array_almost_equal( - call_3.kwargs["weights"], np.ones((4, 2)) - ) - - def test_returned(self): - output = self.cube.aggregated_by( - "simple_agg", self.mock_weighted_agg, returned=True - ) - - self.assertTrue(isinstance(output, tuple)) - self.assertEqual(len(output), 2) - self.assertEqual(output[0].shape, (2, 11)) - self.assertEqual(output[1].shape, (2, 11)) - - def test_fail_1d_weights_wrong_len(self): - wrong_weights = np.array([1.0, 2.0]) - msg = ( - r"1D weights must have the same length as the dimension that is " - r"aggregated, got 2, expected 11" - ) - with self.assertRaisesRegex(ValueError, msg): - self.cube.aggregated_by( - "val", self.mock_weighted_agg, weights=wrong_weights - ) - - def test_fail_weights_wrong_shape(self): - wrong_weights = np.ones((42, 1)) - msg = ( - r"Weights must either be 1D or have the same shape as the cube, " - r"got shape \(42, 1\) for weights, \(4, 11\) for cube" - ) - with self.assertRaisesRegex(ValueError, msg): - self.cube.aggregated_by( - "val", self.mock_weighted_agg, weights=wrong_weights - ) - - -class Test_aggregated_by__lazy(tests.IrisTest): - def setUp(self): - self.data = np.arange(44).reshape(4, 11) - self.lazydata = as_lazy_data(self.data) - self.cube = Cube(self.lazydata) - - val_coord = AuxCoord( - [0, 0, 0, 1, 1, 2, 0, 0, 2, 0, 1], long_name="val" - ) - label_coord = AuxCoord( - [ - "alpha", - "alpha", - "beta", - "beta", - "alpha", - "gamma", - "alpha", - "alpha", - "alpha", - "gamma", - "beta", - ], - long_name="label", - units="no_unit", - ) - simple_agg_coord = AuxCoord([1, 1, 2, 2], long_name="simple_agg") - - self.label_mean = np.array( - [ - [4.0 + 1.0 / 3.0, 5.0, 7.0], - [15.0 + 1.0 / 3.0, 16.0, 18.0], - [26.0 + 1.0 / 3.0, 27.0, 29.0], - [37.0 + 1.0 / 3.0, 38.0, 40.0], - ] - ) - self.val_mean = np.array( - [ - [4.0 + 1.0 / 6.0, 5.0 + 2.0 / 3.0, 6.5], - [15.0 + 1.0 / 6.0, 16.0 + 2.0 / 3.0, 17.5], - [26.0 + 1.0 / 6.0, 27.0 + 2.0 / 3.0, 28.5], - [37.0 + 1.0 / 6.0, 38.0 + 2.0 / 3.0, 39.5], - ] - ) - - self.cube.add_aux_coord(simple_agg_coord, 0) - self.cube.add_aux_coord(val_coord, 1) - self.cube.add_aux_coord(label_coord, 1) - - self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) - self.val_weights = 2.0 * np.ones(self.cube.shape, dtype=np.float32) - - def test_agg_by_label__lazy(self): - # Aggregate a cube on a string coordinate label where label - # and val entries are not in step; the resulting cube has a val - # coord of bounded cells and a label coord of single string entries. - res_cube = self.cube.aggregated_by("label", MEAN) - val_coord = AuxCoord( - np.array([1.0, 0.5, 1.0]), - bounds=np.array([[0, 2], [0, 1], [0, 2]]), - long_name="val", - ) - label_coord = AuxCoord( - np.array(["alpha", "beta", "gamma"]), - long_name="label", - units="no_unit", - ) - self.assertTrue(res_cube.has_lazy_data()) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - self.assertArrayEqual(res_cube.data, self.label_mean) - self.assertFalse(res_cube.has_lazy_data()) - - def test_agg_by_val__lazy(self): - # Aggregate a cube on a numeric coordinate val where label - # and val entries are not in step; the resulting cube has a label - # coord with serialised labels from the aggregated cells. - res_cube = self.cube.aggregated_by("val", MEAN) - val_coord = AuxCoord(np.array([0, 1, 2]), long_name="val") - exp0 = "alpha|alpha|beta|alpha|alpha|gamma" - exp1 = "beta|alpha|beta" - exp2 = "gamma|alpha" - label_coord = AuxCoord( - np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" - ) - self.assertTrue(res_cube.has_lazy_data()) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - self.assertArrayEqual(res_cube.data, self.val_mean) - self.assertFalse(res_cube.has_lazy_data()) - - def test_single_string_aggregation__lazy(self): - aux_coords = [ - (AuxCoord(["a", "b", "a"], long_name="foo"), 0), - (AuxCoord(["a", "a", "a"], long_name="bar"), 0), - ] - cube = iris.cube.Cube( - as_lazy_data(np.arange(12).reshape(3, 4)), - aux_coords_and_dims=aux_coords, - ) - means = np.array([[4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]]) - result = cube.aggregated_by("foo", MEAN) - self.assertTrue(result.has_lazy_data()) - self.assertEqual(result.shape, (2, 4)) - self.assertEqual( - result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar") - ) - self.assertArrayEqual(result.data, means) - self.assertFalse(result.has_lazy_data()) - - def test_1d_weights__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) - - cube_agg = self.cube.aggregated_by( - "simple_agg", SUM, weights=self.simple_weights - ) - - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(cube_agg.has_lazy_data()) - self.assertEqual(cube_agg.shape, (2, 11)) - - row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] - row_1 = [ - 110.0, - 114.0, - 118.0, - 122.0, - 126.0, - 130.0, - 134.0, - 138.0, - 142.0, - 146.0, - 150.0, - ] - np.testing.assert_array_almost_equal( - cube_agg.data, np.array([row_0, row_1]) - ) - - def test_2d_weights__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) - - cube_agg = self.cube.aggregated_by( - "val", SUM, weights=self.val_weights - ) - - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(cube_agg.has_lazy_data()) - - self.assertEqual(cube_agg.shape, (4, 3)) - np.testing.assert_array_almost_equal( - cube_agg.data, - np.array( - [ - [50.0, 34.0, 26.0], - [182.0, 100.0, 70.0], - [314.0, 166.0, 114.0], - [446.0, 232.0, 158.0], - ] - ), - ) - - def test_returned__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) - - output = self.cube.aggregated_by( - "simple_agg", SUM, weights=self.simple_weights, returned=True - ) - - self.assertTrue(self.cube.has_lazy_data()) - - self.assertTrue(isinstance(output, tuple)) - self.assertEqual(len(output), 2) - - cube = output[0] - self.assertTrue(isinstance(cube, Cube)) - self.assertTrue(cube.has_lazy_data()) - self.assertEqual(cube.shape, (2, 11)) - row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] - row_1 = [ - 110.0, - 114.0, - 118.0, - 122.0, - 126.0, - 130.0, - 134.0, - 138.0, - 142.0, - 146.0, - 150.0, - ] - np.testing.assert_array_almost_equal( - cube.data, np.array([row_0, row_1]) - ) - - weights = output[1] - self.assertEqual(weights.shape, (2, 11)) - np.testing.assert_array_almost_equal( - weights, - np.array( - [ - [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], - [4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0], - ] - ), - ) - - class Test_rolling_window(tests.IrisTest): def setUp(self): self.cube = Cube(np.arange(6)) diff --git a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py new file mode 100644 index 0000000000..3230e3de00 --- /dev/null +++ b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py @@ -0,0 +1,845 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the `iris.cube.Cube` class aggregated_by method.""" + +# import iris tests first so that some things can be initialised +# before importing anything else. +import iris.tests as tests # isort:skip + +from unittest import mock + +from cf_units import Unit +import numpy as np + +from iris._lazy_data import as_lazy_data +import iris.analysis +from iris.analysis import MEAN, SUM, Aggregator, WeightedAggregator +import iris.aux_factory +import iris.coords +from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord +from iris.cube import Cube +import iris.exceptions + + +class Test_aggregated_by(tests.IrisTest): + def setUp(self): + self.cube = Cube(np.arange(44).reshape(4, 11)) + + val_coord = AuxCoord( + [0, 0, 0, 1, 1, 2, 0, 0, 2, 0, 1], long_name="val" + ) + label_coord = AuxCoord( + [ + "alpha", + "alpha", + "beta", + "beta", + "alpha", + "gamma", + "alpha", + "alpha", + "alpha", + "gamma", + "beta", + ], + long_name="label", + units="no_unit", + ) + simple_agg_coord = AuxCoord([1, 1, 2, 2], long_name="simple_agg") + spanning_coord = AuxCoord( + np.arange(44).reshape(4, 11), long_name="spanning" + ) + spanning_label_coord = AuxCoord( + np.arange(1, 441, 10).reshape(4, 11).astype(str), + long_name="span_label", + units="no_unit", + ) + + self.cube.add_aux_coord(simple_agg_coord, 0) + self.cube.add_aux_coord(val_coord, 1) + self.cube.add_aux_coord(label_coord, 1) + self.cube.add_aux_coord(spanning_coord, (0, 1)) + self.cube.add_aux_coord(spanning_label_coord, (0, 1)) + + self.mock_agg = mock.Mock(spec=Aggregator) + self.mock_agg.cell_method = [] + self.mock_agg.aggregate = mock.Mock( + return_value=mock.Mock(dtype="object") + ) + self.mock_agg.aggregate_shape = mock.Mock(return_value=()) + self.mock_agg.lazy_func = None + self.mock_agg.post_process = mock.Mock(side_effect=lambda x, y, z: x) + + self.mock_weighted_agg = mock.Mock(spec=WeightedAggregator) + self.mock_weighted_agg.cell_method = [] + + def mock_weighted_aggregate(*_, **kwargs): + if kwargs.get("returned", False): + return (mock.Mock(dtype="object"), mock.Mock(dtype="object")) + return mock.Mock(dtype="object") + + self.mock_weighted_agg.aggregate = mock.Mock( + side_effect=mock_weighted_aggregate + ) + self.mock_weighted_agg.aggregate_shape = mock.Mock(return_value=()) + self.mock_weighted_agg.lazy_func = None + self.mock_weighted_agg.post_process = mock.Mock( + side_effect=lambda x, y, z, **kwargs: y + ) + + self.ancillary_variable = AncillaryVariable( + [0, 1, 2, 3], long_name="foo" + ) + self.cube.add_ancillary_variable(self.ancillary_variable, 0) + self.cell_measure = CellMeasure([0, 1, 2, 3], long_name="bar") + self.cube.add_cell_measure(self.cell_measure, 0) + + self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) + self.val_weights = np.ones_like(self.cube.data, dtype=np.float32) + + def test_2d_coord_simple_agg(self): + # For 2d coords, slices of aggregated coord should be the same as + # aggregated slices. + res_cube = self.cube.aggregated_by("simple_agg", self.mock_agg) + for res_slice, cube_slice in zip( + res_cube.slices("simple_agg"), self.cube.slices("simple_agg") + ): + cube_slice_agg = cube_slice.aggregated_by( + "simple_agg", self.mock_agg + ) + self.assertEqual( + res_slice.coord("spanning"), cube_slice_agg.coord("spanning") + ) + self.assertEqual( + res_slice.coord("span_label"), + cube_slice_agg.coord("span_label"), + ) + + def test_agg_by_label(self): + # Aggregate a cube on a string coordinate label where label + # and val entries are not in step; the resulting cube has a val + # coord of bounded cells and a label coord of single string entries. + res_cube = self.cube.aggregated_by("label", self.mock_agg) + val_coord = AuxCoord( + np.array([1.0, 0.5, 1.0]), + bounds=np.array([[0, 2], [0, 1], [0, 2]]), + long_name="val", + ) + label_coord = AuxCoord( + np.array(["alpha", "beta", "gamma"]), + long_name="label", + units="no_unit", + ) + self.assertEqual(res_cube.coord("val"), val_coord) + self.assertEqual(res_cube.coord("label"), label_coord) + + def test_agg_by_label_bounded(self): + # Aggregate a cube on a string coordinate label where label + # and val entries are not in step; the resulting cube has a val + # coord of bounded cells and a label coord of single string entries. + val_points = self.cube.coord("val").points + self.cube.coord("val").bounds = np.array( + [val_points - 0.5, val_points + 0.5] + ).T + res_cube = self.cube.aggregated_by("label", self.mock_agg) + val_coord = AuxCoord( + np.array([1.0, 0.5, 1.0]), + bounds=np.array([[-0.5, 2.5], [-0.5, 1.5], [-0.5, 2.5]]), + long_name="val", + ) + label_coord = AuxCoord( + np.array(["alpha", "beta", "gamma"]), + long_name="label", + units="no_unit", + ) + self.assertEqual(res_cube.coord("val"), val_coord) + self.assertEqual(res_cube.coord("label"), label_coord) + + def test_2d_agg_by_label(self): + res_cube = self.cube.aggregated_by("label", self.mock_agg) + # For 2d coord, slices of aggregated coord should be the same as + # aggregated slices. + for res_slice, cube_slice in zip( + res_cube.slices("val"), self.cube.slices("val") + ): + cube_slice_agg = cube_slice.aggregated_by("label", self.mock_agg) + self.assertEqual( + res_slice.coord("spanning"), cube_slice_agg.coord("spanning") + ) + + def test_agg_by_val(self): + # Aggregate a cube on a numeric coordinate val where label + # and val entries are not in step; the resulting cube has a label + # coord with serialised labels from the aggregated cells. + res_cube = self.cube.aggregated_by("val", self.mock_agg) + val_coord = AuxCoord(np.array([0, 1, 2]), long_name="val") + exp0 = "alpha|alpha|beta|alpha|alpha|gamma" + exp1 = "beta|alpha|beta" + exp2 = "gamma|alpha" + label_coord = AuxCoord( + np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" + ) + self.assertEqual(res_cube.coord("val"), val_coord) + self.assertEqual(res_cube.coord("label"), label_coord) + + def test_2d_agg_by_val(self): + res_cube = self.cube.aggregated_by("val", self.mock_agg) + # For 2d coord, slices of aggregated coord should be the same as + # aggregated slices. + for res_slice, cube_slice in zip( + res_cube.slices("val"), self.cube.slices("val") + ): + cube_slice_agg = cube_slice.aggregated_by("val", self.mock_agg) + self.assertEqual( + res_slice.coord("spanning"), cube_slice_agg.coord("spanning") + ) + + def test_single_string_aggregation(self): + aux_coords = [ + (AuxCoord(["a", "b", "a"], long_name="foo"), 0), + (AuxCoord(["a", "a", "a"], long_name="bar"), 0), + ] + cube = iris.cube.Cube( + np.arange(12).reshape(3, 4), aux_coords_and_dims=aux_coords + ) + result = cube.aggregated_by("foo", MEAN) + self.assertEqual(result.shape, (2, 4)) + self.assertEqual( + result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar") + ) + + def test_ancillary_variables_and_cell_measures_kept(self): + cube_agg = self.cube.aggregated_by("val", self.mock_agg) + self.assertEqual( + cube_agg.ancillary_variables(), [self.ancillary_variable] + ) + self.assertEqual(cube_agg.cell_measures(), [self.cell_measure]) + + def test_ancillary_variables_and_cell_measures_removed(self): + cube_agg = self.cube.aggregated_by("simple_agg", self.mock_agg) + self.assertEqual(cube_agg.ancillary_variables(), []) + self.assertEqual(cube_agg.cell_measures(), []) + + def test_1d_weights(self): + self.cube.aggregated_by( + "simple_agg", self.mock_weighted_agg, weights=self.simple_weights + ) + + self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 2) + + # A simple mock.assert_called_with does not work due to ValueError: The + # truth value of an array with more than one element is ambiguous. Use + # a.any() or a.all() + call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] + np.testing.assert_array_equal( + call_1.args[0], + np.array( + [ + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], + ] + ), + ) + self.assertEqual(call_1.kwargs["axis"], 0) + np.testing.assert_array_almost_equal( + call_1.kwargs["weights"], + np.array( + [ + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], + ] + ), + ) + + call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] + np.testing.assert_array_equal( + call_2.args[0], + np.array( + [ + [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32], + [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], + ] + ), + ) + self.assertEqual(call_2.kwargs["axis"], 0) + np.testing.assert_array_almost_equal( + call_2.kwargs["weights"], + np.array( + [ + [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], + [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], + ] + ), + ) + + def test_2d_weights(self): + self.cube.aggregated_by( + "val", self.mock_weighted_agg, weights=self.val_weights + ) + + self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 3) + + # A simple mock.assert_called_with does not work due to ValueError: The + # truth value of an array with more than one element is ambiguous. Use + # a.any() or a.all() + call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] + np.testing.assert_array_equal( + call_1.args[0], + np.array( + [ + [0, 1, 2, 6, 7, 9], + [11, 12, 13, 17, 18, 20], + [22, 23, 24, 28, 29, 31], + [33, 34, 35, 39, 40, 42], + ] + ), + ) + self.assertEqual(call_1.kwargs["axis"], 1) + np.testing.assert_array_almost_equal( + call_1.kwargs["weights"], np.ones((4, 6)) + ) + + call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] + np.testing.assert_array_equal( + call_2.args[0], + np.array([[3, 4, 10], [14, 15, 21], [25, 26, 32], [36, 37, 43]]), + ) + self.assertEqual(call_2.kwargs["axis"], 1) + np.testing.assert_array_almost_equal( + call_2.kwargs["weights"], np.ones((4, 3)) + ) + + call_3 = self.mock_weighted_agg.aggregate.mock_calls[2] + np.testing.assert_array_equal( + call_3.args[0], np.array([[5, 8], [16, 19], [27, 30], [38, 41]]) + ) + self.assertEqual(call_3.kwargs["axis"], 1) + np.testing.assert_array_almost_equal( + call_3.kwargs["weights"], np.ones((4, 2)) + ) + + def test_returned(self): + output = self.cube.aggregated_by( + "simple_agg", self.mock_weighted_agg, returned=True + ) + + self.assertTrue(isinstance(output, tuple)) + self.assertEqual(len(output), 2) + self.assertEqual(output[0].shape, (2, 11)) + self.assertEqual(output[1].shape, (2, 11)) + + def test_fail_1d_weights_wrong_len(self): + wrong_weights = np.array([1.0, 2.0]) + msg = ( + r"1D weights must have the same length as the dimension that is " + r"aggregated, got 2, expected 11" + ) + with self.assertRaisesRegex(ValueError, msg): + self.cube.aggregated_by( + "val", self.mock_weighted_agg, weights=wrong_weights + ) + + def test_fail_weights_wrong_shape(self): + wrong_weights = np.ones((42, 1)) + msg = ( + r"Weights must either be 1D or have the same shape as the cube, " + r"got shape \(42, 1\) for weights, \(4, 11\) for cube" + ) + with self.assertRaisesRegex(ValueError, msg): + self.cube.aggregated_by( + "val", self.mock_weighted_agg, weights=wrong_weights + ) + + +class Test_aggregated_by__lazy(tests.IrisTest): + def setUp(self): + self.data = np.arange(44).reshape(4, 11) + self.lazydata = as_lazy_data(self.data) + self.cube = Cube(self.lazydata) + + val_coord = AuxCoord( + [0, 0, 0, 1, 1, 2, 0, 0, 2, 0, 1], long_name="val" + ) + label_coord = AuxCoord( + [ + "alpha", + "alpha", + "beta", + "beta", + "alpha", + "gamma", + "alpha", + "alpha", + "alpha", + "gamma", + "beta", + ], + long_name="label", + units="no_unit", + ) + simple_agg_coord = AuxCoord([1, 1, 2, 2], long_name="simple_agg") + + self.label_mean = np.array( + [ + [4.0 + 1.0 / 3.0, 5.0, 7.0], + [15.0 + 1.0 / 3.0, 16.0, 18.0], + [26.0 + 1.0 / 3.0, 27.0, 29.0], + [37.0 + 1.0 / 3.0, 38.0, 40.0], + ] + ) + self.val_mean = np.array( + [ + [4.0 + 1.0 / 6.0, 5.0 + 2.0 / 3.0, 6.5], + [15.0 + 1.0 / 6.0, 16.0 + 2.0 / 3.0, 17.5], + [26.0 + 1.0 / 6.0, 27.0 + 2.0 / 3.0, 28.5], + [37.0 + 1.0 / 6.0, 38.0 + 2.0 / 3.0, 39.5], + ] + ) + + self.cube.add_aux_coord(simple_agg_coord, 0) + self.cube.add_aux_coord(val_coord, 1) + self.cube.add_aux_coord(label_coord, 1) + + self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) + self.val_weights = 2.0 * np.ones(self.cube.shape, dtype=np.float32) + + def test_agg_by_label__lazy(self): + # Aggregate a cube on a string coordinate label where label + # and val entries are not in step; the resulting cube has a val + # coord of bounded cells and a label coord of single string entries. + res_cube = self.cube.aggregated_by("label", MEAN) + val_coord = AuxCoord( + np.array([1.0, 0.5, 1.0]), + bounds=np.array([[0, 2], [0, 1], [0, 2]]), + long_name="val", + ) + label_coord = AuxCoord( + np.array(["alpha", "beta", "gamma"]), + long_name="label", + units="no_unit", + ) + self.assertTrue(res_cube.has_lazy_data()) + self.assertEqual(res_cube.coord("val"), val_coord) + self.assertEqual(res_cube.coord("label"), label_coord) + self.assertArrayEqual(res_cube.data, self.label_mean) + self.assertFalse(res_cube.has_lazy_data()) + + def test_agg_by_val__lazy(self): + # Aggregate a cube on a numeric coordinate val where label + # and val entries are not in step; the resulting cube has a label + # coord with serialised labels from the aggregated cells. + res_cube = self.cube.aggregated_by("val", MEAN) + val_coord = AuxCoord(np.array([0, 1, 2]), long_name="val") + exp0 = "alpha|alpha|beta|alpha|alpha|gamma" + exp1 = "beta|alpha|beta" + exp2 = "gamma|alpha" + label_coord = AuxCoord( + np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" + ) + self.assertTrue(res_cube.has_lazy_data()) + self.assertEqual(res_cube.coord("val"), val_coord) + self.assertEqual(res_cube.coord("label"), label_coord) + self.assertArrayEqual(res_cube.data, self.val_mean) + self.assertFalse(res_cube.has_lazy_data()) + + def test_single_string_aggregation__lazy(self): + aux_coords = [ + (AuxCoord(["a", "b", "a"], long_name="foo"), 0), + (AuxCoord(["a", "a", "a"], long_name="bar"), 0), + ] + cube = iris.cube.Cube( + as_lazy_data(np.arange(12).reshape(3, 4)), + aux_coords_and_dims=aux_coords, + ) + means = np.array([[4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]]) + result = cube.aggregated_by("foo", MEAN) + self.assertTrue(result.has_lazy_data()) + self.assertEqual(result.shape, (2, 4)) + self.assertEqual( + result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar") + ) + self.assertArrayEqual(result.data, means) + self.assertFalse(result.has_lazy_data()) + + def test_1d_weights__lazy(self): + self.assertTrue(self.cube.has_lazy_data()) + + cube_agg = self.cube.aggregated_by( + "simple_agg", SUM, weights=self.simple_weights + ) + + self.assertTrue(self.cube.has_lazy_data()) + self.assertTrue(cube_agg.has_lazy_data()) + self.assertEqual(cube_agg.shape, (2, 11)) + + row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + row_1 = [ + 110.0, + 114.0, + 118.0, + 122.0, + 126.0, + 130.0, + 134.0, + 138.0, + 142.0, + 146.0, + 150.0, + ] + np.testing.assert_array_almost_equal( + cube_agg.data, np.array([row_0, row_1]) + ) + + def test_2d_weights__lazy(self): + self.assertTrue(self.cube.has_lazy_data()) + + cube_agg = self.cube.aggregated_by( + "val", SUM, weights=self.val_weights + ) + + self.assertTrue(self.cube.has_lazy_data()) + self.assertTrue(cube_agg.has_lazy_data()) + + self.assertEqual(cube_agg.shape, (4, 3)) + np.testing.assert_array_almost_equal( + cube_agg.data, + np.array( + [ + [50.0, 34.0, 26.0], + [182.0, 100.0, 70.0], + [314.0, 166.0, 114.0], + [446.0, 232.0, 158.0], + ] + ), + ) + + def test_returned__lazy(self): + self.assertTrue(self.cube.has_lazy_data()) + + output = self.cube.aggregated_by( + "simple_agg", SUM, weights=self.simple_weights, returned=True + ) + + self.assertTrue(self.cube.has_lazy_data()) + + self.assertTrue(isinstance(output, tuple)) + self.assertEqual(len(output), 2) + + cube = output[0] + self.assertTrue(isinstance(cube, Cube)) + self.assertTrue(cube.has_lazy_data()) + self.assertEqual(cube.shape, (2, 11)) + row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + row_1 = [ + 110.0, + 114.0, + 118.0, + 122.0, + 126.0, + 130.0, + 134.0, + 138.0, + 142.0, + 146.0, + 150.0, + ] + np.testing.assert_array_almost_equal( + cube.data, np.array([row_0, row_1]) + ) + + weights = output[1] + self.assertEqual(weights.shape, (2, 11)) + np.testing.assert_array_almost_equal( + weights, + np.array( + [ + [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], + [4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0], + ] + ), + ) + + +class Test_aggregated_by__climatology(tests.IrisTest): + def setUp(self): + self.data = np.arange(100).reshape(20, 5) + self.aggregator = iris.analysis.MEAN + + def get_result( + self, + transpose: bool = False, + second_categorised: bool = False, + bounds: bool = False, + partially_aligned: bool = False, + partially_aligned_timelike: bool = False, + invalid_units: bool = False, + already_climatological: bool = False, + climatological_op: bool = True, + ) -> Cube: + cube_data = self.data + if transpose: + cube_data = cube_data.T + axes = [1, 0] + else: + axes = [0, 1] + if not invalid_units: + units = Unit("days since 1970-01-01") + else: + units = Unit("m") + if partially_aligned_timelike: + pa_units = Unit("days since 1970-01-01") + else: + pa_units = Unit("m") + + # DimCoords + aligned_coord = DimCoord( + np.arange(20), + long_name="aligned", + units=units, + ) + orthogonal_coord = DimCoord(np.arange(5), long_name="orth") + + if bounds: + aligned_coord.guess_bounds() + + aligned_coord.climatological = already_climatological + + dim_coords_and_dims = zip([aligned_coord, orthogonal_coord], axes) + + # AuxCoords + categorised_coord1 = AuxCoord( + np.tile([0, 1], 10), long_name="cat1", units=Unit("month") + ) + + if second_categorised: + categorised_coord2 = AuxCoord( + np.tile([0, 1, 2, 3, 4], 4), long_name="cat2" + ) + categorised_coords = [categorised_coord1, categorised_coord2] + else: + categorised_coords = categorised_coord1 + + aux_coords_and_dims = [ + (categorised_coord1, axes[0]), + ] + + if second_categorised: + aux_coords_and_dims.append((categorised_coord2, axes[0])) + + if partially_aligned: + partially_aligned_coord = AuxCoord( + cube_data + 1, + long_name="part_aligned", + units=pa_units, + ) + aux_coords_and_dims.append((partially_aligned_coord, (0, 1))) + + # Build cube + in_cube = iris.cube.Cube( + cube_data, + long_name="wibble", + dim_coords_and_dims=dim_coords_and_dims, + aux_coords_and_dims=aux_coords_and_dims, + ) + + out_cube = in_cube.aggregated_by( + categorised_coords, + self.aggregator, + climatological=climatological_op, + ) + + return out_cube + + def test_basic(self): + """ + Check the least complicated version works (set climatological, set + points correctly). + """ + result = self.get_result() + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, np.arange(2)) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + self.assertTrue(aligned_coord.climatological) + self.assertIn(aligned_coord, result.dim_coords) + + categorised_coord = result.coord("cat1") + self.assertArrayEqual(categorised_coord.points, np.arange(2)) + self.assertIsNone(categorised_coord.bounds) + self.assertFalse(categorised_coord.climatological) + + def test_2d_other_coord(self): + """ + Check that we can handle aggregation applying to a 2d AuxCoord that + covers the aggregation dimension and another one. + """ + result = self.get_result(partially_aligned=True) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, np.arange(2)) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + self.assertTrue(aligned_coord.climatological) + + part_aligned_coord = result.coord("part_aligned") + self.assertArrayEqual( + part_aligned_coord.points, np.arange(46, 56).reshape(2, 5) + ) + self.assertArrayEqual( + part_aligned_coord.bounds, + np.array([np.arange(1, 11), np.arange(91, 101)]).T.reshape( + 2, 5, 2 + ), + ) + self.assertFalse(part_aligned_coord.climatological) + + def test_2d_timelike_other_coord(self): + """ + Check that we can handle aggregation applying to a 2d AuxCoord that + covers the aggregation dimension and another one. + """ + result = self.get_result( + partially_aligned=True, partially_aligned_timelike=True + ) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, np.arange(2)) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + self.assertTrue(aligned_coord.climatological) + + part_aligned_coord = result.coord("part_aligned") + self.assertArrayEqual( + part_aligned_coord.points, np.arange(1, 11).reshape(2, 5) + ) + self.assertArrayEqual( + part_aligned_coord.bounds, + np.array([np.arange(1, 11), np.arange(91, 101)]).T.reshape( + 2, 5, 2 + ), + ) + self.assertTrue(part_aligned_coord.climatological) + + def test_transposed(self): + """ + Check that we can handle the axis of aggregation being a different one. + """ + result = self.get_result(transpose=True) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, np.arange(2)) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + self.assertTrue(aligned_coord.climatological) + + categorised_coord = result.coord("cat1") + self.assertArrayEqual(categorised_coord.points, np.arange(2)) + self.assertIsNone(categorised_coord.bounds) + self.assertFalse(categorised_coord.climatological) + + def test_bounded(self): + """Check that we handle bounds correctly.""" + result = self.get_result(bounds=True) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, [-0.5, 0.5]) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) + ) + self.assertTrue(aligned_coord.climatological) + + def test_multiple_agg_coords(self): + """ + Check that we can aggregate on multiple coords on the same axis. + """ + result = self.get_result(second_categorised=True) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, np.arange(10)) + self.assertArrayEqual( + aligned_coord.bounds, + np.array([np.arange(10), np.arange(10, 20)]).T, + ) + self.assertTrue(aligned_coord.climatological) + + categorised_coord1 = result.coord("cat1") + self.assertArrayEqual( + categorised_coord1.points, np.tile(np.arange(2), 5) + ) + self.assertIsNone(categorised_coord1.bounds) + self.assertFalse(categorised_coord1.climatological) + + categorised_coord2 = result.coord("cat2") + self.assertArrayEqual( + categorised_coord2.points, np.tile(np.arange(5), 2) + ) + self.assertIsNone(categorised_coord2.bounds) + self.assertFalse(categorised_coord2.climatological) + + def test_non_climatological_units(self): + """ + Check that the failure to set the climatological flag on an incompatible + unit is handled quietly. + """ + result = self.get_result(invalid_units=True) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, np.arange(9, 11)) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[0, 18], [1, 19]]) + ) + self.assertFalse(aligned_coord.climatological) + + def test_clim_in_clim_op(self): + """ + Check the least complicated version works (set climatological, set + points correctly). For the input coordinate to be climatological, it + must have bounds + """ + result = self.get_result(bounds=True, already_climatological=True) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, [-0.5, 0.5]) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) + ) + self.assertTrue(aligned_coord.climatological) + + categorised_coord = result.coord("cat1") + self.assertArrayEqual(categorised_coord.points, np.arange(2)) + self.assertIsNone(categorised_coord.bounds) + self.assertFalse(categorised_coord.climatological) + + def test_clim_in_no_clim_op(self): + """ + Check the least complicated version works (set climatological, set + points correctly). For the input coordinate to be climatological, it + must have bounds. + """ + result = self.get_result( + bounds=True, already_climatological=True, climatological_op=False + ) + + aligned_coord = result.coord("aligned") + self.assertArrayEqual(aligned_coord.points, np.arange(9, 11)) + self.assertArrayEqual( + aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) + ) + self.assertTrue(aligned_coord.climatological) + + categorised_coord = result.coord("cat1") + self.assertArrayEqual(categorised_coord.points, np.arange(2)) + self.assertIsNone(categorised_coord.bounds) + self.assertFalse(categorised_coord.climatological) + + +if __name__ == "__main__": + tests.main() From c65462a07791949db7cc0067e26d52dd1c6a81bc Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 12 May 2022 13:30:47 +0100 Subject: [PATCH 103/319] Remove Mule monkey patch. (#4738) --- .../benchmarks/generate_data/um_files.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/benchmarks/benchmarks/generate_data/um_files.py b/benchmarks/benchmarks/generate_data/um_files.py index 1037954f08..39773bbb4b 100644 --- a/benchmarks/benchmarks/generate_data/um_files.py +++ b/benchmarks/benchmarks/generate_data/um_files.py @@ -24,7 +24,6 @@ def _create_um_files( from datetime import datetime from tempfile import NamedTemporaryFile - from mo_pack import compress_wgdos as mo_pack_compress from mule import ArrayDataProvider, Field3, FieldsFile from mule.pp import fields_to_pp_file import numpy as np @@ -32,23 +31,6 @@ def _create_um_files( from iris import load_cube from iris import save as save_cube - def packing_patch(*compress_args, **compress_kwargs) -> bytes: - """ - Force conversion from returned :class:`memoryview` to :class:`bytes`. - - Downstream uses of :func:`mo_pack.compress_wgdos` were written - for the ``Python2`` behaviour, where the returned buffer had a - different ``__len__`` value to the current :class:`memoryview`. - Unable to fix directly in Mule, so monkey patching for now. - """ - return mo_pack_compress(*compress_args, **compress_kwargs).tobytes() - - import mo_pack - - mo_pack.compress_wgdos = packing_patch - - ######## - template = { "fixed_length_header": {"dataset_type": 3, "grid_staggering": 3}, "integer_constants": { From af315380da94f3d191b7f92de27f2b379cd0125d Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 12 May 2022 17:02:33 +0100 Subject: [PATCH 104/319] climatology doc tweak (#4740) --- docs/src/whatsnew/latest.rst | 3 ++- lib/iris/cube.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 6ed6764cfa..5a0c72f23e 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -61,7 +61,8 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ and `@rcomer`_ (reviewer) added a ``climatological`` keyword to :meth:`~iris.cube.Cube.aggregated_by` that causes the climatological flag to be set and the point for each cell to equal its first bound, thereby - preserving the time of year. + preserving the time of year. (:issue:`1422`, :issue:`4098`, :issue:`4665`, + :pull:`4723`) 🐛 Bugs Fixed diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 386e77bdc5..6211f0a727 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -3874,7 +3874,7 @@ def aggregated_by( Indicates whether the output is expected to be climatological. For any aggregated time coord(s), this causes the climatological flag to be set and the point for each cell to equal its first bound, thereby - preserving the time of year + preserving the time of year. Returns ------- From 81dd6c8b2b766979dd779a47c0d8fc080b2d2aa3 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 13 May 2022 11:04:48 +0100 Subject: [PATCH 105/319] Mesh docs - include examples of converting different mesh formats. (#4739) * Mesh docs - include examples of converting different mesh formats. * SMC mesh example fix gotcha. * Add sample images to mesh conversion examples. * Mesh conversion examples - fix bullet points. * Mesh conversion examples - print meshes. * Whats New entry. * Mesh conversion examples - more detailed comments. --- .../ugrid/images/fesom_mesh.png | Bin 0 -> 615513 bytes .../further_topics/ugrid/images/smc_mesh.png | Bin 0 -> 615500 bytes docs/src/further_topics/ugrid/index.rst | 2 + .../src/further_topics/ugrid/other_meshes.rst | 225 ++++++++++++++++++ docs/src/whatsnew/latest.rst | 3 + 5 files changed, 230 insertions(+) create mode 100644 docs/src/further_topics/ugrid/images/fesom_mesh.png create mode 100644 docs/src/further_topics/ugrid/images/smc_mesh.png create mode 100644 docs/src/further_topics/ugrid/other_meshes.rst diff --git a/docs/src/further_topics/ugrid/images/fesom_mesh.png b/docs/src/further_topics/ugrid/images/fesom_mesh.png new file mode 100644 index 0000000000000000000000000000000000000000..283899a94b0221a4cee4ff128629bf3a6e53f32f GIT binary patch literal 615513 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%0D&b!JS?05-}+{B1*KLJ!dXS6e5kF6X>)yyrfs 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 z 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%0DCJeaHf6(z! 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�qf|Q2h*}|?`*qIBrdyVK-Yf4r`mA_|Ah{9kTTEJ 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ⅈCf*%o4&6_At%Vjh8t3=Kcq-p)eVtOaBNQ3 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 literal 0 HcmV?d00001 diff --git a/docs/src/further_topics/ugrid/images/smc_mesh.png b/docs/src/further_topics/ugrid/images/smc_mesh.png new file mode 100644 index 0000000000000000000000000000000000000000..8c5a9d86eb3911f24f817241146782328c348439 GIT binary patch literal 615500 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

_w$<;N(R=vIGQt-KARJL&r`^cd7o+5k)O^9F&f} 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

J{YR94TF99N8Hc)@uh@t1y2@4pH6d-qKc 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

7a-yzL4=hfQkCC5gm^b}NZx+K%Wq5-fl};Vm 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�adz3b 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

hz9uW3pMLhaU96wxv*xrDfbx%! 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

L*4ctrme7w>?wBCK4=-)W-Wy#=_^VuPCUbJO5g4 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 literal 0 HcmV?d00001 diff --git a/docs/src/further_topics/ugrid/index.rst b/docs/src/further_topics/ugrid/index.rst index 81ba24428a..c45fd271a2 100644 --- a/docs/src/further_topics/ugrid/index.rst +++ b/docs/src/further_topics/ugrid/index.rst @@ -38,6 +38,7 @@ Read on to find out more... * :doc:`data_model` - learn why the mesh experience is so different. * :doc:`partner_packages` - meet some optional dependencies that provide powerful mesh operations. * :doc:`operations` - experience how your workflows will look when written for mesh data. +* :doc:`other_meshes` - check out some examples of converting various mesh formats into Iris' mesh format. .. Need an actual TOC to get Sphinx working properly, but have hidden it in @@ -50,5 +51,6 @@ Read on to find out more... data_model partner_packages operations + other_meshes __ CF-UGRID_ diff --git a/docs/src/further_topics/ugrid/other_meshes.rst b/docs/src/further_topics/ugrid/other_meshes.rst new file mode 100644 index 0000000000..e6f477624e --- /dev/null +++ b/docs/src/further_topics/ugrid/other_meshes.rst @@ -0,0 +1,225 @@ +.. _other_meshes: + +Converting Other Mesh Formats +***************************** + +Iris' Mesh Data Model is based primarily on the CF-UGRID conventions (see +:doc:`data_model`), but other mesh formats can be converted to fit into this +model, **enabling use of Iris' specialised mesh support**. Below are some +examples demonstrating how this works for various mesh formats. + +.. contents:: + :local: + +`FESOM 1.4`_ Voronoi Polygons +----------------------------- +.. figure:: images/fesom_mesh.png + :width: 300 + :alt: Sample of FESOM mesh voronoi polygons, with variable numbers of sides. + +A FESOM mesh encoded in a NetCDF file includes: + +* X+Y point coordinates +* X+Y corners coordinates of the Voronoi Polygons around these points - + represented as the bounds of the coordinates + +To represent the Voronoi Polygons as faces, the corner coordinates will be used +as the **nodes** when creating the Iris +:class:`~iris.experimental.ugrid.mesh.Mesh`. + +.. dropdown:: :opticon:`code` + + .. code-block:: python + + >>> import iris + >>> from iris.experimental.ugrid import Mesh + + + >>> temperature_cube = iris.load_cube("my_file.nc", "sea_surface_temperature") + >>> print(temperature_cube) + sea_surface_temperature / (degC) (time: 12; -- : 126859) + Dimension coordinates: + time x - + Auxiliary coordinates: + latitude - x + longitude - x + Cell methods: + mean where sea area + mean time + Attributes: + grid 'FESOM 1.4 (unstructured grid in the horizontal with 126859 wet nodes;... + ... + + >>> print(temperature_cube.coord("longitude")) + AuxCoord : longitude / (degrees) + points: + bounds: + shape: (126859,) bounds(126859, 18) + dtype: float64 + standard_name: 'longitude' + var_name: 'lon' + + # Use a Mesh to represent the Cube's horizontal geography, by replacing + # the existing face AuxCoords with new MeshCoords. + >>> fesom_mesh = Mesh.from_coords(temperature_cube.coord('longitude'), + ... temperature_cube.coord('latitude')) + >>> for new_coord in fesom_mesh.to_MeshCoords("face"): + ... old_coord = temperature_cube.coord(new_coord.name()) + ... unstructured_dim, = old_coord.cube_dims(temperature_cube) + ... temperature_cube.remove_coord(old_coord) + ... temperature_cube.add_aux_coord(new_coord, unstructured_dim) + + >>> print(temperature_cube) + sea_surface_temperature / (degC) (time: 12; -- : 126859) + Dimension coordinates: + time x - + Mesh coordinates: + latitude - x + longitude - x + Cell methods: + mean where sea area + mean time + Attributes: + grid 'FESOM 1.4 (unstructured grid in the horizontal with 126859 wet nodes;... + ... + + >>> print(temperature_cube.mesh) + Mesh : 'unknown' + topology_dimension: 2 + node + node_dimension: 'Mesh2d_node' + node coordinates + shape(2283462,)> + shape(2283462,)> + face + face_dimension: 'Mesh2d_face' + face_node_connectivity: shape(126859, 18)> + face coordinates + shape(126859,)> + shape(126859,)> + +`WAVEWATCH III`_ Spherical Multi-Cell (SMC) WAVE Quad Grid +---------------------------------------------------------- +.. figure:: images/smc_mesh.png + :width: 300 + :alt: Sample of an SMC mesh, with decreasing quad sizes at the coastlines. + +An SMC grid encoded in a NetCDF file includes: + +* X+Y face centre coordinates +* X+Y base face sizes +* X+Y face size factors + +From this information we can derive face corner coordinates, which will be used +as the **nodes** when creating the Iris +:class:`~iris.experimental.ugrid.mesh.Mesh`. + + +.. dropdown:: :opticon:`code` + + .. code-block:: python + + >>> import iris + >>> from iris.experimental.ugrid import Mesh + >>> import numpy as np + + + >>> wave_cube = iris.load_cube("my_file.nc", "sea_surface_wave_significant_height") + >>> print(wave_cube) + sea_surface_wave_significant_height / (m) (time: 7; -- : 666328) + Dimension coordinates: + time x - + Auxiliary coordinates: + forecast_period x - + latitude - x + latitude cell size factor - x + longitude - x + longitude cell size factor - x + Scalar coordinates: + forecast_reference_time 2021-12-05 00:00:00 + Attributes: + SIN4 namelist parameter BETAMAX 1.39 + SMC_grid_type 'seapoint' + WAVEWATCH_III_switches 'NOGRB SHRD PR2 UNO SMC FLX0 LN1 ST4 NL1 BT1 DB1 TR0 BS0 IC0 IS0 REF0 WNT1... + WAVEWATCH_III_version_number '7.13' + altitude_resolution 'n/a' + area 'Global wave model GS512L4EUK' + base_lat_size 0.029296871 + base_lon_size 0.043945305 + ... + + >>> faces_x = wave_cube.coord("longitude") + >>> faces_y = wave_cube.coord("latitude") + >>> face_size_factor_x = wave_cube.coord("longitude cell size factor") + >>> face_size_factor_y = wave_cube.coord("latitude cell size factor") + >>> base_x_size = wave_cube.attributes["base_lon_size"] + >>> base_y_size = wave_cube.attributes["base_lat_size"] + + # Calculate face corners from face centres and face size factors. + >>> face_centres_x = faces_x.points + >>> face_centres_y = faces_y.points + >>> face_size_x = face_size_factor_x.points * base_x_size + >>> face_size_y = face_size_factor_y.points * base_y_size + + >>> x_mins = (face_centres_x - 0.5 * face_size_x).reshape(-1, 1) + >>> x_maxs = (face_centres_x + 0.5 * face_size_x).reshape(-1, 1) + >>> y_mins = (face_centres_y - 0.5 * face_size_y).reshape(-1, 1) + >>> y_maxs = (face_centres_y + 0.5 * face_size_y).reshape(-1, 1) + + >>> face_corners_x = np.hstack([x_mins, x_maxs, x_maxs, x_mins]) + >>> face_corners_y = np.hstack([y_mins, y_mins, y_maxs, y_maxs]) + + # Add face corners as coordinate bounds. + >>> faces_x.bounds = face_corners_x + >>> faces_y.bounds = face_corners_y + + # Use a Mesh to represent the Cube's horizontal geography, by replacing + # the existing face AuxCoords with new MeshCoords. + >>> smc_mesh = Mesh.from_coords(faces_x, faces_y) + >>> for new_coord in smc_mesh.to_MeshCoords("face"): + ... old_coord = wave_cube.coord(new_coord.name()) + ... unstructured_dim, = old_coord.cube_dims(wave_cube) + ... wave_cube.remove_coord(old_coord) + ... wave_cube.add_aux_coord(new_coord, unstructured_dim) + + >>> print(wave_cube) + sea_surface_wave_significant_height / (m) (time: 7; -- : 666328) + Dimension coordinates: + time x - + Mesh coordinates: + latitude - x + longitude - x + Auxiliary coordinates: + forecast_period x - + latitude cell size factor - x + longitude cell size factor - x + Scalar coordinates: + forecast_reference_time 2021-12-05 00:00:00 + Attributes: + SIN4 namelist parameter BETAMAX 1.39 + SMC_grid_type 'seapoint' + WAVEWATCH_III_switches 'NOGRB SHRD PR2 UNO SMC FLX0 LN1 ST4 NL1 BT1 DB1 TR0 BS0 IC0 IS0 REF0 WNT1... + WAVEWATCH_III_version_number '7.13' + altitude_resolution 'n/a' + area 'Global wave model GS512L4EUK' + base_lat_size 0.029296871 + base_lon_size 0.043945305 + ... + + >>> print(wave_cube.mesh) + Mesh : 'unknown' + topology_dimension: 2 + node + node_dimension: 'Mesh2d_node' + node coordinates + + + face + face_dimension: 'Mesh2d_face' + face_node_connectivity: + face coordinates + + + +.. _WAVEWATCH III: https://github.com/NOAA-EMC/WW3 +.. _FESOM 1.4: https://fesom.de/models/fesom14/ diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 5a0c72f23e..d6a61955b8 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -161,6 +161,9 @@ This document explains the changes made to Iris for this release numpydoc strings and fixed some API documentation rendering. See :ref:`docstrings`. (:issue:`4657`, :pull:`4689`) +# `@trexfeathers`_ added a page with examples of converting various mesh + formats into the Iris Mesh Data Model. (:pull:`4739`) + 💼 Internal =========== From 95f6a3da6834448c2471107b9970c0a3a4f8fffb Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 13 May 2022 14:23:52 +0100 Subject: [PATCH 106/319] Gallery: update NEMO example (#4741) * update nemo example * clearer comment --- .../oceanography/plot_load_nemo.py | 19 +++++++++---------- docs/src/whatsnew/latest.rst | 3 +++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/gallery_code/oceanography/plot_load_nemo.py b/docs/gallery_code/oceanography/plot_load_nemo.py index 4bfee5ac8e..b19f37e1f5 100644 --- a/docs/gallery_code/oceanography/plot_load_nemo.py +++ b/docs/gallery_code/oceanography/plot_load_nemo.py @@ -13,7 +13,7 @@ import iris import iris.plot as iplt import iris.quickplot as qplt -from iris.util import promote_aux_coord_to_dim_coord +from iris.util import equalise_attributes, promote_aux_coord_to_dim_coord def main(): @@ -21,16 +21,15 @@ def main(): fname = iris.sample_data_path("NEMO/nemo_1m_*.nc") cubes = iris.load(fname) - # Some attributes are unique to each file and must be blanked - # to allow concatenation. - differing_attrs = ["file_name", "name", "timeStamp", "TimeStamp"] - for cube in cubes: - for attribute in differing_attrs: - cube.attributes[attribute] = "" - - # The cubes still cannot be concatenated because their time dimension is - # time_counter rather than time. time needs to be promoted to allow + # Some attributes are unique to each file and must be removed to allow # concatenation. + equalise_attributes(cubes) + + # The cubes still cannot be concatenated because their dimension coordinate + # is "time_counter", which has the same value for each cube. concatenate + # needs distinct values in order to create a new DimCoord for the output + # cube. Here, each cube has a "time" auxiliary coordinate, and these do + # have distinct values, so we can promote them to allow concatenation. for cube in cubes: promote_aux_coord_to_dim_coord(cube, "time") diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d6a61955b8..cb809b9fb7 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -164,6 +164,9 @@ This document explains the changes made to Iris for this release # `@trexfeathers`_ added a page with examples of converting various mesh formats into the Iris Mesh Data Model. (:pull:`4739`) +#. `@rcomer`_ updated the "Load a Time Series of Data From the NEMO Model" + gallery example. (:pull:`4741`) + 💼 Internal =========== From 171d493b489ac06042532b52bd64285ee87a6ad2 Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Sun, 15 May 2022 17:26:01 +0100 Subject: [PATCH 107/319] fixed numbered list entry (#4743) --- docs/src/whatsnew/latest.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index cb809b9fb7..5d9c7d6246 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -161,8 +161,8 @@ This document explains the changes made to Iris for this release numpydoc strings and fixed some API documentation rendering. See :ref:`docstrings`. (:issue:`4657`, :pull:`4689`) -# `@trexfeathers`_ added a page with examples of converting various mesh - formats into the Iris Mesh Data Model. (:pull:`4739`) +#. `@trexfeathers`_ added a page with examples of converting various mesh + formats into the Iris Mesh Data Model. (:pull:`4739`) #. `@rcomer`_ updated the "Load a Time Series of Data From the NEMO Model" gallery example. (:pull:`4741`) From 330eb3bdb6425c49bfe5533368d2f5426d289938 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 16 May 2022 09:10:51 +0100 Subject: [PATCH 108/319] Plotting circular data extends args as well as the data (#4649) * Extend any kwarg array * Add test * Add comments and update imagerepo.json * Try using the value as an array, in case it's been given as a list etc. * What's new --- docs/src/whatsnew/latest.rst | 3 +++ lib/iris/plot.py | 20 ++++++++++++++++---- lib/iris/tests/results/imagerepo.json | 3 +++ lib/iris/tests/test_plot.py | 17 +++++++++++++++++ 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 5d9c7d6246..14d5d0c924 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -105,6 +105,9 @@ This document explains the changes made to Iris for this release 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`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 10bd740306..74e5d5788c 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -978,16 +978,28 @@ def _map_common( # is useful in anywhere other than this plotting routine, it may be better # placed in the CS. if getattr(x_coord, "circular", False): + original_length = y.shape[1] _, direction = iris.util.monotonic( x_coord.points, return_direction=True ) y = np.append(y, y[:, 0:1], axis=1) x = np.append(x, x[:, 0:1] + 360 * direction, axis=1) data = ma.concatenate([data, data[:, 0:1]], axis=1) - if "_v_data" in kwargs: - v_data = kwargs["_v_data"] - v_data = ma.concatenate([v_data, v_data[:, 0:1]], axis=1) - kwargs["_v_data"] = v_data + + # Having extended the data, we also need to extend extra kwargs for + # matplotlib (e.g. point colours) + for key, val in kwargs.items(): + try: + val_arr = np.array(val) + except TypeError: + continue + if val_arr.ndim >= 2 and val_arr.shape[1] == original_length: + # Concatenate the first column to the end of the data then + # update kwargs + val_arr = ma.concatenate( + [val_arr, val_arr[:, 0:1, ...]], axis=1 + ) + kwargs[key] = val_arr # Replace non-cartopy subplot/axes with a cartopy alternative and set the # transform keyword. diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index 6a997c38b4..268b15d668 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -411,6 +411,9 @@ "https://scitools.github.io/test-iris-imagehash/images/v4/acf939339a16c64de306318638673c738c19d71cf3866186d8636e69bd191b9e.png", "https://scitools.github.io/test-iris-imagehash/images/v4/edb23c529649c78de38773e538650c729e92279be12de1edc4f246b2139c3b01.png" ], + "iris.tests.test_plot.Test2dPoints.test_circular_changes.0": [ + "https://scitools.github.io/test-iris-imagehash/images/v4/fa81c57a857e93bd9b193e436ec4ccb03b01c14a857e3e34911f3b816e81c57b.png" + ], "iris.tests.test_plot.TestAttributePositive.test_1d_positive_down.0": [ "https://scitools.github.io/test-iris-imagehash/images/v4/87fef8117980c7c160078f1ffc049e7e90159a7a95419a7e910dcf1ece19ce3a.png", "https://scitools.github.io/test-iris-imagehash/images/v4/a7fe781b708487c360079e3bb4789869816bdb64c76b4a3cce7b4e749a6130c5.png" diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py index 2a08635ae0..458616a6fb 100644 --- a/lib/iris/tests/test_plot.py +++ b/lib/iris/tests/test_plot.py @@ -324,6 +324,23 @@ def setUp(self): self.draw_method = qplt.scatter +@tests.skip_data +@tests.skip_plot +class Test2dPoints(tests.GraphicsTest): + def setUp(self): + super().setUp() + pp_file = tests.get_data_path(("PP", "globClim1", "u_wind.pp")) + self.cube = iris.load(pp_file)[0][0] + + def test_circular_changes(self): + # Circular + iplt.pcolormesh(self.cube, vmax=50) + iplt.points(self.cube, s=self.cube.data) + plt.gca().coastlines() + + self.check_graphic() + + @tests.skip_data @tests.skip_plot class TestAttributePositive(tests.GraphicsTest): From df7b03af72a857fbaeba3e94a5d3c4c75fffeb25 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 16 May 2022 09:25:11 +0100 Subject: [PATCH 109/319] Datum performance improvements (#4731) * Set stacklevel to get more useful FutureWarning * Remove spare printing * Only generate crs and globe on demand, handle attr setting * Updates docstrings to make it clearer what's going on * Additional tests (and fixes found from testing) * Warn that inverse_flattening setting doesn't help --- lib/iris/coord_systems.py | 244 +++++++++++++++--- .../fileformats/_nc_load_rules/helpers.py | 2 +- lib/iris/tests/integration/test_netcdf.py | 3 - lib/iris/tests/test_coordsystem.py | 138 ++++++++++ .../nc_load_rules/actions/__init__.py | 1 - 5 files changed, 345 insertions(+), 43 deletions(-) diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 1f755998b1..9544585535 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -9,6 +9,7 @@ """ from abc import ABCMeta, abstractmethod +from functools import cached_property import warnings import cartopy.crs as ccrs @@ -53,10 +54,28 @@ class CoordSystem(metaclass=ABCMeta): grid_mapping_name = None def __eq__(self, other): - return ( - self.__class__ == other.__class__ - and self.__dict__ == other.__dict__ - ) + """ + Override equality + + The `_globe` and `_crs` attributes are not compared because they are + cached properties and completely derived from other attributes. The + nature of caching means that they can appear on one object and not on + another despite the objects being identical, and them being completely + derived from other attributes means they will only differ if other + attributes that are being tested for equality differ. + """ + if self.__class__ != other.__class__: + return False + self_keys = set(self.__dict__.keys()) + other_keys = set(other.__dict__.keys()) + check_keys = (self_keys | other_keys) - {"_globe", "_crs"} + for key in check_keys: + try: + if self.__dict__[key] != other.__dict__[key]: + return False + except KeyError: + return False + return True def __ne__(self, other): # Must supply __ne__, Python does not defer to __eq__ for @@ -132,7 +151,6 @@ class GeogCS(CoordSystem): """ A geographic (ellipsoidal) coordinate system, defined by the shape of the Earth and a prime meridian. - """ grid_mapping_name = "latitude_longitude" @@ -156,8 +174,6 @@ def __init__( Can be omitted if both axes given (see note below). Default 0.0 * longitude_of_prime_meridian: Specifies the prime meridian on the ellipsoid, in degrees. Default 0.0 - * datum: - Name of the datum used to modify the ellipsoid. Default None Notes ----- @@ -172,6 +188,10 @@ def __init__( Currently, Iris will not allow over-specification (all three ellipsoid parameters). + After object creation, altering any of these properties will not update + the others. semi_major_axis and semi_minor_axis are used when creating + Cartopy objects. + Examples:: cs = GeogCS(6371229) @@ -199,34 +219,60 @@ def __init__( ): raise ValueError("Ellipsoid is overspecified") - # We didn't get enough to specify an ellipse. - if semi_major_axis is None and ( - semi_minor_axis is None or inverse_flattening is None + # Perfect sphere (semi_major_axis only)? (1 0 0) + elif semi_major_axis is not None and ( + semi_minor_axis is None and not inverse_flattening ): - raise ValueError("Insufficient ellipsoid specification") + semi_minor_axis = semi_major_axis + inverse_flattening = 0.0 - # Making a globe needs a semi_major_axis and a semi_minor_axis - if semi_major_axis is None: + # Calculate semi_major_axis? (0 1 1) + elif semi_major_axis is None and ( + semi_minor_axis is not None and inverse_flattening is not None + ): semi_major_axis = -semi_minor_axis / ( (1.0 - inverse_flattening) / inverse_flattening ) - if semi_minor_axis is None and inverse_flattening: + + # Calculate semi_minor_axis? (1 0 1) + elif semi_minor_axis is None and ( + semi_major_axis is not None and inverse_flattening is not None + ): semi_minor_axis = semi_major_axis - ( (1.0 / inverse_flattening) * semi_major_axis ) + # Calculate inverse_flattening? (1 1 0) + elif inverse_flattening is None and ( + semi_major_axis is not None and semi_minor_axis is not None + ): + if semi_major_axis == semi_minor_axis: + inverse_flattening = 0.0 + else: + inverse_flattening = 1.0 / ( + (semi_major_axis - semi_minor_axis) / semi_major_axis + ) + + # We didn't get enough to specify an ellipse. + else: + raise ValueError("Insufficient ellipsoid specification") + + #: Major radius of the ellipsoid in metres. + self._semi_major_axis = float(semi_major_axis) + + #: Minor radius of the ellipsoid in metres. + self._semi_minor_axis = float(semi_minor_axis) + + #: :math:`1/f` where :math:`f = (a-b)/a`. + self._inverse_flattening = float(inverse_flattening) + + self._datum = None + #: Describes 'zero' on the ellipsoid in degrees. self.longitude_of_prime_meridian = _arg_default( longitude_of_prime_meridian, 0 ) - globe = ccrs.Globe( - ellipse=None, - semimajor_axis=semi_major_axis, - semiminor_axis=semi_minor_axis, - ) - self._crs = ccrs.Geodetic(globe) - def _pretty_attrs(self): attrs = [("semi_major_axis", self.semi_major_axis)] if self.semi_major_axis != self.semi_minor_axis: @@ -292,37 +338,159 @@ def as_cartopy_projection(self): ) def as_cartopy_globe(self): - return self._crs.globe + return self._globe - def __getattr__(self, name): - if name == "semi_major_axis": + @cached_property + def _globe(self): + """ + A representation of this CRS as a Cartopy Globe. + + Note + ---- + This property is created when required and then cached for speed. That + cached value is cleared when an assignment is made to a property of the + class that invalidates the cache. + """ + if self._datum is not None: + short_datum = _short_datum_names.get(self._datum, self._datum) + # Cartopy doesn't actually enact datums unless they're provided without + # ellipsoid axes, so only provide the datum + return ccrs.Globe(short_datum, ellipse=None) + else: + return ccrs.Globe( + ellipse=None, + semimajor_axis=self._semi_major_axis, + semiminor_axis=self._semi_minor_axis, + ) + + @cached_property + def _crs(self): + """ + A representation of this CRS as a Cartopy CRS. + + Note + ---- + This property is created when required and then cached for speed. That + cached value is cleared when an assignment is made to a property of the + class that invalidates the cache. + """ + return ccrs.Geodetic(self._globe) + + def _wipe_cached_properties(self): + """ + Wipes the cached properties on the object as part of any update to a + value that invalidates the cache. + """ + try: + delattr(self, "_crs") + except AttributeError: + pass + try: + delattr(self, "_globe") + except AttributeError: + pass + + @property + def semi_major_axis(self): + if self._semi_major_axis is not None: + return self._semi_major_axis + else: return self._crs.ellipsoid.semi_major_metre - if name == "semi_minor_axis": + + @semi_major_axis.setter + def semi_major_axis(self, value): + """ + Setting this property to a different value invalidates the current datum + (if any) because a datum encodes a specific semi-major axis. This also + invalidates the cached `cartopy.Globe` and `cartopy.CRS`. + """ + value = float(value) + if not np.isclose(self.semi_major_axis, value): + self._datum = None + self._wipe_cached_properties() + self._semi_major_axis = value + + @property + def semi_minor_axis(self): + if self._semi_minor_axis is not None: + return self._semi_minor_axis + else: return self._crs.ellipsoid.semi_minor_metre - if name == "inverse_flattening": - return self._crs.ellipsoid.inverse_flattening - if name == "datum": - datum = self._crs.datum.name - # An unknown crs datum will be treated as None - if datum == "unknown": - return None + + @semi_minor_axis.setter + def semi_minor_axis(self, value): + """ + Setting this property to a different value invalidates the current datum + (if any) because a datum encodes a specific semi-minor axis. This also + invalidates the cached `cartopy.Globe` and `cartopy.CRS`. + """ + value = float(value) + if not np.isclose(self.semi_minor_axis, value): + self._datum = None + self._wipe_cached_properties() + self._semi_minor_axis = value + + @property + def inverse_flattening(self): + if self._inverse_flattening is not None: + return self._inverse_flattening + else: + self._crs.ellipsoid.inverse_flattening + + @inverse_flattening.setter + def inverse_flattening(self, value): + """ + Setting this property to a different value does not affect the behaviour + of this object any further than the value of this property. + """ + wmsg = ( + "Setting inverse_flattening does not affect other properties of " + "the GeogCS object. To change other properties set them explicitly" + " or create a new GeogCS instance." + ) + warnings.warn(wmsg, UserWarning) + value = float(value) + self._inverse_flattening = value + + @property + def datum(self): + if self._datum is None: + return None + else: + datum = self._datum return datum - return getattr(super(), name) + + @datum.setter + def datum(self, value): + """ + Setting this property to a different value invalidates the current + values of the ellipsoid measurements because a datum encodes its own + ellipse. This also invalidates the cached `cartopy.Globe` and + `cartopy.CRS`. + """ + if self._datum != value: + self._semi_major_axis = None + self._semi_minor_axis = None + self._inverse_flattening = None + self._wipe_cached_properties() + self._datum = value @classmethod def from_datum(cls, datum, longitude_of_prime_meridian=None): - short_datum = _short_datum_names.get(datum, datum) - - # Cartopy doesn't actually enact datums unless they're provided without - # ellipsoid axes, so only provide the datum crs = super().__new__(cls) - crs._crs = ccrs.Geodetic(ccrs.Globe(short_datum, ellipse=None)) + + crs._semi_major_axis = None + crs._semi_minor_axis = None + crs._inverse_flattening = None #: Describes 'zero' on the ellipsoid in degrees. crs.longitude_of_prime_meridian = _arg_default( longitude_of_prime_meridian, 0 ) + + crs._datum = datum + return crs diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 65ab3d8b5b..34eecdd310 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -275,7 +275,7 @@ def _get_ellipsoid(cf_grid_var): "applied. To apply the datum when loading, use the " "iris.FUTURE.datum_support flag." ) - warnings.warn(wmsg, FutureWarning) + warnings.warn(wmsg, FutureWarning, stacklevel=14) datum = None if datum is not None: diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index 8c913a1043..14f1ee59e1 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -661,9 +661,6 @@ def test_save_datum(self): iris.save(test_cube, filename) with iris.FUTURE.context(datum_support=True): cube = iris.load_cube(filename) - print(cube) - for coord in cube.coords(): - print(coord) test_crs = cube.coord("projection_y_coordinate").coord_system actual = str(test_crs.as_cartopy_crs().datum) diff --git a/lib/iris/tests/test_coordsystem.py b/lib/iris/tests/test_coordsystem.py index 046d76b15a..e4c776a063 100644 --- a/lib/iris/tests/test_coordsystem.py +++ b/lib/iris/tests/test_coordsystem.py @@ -224,6 +224,144 @@ def test_as_cartopy_crs(self): self.assertEqual(res, expected) +class Test_GeogCS_equality(tests.IrisTest): + """Test cached values don't break GeogCS equality""" + + def test_as_cartopy_globe(self): + cs_const = GeogCS(6543210, 6500000) + cs_mut = GeogCS(6543210, 6500000) + initial_globe = cs_mut.as_cartopy_globe() + new_globe = cs_mut.as_cartopy_globe() + + self.assertIs(new_globe, initial_globe) + self.assertEqual(cs_const, cs_mut) + + def test_as_cartopy_projection(self): + cs_const = GeogCS(6543210, 6500000) + cs_mut = GeogCS(6543210, 6500000) + initial_projection = cs_mut.as_cartopy_projection() + initial_globe = initial_projection.globe + new_projection = cs_mut.as_cartopy_projection() + new_globe = new_projection.globe + + self.assertIs(new_globe, initial_globe) + self.assertEqual(cs_const, cs_mut) + + def test_as_cartopy_crs(self): + cs_const = GeogCS(6543210, 6500000) + cs_mut = GeogCS(6543210, 6500000) + initial_crs = cs_mut.as_cartopy_crs() + initial_globe = initial_crs.globe + new_crs = cs_mut.as_cartopy_crs() + new_globe = new_crs.globe + + self.assertIs(new_crs, initial_crs) + self.assertIs(new_globe, initial_globe) + self.assertEqual(cs_const, cs_mut) + + def test_update_to_equivalent(self): + cs_const = GeogCS(6500000, 6000000) + # Cause caching + _ = cs_const.as_cartopy_crs() + + cs_mut = GeogCS(6543210, 6000000) + # Cause caching + _ = cs_mut.as_cartopy_crs() + # Set value + cs_mut.semi_major_axis = 6500000 + cs_mut.inverse_flattening = 13 + + self.assertEqual(cs_const.semi_major_axis, 6500000) + self.assertEqual(cs_mut.semi_major_axis, 6500000) + self.assertEqual(cs_const, cs_mut) + + +class Test_GeogCS_mutation(tests.IrisTest): + "Test that altering attributes of a GeogCS instance behaves as expected" + + def test_semi_major_axis_change(self): + # Clear datum + # Clear caches + cs = GeogCS.from_datum("OSGB 1936") + _ = cs.as_cartopy_crs() + self.assertEqual(cs.datum, "OSGB 1936") + cs.semi_major_axis = 6000000 + self.assertIsNone(cs.datum) + self.assertEqual(cs.as_cartopy_globe().semimajor_axis, 6000000) + + def test_semi_major_axis_no_change(self): + # Datum untouched + # Caches untouched + cs = GeogCS.from_datum("OSGB 1936") + initial_crs = cs.as_cartopy_crs() + self.assertEqual(cs.datum, "OSGB 1936") + cs.semi_major_axis = 6377563.396 + self.assertEqual(cs.datum, "OSGB 1936") + new_crs = cs.as_cartopy_crs() + self.assertIs(new_crs, initial_crs) + + def test_semi_minor_axis_change(self): + # Clear datum + # Clear caches + cs = GeogCS.from_datum("OSGB 1936") + _ = cs.as_cartopy_crs() + self.assertEqual(cs.datum, "OSGB 1936") + cs.semi_minor_axis = 6000000 + self.assertIsNone(cs.datum) + self.assertEqual(cs.as_cartopy_globe().semiminor_axis, 6000000) + + def test_semi_minor_axis_no_change(self): + # Datum untouched + # Caches untouched + cs = GeogCS.from_datum("OSGB 1936") + initial_crs = cs.as_cartopy_crs() + self.assertEqual(cs.datum, "OSGB 1936") + cs.semi_minor_axis = 6356256.909237285 + self.assertEqual(cs.datum, "OSGB 1936") + new_crs = cs.as_cartopy_crs() + self.assertIs(new_crs, initial_crs) + + def test_datum_change(self): + # Semi-major axis changes + # All internal ellipoid values set to None + # CRS changes + cs = GeogCS(6543210, 6500000) + _ = cs.as_cartopy_crs() + self.assertTrue("_globe" in cs.__dict__) + self.assertTrue("_crs" in cs.__dict__) + self.assertEqual(cs.semi_major_axis, 6543210) + cs.datum = "OSGB 1936" + self.assertEqual(cs.as_cartopy_crs().datum, "OSGB 1936") + self.assertIsNone(cs.__dict__["_semi_major_axis"]) + self.assertIsNone(cs.__dict__["_semi_minor_axis"]) + self.assertIsNone(cs.__dict__["_inverse_flattening"]) + self.assertEqual(cs.semi_major_axis, 6377563.396) + + def test_datum_no_change(self): + # Caches untouched + cs = GeogCS.from_datum("OSGB 1936") + initial_crs = cs.as_cartopy_crs() + cs.datum = "OSGB 1936" + new_crs = cs.as_cartopy_crs() + self.assertIs(new_crs, initial_crs) + + def test_inverse_flattening_change(self): + # Caches untouched + # Axes unchanged (this behaviour is odd, but matches existing behaviour) + # Warning about lack of effect on other aspects + cs = GeogCS(6543210, 6500000) + initial_crs = cs.as_cartopy_crs() + with self.assertWarnsRegex( + UserWarning, + "Setting inverse_flattening does not affect other properties of the GeogCS object.", + ): + cs.inverse_flattening = cs.inverse_flattening + 1 + new_crs = cs.as_cartopy_crs() + self.assertIs(new_crs, initial_crs) + self.assertEqual(cs.semi_major_axis, 6543210) + self.assertEqual(cs.semi_minor_axis, 6500000) + + class Test_RotatedGeogCS_construction(tests.IrisTest): def test_init(self): rcs = RotatedGeogCS( diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py index f79aa494f3..2f627f0a2c 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py @@ -104,7 +104,6 @@ def load_cube_from_cdl(self, cdl_string, cdl_path, nc_path): "applied. To apply the datum when loading, use the " "iris.FUTURE.datum_support flag.", category=FutureWarning, - module="iris.fileformats._nc_load_rules.helpers", ) # Call the main translation function to load a single cube. # _load_cube establishes per-cube facts, activates rules and From ba07f7b3dec18e44bf461a8a73b69016148b4857 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 09:59:45 +0100 Subject: [PATCH 110/319] Updated environment lockfiles (#4742) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 66 ++++++++++++--------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index be82d62ffe..f24d6d9bc2 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -9,29 +9,33 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed3 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-11.2.0-h5c6108e_16.tar.bz2#ff034874d96195a5c5be34200689b5b7 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-11.2.0-he4da1e4_16.tar.bz2#8cfd1cd3273ff187be91b868ddf9a636 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-11.2.0-h69a702a_16.tar.bz2#27974aad841c189854df09426b1b9fac -https://conda.anaconda.org/conda-forge/linux-64/libgomp-11.2.0-h1d223b6_16.tar.bz2#e935fb0c92c6ffb63c736a2012604d72 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-11.2.0-h1d223b6_16.tar.bz2#71feb63a30085cbce51847d5ef1f769d +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.3.2-h166bdaf_0.tar.bz2#b7607b7b62dce55c194ad84f99464e5f +https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#fe9a66a351bfa7a84c3108304c7bcba5 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 -https://conda.anaconda.org/conda-forge/linux-64/icu-69.1-h9c3ff4c_0.tar.bz2#e0773c9556d588b062a4e1424a6a02fa +https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 +https://conda.anaconda.org/conda-forge/linux-64/json-c-0.15-h98cffda_0.tar.bz2#f32d45a88e7462be446824654dbcf4a4 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c +https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.10-h7f98852_0.tar.bz2#ffa3a757a97e851293909b49f49f28fb https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 @@ -67,9 +71,10 @@ https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.t https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-14_linux64_openblas.tar.bz2#fb31fbbde682414550bbe15e3964420f https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.51-h166bdaf_1.tar.bz2#875e5931fb2faa6dacb4c13672630ae3 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 -https://conda.anaconda.org/conda-forge/linux-64/libllvm13-13.0.1-hf817b99_2.tar.bz2#47da3ce0d8b2e65ccb226c186dd91eba +https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.3-he0ac6c6_0.tar.bz2#8ab9f9c0fc641b53c5f87368e41af18d https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_0.tar.bz2#69c2d220ca1b41a881928a91a6cbbd93 @@ -83,14 +88,15 @@ https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar. https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-14_linux64_openblas.tar.bz2#1b41ea4c32014d878e84de4e5690df7a -https://conda.anaconda.org/conda-forge/linux-64/libclang-13.0.1-default_hc23dcda_0.tar.bz2#8cebb0736cba83485b13dc10d242d96d +https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.3-default_h3a83d3e_0.tar.bz2#1a571eb098b894a66642cd8b638f5f1d +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-14_linux64_openblas.tar.bz2#13367ebd0243a949cee7564b13c3cd42 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.12-h885dcf4_1.tar.bz2#d1355eaa48f465782f228275a0a69771 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_0.tar.bz2#3314d5f8d6c831514363625cb28c8ff1 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.5-h4ff8645_0.tar.bz2#a1448f0c31baec3946d2dcf09f905c9e @@ -102,9 +108,11 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.b https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.2-hd4edc92_0.tar.bz2#5608a9802071373781ee401786fa4846 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.0-h7bff187_0.tar.bz2#e2d939fa77fe69cd50f751961f17786a -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.2-hd57d9b9_0.tar.bz2#91b38e297e1cc79f88f7cbf7bdb248e0 +https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.3-default_h2e3cab8_0.tar.bz2#2343b7c11c031c5415b28641eb81275d +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.3-hd77ab85_0.tar.bz2#13980beea7c55e6b68db1349f25bd454 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/nss-3.77-h2350873_0.tar.bz2#260617b7829b86e9e939b01c9cad1526 @@ -117,11 +125,11 @@ https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#eb https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f -https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.0-h7bff187_0.tar.bz2#81e39fb3ae82be7e8d2dd7046f393588 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.6.0-pyhd8ed1ab_0.tar.bz2#6e03ca6c7b47a4152a2b12c6eee3bd32 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.0-pyhd8ed1ab_0.tar.bz2#cfb94034fcdfc80b61164878dc677a8d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.3.0-pyhd8ed1ab_0.tar.bz2#960b78685ccbedb992886fc4ce37bf6e https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.2-hcf0ee16_0.tar.bz2#79d7fca692d224dc29a72bda90f78a7b @@ -130,6 +138,7 @@ https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b5 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-hfd4fe87_1001.tar.bz2#a44a1d690250f6bbb01b7e328ea0c719 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 @@ -137,7 +146,7 @@ https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.0-h93bde94_1.tar.bz2#cf908994f24ea526afc59f295d5b07c1 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff -https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.8-pyhd8ed1ab_0.tar.bz2#7f5738c49fdccd0fc755bfd25a5ea66c +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 @@ -158,30 +167,29 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#05 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.10.1-pyhd8ed1ab_0.tar.bz2#2ec70a4a964b696170d730466c668f60 https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha12eb4b_1010.tar.bz2#e15c0969bf37df9dae513a48ac871a7d +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.15.2-py38h578d9bd_3.tar.bz2#e3165d234a00a89a7ce018283963f3f3 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_1.tar.bz2#21862e22238907d485e460f9c2e9ae4d https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_1.tar.bz2#34c284cb94bd8c5118ccfe6d6fc3dd3f -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h283352f_2.tar.bz2#2b0d39005a2e8347f329fe578bd6488a +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h99721a1_2.tar.bz2#e6ed43e96f813b184fe9bb677476e56d https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.2-py38h9776b28_0.tar.bz2#bd527d652ba06fb2aae61640bcf7c435 +https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.0-py38h0a891b7_1.tar.bz2#92045570d1da14b3f90621c54f8afb12 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-4.19.18-py38h709712a_8.tar.bz2#11b72f5b1cc15427c89232321172a0bc +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-hb166930_4.tar.bz2#7c165f1b39385b9fee1f279005ee512e https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/qt-5.12.9-h1304e3e_6.tar.bz2#f2985d160b8c43dd427923c04cd732fe -https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.1.0-py38h578d9bd_0.tar.bz2#201ea562e8f45f05a66f715f18f1b728 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.2.0-py38h578d9bd_0.tar.bz2#5efbc16cda18259c2ce412d181ba0d9f https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_0.tar.bz2#41427ff3fd8d35e5ab1cdcec4d94ea6b @@ -190,21 +198,22 @@ https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar. https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.5.0-pyhd8ed1ab_0.tar.bz2#3aef8ad6f9af56117e959a53cb9b9fd1 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.0-h40b6f09_0.tar.bz2#017b20e7e98860f0bfa7492ce16390a7 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.1-hf9f4e7c_0.tar.bz2#842ac05681ac1ed099fad99bb717c641 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_0.tar.bz2#2d307d13155817b5f5da36cc69358fe6 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_1.tar.bz2#0cdb1150994e0c449b25d6f92b69eda2 -https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff +https://conda.anaconda.org/conda-forge/noarch/pip-22.1-pyhd8ed1ab_0.tar.bz2#bc23e31a667caa608150cbd34b4e4796 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee -https://conda.anaconda.org/conda-forge/linux-64/pyqt-impl-5.12.3-py38h0ffb2e6_8.tar.bz2#acfc7625a212c27f7decdca86fdb2aba https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.3-hf97cb25_1.tar.bz2#79853477ea006ccccb7a39c2d33f51b9 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h97f7145_1.tar.bz2#e0ada9ddff3a730b7dfc976a5ee3f420 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py38h709712a_2.tar.bz2#8ff0cdb63842c29388a34a6af7b5ce6f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 @@ -215,16 +224,15 @@ https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bf https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 -https://conda.anaconda.org/conda-forge/linux-64/pyqtchart-5.12-py38h7400c14_8.tar.bz2#78a2a6cb4ef31f997c1bee8223a9e579 -https://conda.anaconda.org/conda-forge/linux-64/pyqtwebengine-5.12.1-py38h7400c14_8.tar.bz2#857894ea9c5e53c962c3a0932efa71ea +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py38hfa26641_0.tar.bz2#d69f44759f54d1b312d097cd6cf80588 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.52.5-h0a9e6e8_3.tar.bz2#a08562889b985d021550e22443cf0fce +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.1-h7abd40a_0.tar.bz2#be92fad99cf5775c70f710057951275e https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.12.3-py38h578d9bd_8.tar.bz2#88368a5889f31dff922a2d57bbfc3f5b +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py38hfa26641_0.tar.bz2#f151689583a3c94fd4bcf33cdbdd8fec https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 From caae45075795c1e13fbb0f04488c3b99f15b60e8 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Thu, 19 May 2022 12:34:16 +0100 Subject: [PATCH 111/319] Extra datum translation (#4753) --- lib/iris/coord_systems.py | 1 + lib/iris/tests/integration/test_netcdf.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 9544585535..0bd7145a5f 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -143,6 +143,7 @@ def as_cartopy_projection(self): _short_datum_names = { "OSGB 1936": "OSGB36", + "OSGB_1936": "OSGB36", "WGS 84": "WGS84", } diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index 14f1ee59e1..4989ef9b6c 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -628,7 +628,6 @@ def test_no_load_datum_cf_var(self): def test_save_datum(self): expected = "OSGB 1936" - # saved_crs = iris.coord_systems.GeogCS.from_datum(datum="OSGB36") saved_crs = iris.coord_systems.Mercator( ellipsoid=iris.coord_systems.GeogCS.from_datum("OSGB36") ) From 508aa2b35ff63b0892801332f90c2e71fb5e7b5a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 23 May 2022 10:28:50 +0100 Subject: [PATCH 112/319] Updated environment lockfiles (#4760) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 28 ++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index f24d6d9bc2..13d48983fc 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -3,7 +3,7 @@ # input_hash: 41315fe97a24272298c496dfe62676b5ef3ce15bd4750681498f42fd4e9ea036 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2021.10.8-ha878542_0.tar.bz2#575611b8a84f45960e87722eeb51fa26 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.5.18.1-ha878542_0.tar.bz2#352e93bbe1d604002b11bbcf425bf866 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -77,13 +77,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.3-he0ac6c6_0.tar.bz2#8ab9f9c0fc641b53c5f87368e41af18d https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_0.tar.bz2#69c2d220ca1b41a881928a91a6cbbd93 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2#5788de3c8d7a7d64ac56c784c4ef48e6 https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h166bdaf_1014.tar.bz2#def3b82d1a03aa695bb38ac1dd072ff2 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-ha95c52a_0.tar.bz2#5222b231b1ef49a7f60d40b363469b70 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_1.tar.bz2#3db63b53bb194dbaa7dc3d8833e98da2 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 @@ -98,7 +98,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_0.tar.bz2#3314d5f8d6c831514363625cb28c8ff1 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.5-h4ff8645_0.tar.bz2#a1448f0c31baec3946d2dcf09f905c9e https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 @@ -106,7 +106,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.2-hd4edc92_0.tar.bz2#5608a9802071373781ee401786fa4846 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.2-hd4edc92_1.tar.bz2#c16a9b2773180a641583f1d3690e3ff6 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.3-default_h2e3cab8_0.tar.bz2#2343b7c11c031c5415b28641eb81275d https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 @@ -123,7 +123,7 @@ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489 https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.0.0-pyhd8ed1ab_0.tar.bz2#3a8fc8b627d5fb6af827e126a10a86c6 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.4-pyh9f0ad1d_0.tar.bz2#c08b4c1326b880ed44f3ffb04803332f https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb @@ -131,7 +131,7 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.0-pyhd8ed1ab_0.tar.bz2#cfb94034fcdfc80b61164878dc677a8d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.3.0-pyhd8ed1ab_0.tar.bz2#960b78685ccbedb992886fc4ce37bf6e +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.2-hcf0ee16_0.tar.bz2#79d7fca692d224dc29a72bda90f78a7b https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd @@ -168,7 +168,7 @@ https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h https://conda.anaconda.org/conda-forge/noarch/babel-2.10.1-pyhd8ed1ab_0.tar.bz2#2ec70a4a964b696170d730466c668f60 https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 -https://conda.anaconda.org/conda-forge/linux-64/certifi-2021.10.8-py38h578d9bd_2.tar.bz2#63a01bce71bc3e8c8e0510ed997d1458 +https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.5.18.1-py38h578d9bd_0.tar.bz2#429a49d95358a078211aad97c4fc286c https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a https://conda.anaconda.org/conda-forge/linux-64/docutils-0.15.2-py38h578d9bd_3.tar.bz2#e3165d234a00a89a7ce018283963f3f3 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_1.tar.bz2#21862e22238907d485e460f9c2e9ae4d @@ -189,13 +189,13 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.2.0-py38h578d9bd_0.tar.bz2#5efbc16cda18259c2ce412d181ba0d9f +https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.3.2-py38h578d9bd_0.tar.bz2#8b04fc38afd584d2531e630e1a59abd9 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_0.tar.bz2#41427ff3fd8d35e5ab1cdcec4d94ea6b https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e -https://conda.anaconda.org/conda-forge/linux-64/cryptography-36.0.2-py38h2b5fc30_1.tar.bz2#1541e6e63753f197165277eac0d434a1 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30_0.tar.bz2#bcc387154aae535f8b4f84822621b5f7 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.5.0-pyhd8ed1ab_0.tar.bz2#3aef8ad6f9af56117e959a53cb9b9fd1 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.1-hf9f4e7c_0.tar.bz2#842ac05681ac1ed099fad99bb717c641 @@ -204,21 +204,21 @@ https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007. https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_1.tar.bz2#0cdb1150994e0c449b25d6f92b69eda2 -https://conda.anaconda.org/conda-forge/noarch/pip-22.1-pyhd8ed1ab_0.tar.bz2#bc23e31a667caa608150cbd34b4e4796 +https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.3-hf97cb25_1.tar.bz2#79853477ea006ccccb7a39c2d33f51b9 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.0-py38h56a6a73_1.tar.bz2#86073932d9e675c5929376f6f8b79b97 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38h1ee437e_0.tar.bz2#a0a8bc19d491ec659a534c9a11cf74a0 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h97f7145_1.tar.bz2#e0ada9ddff3a730b7dfc976a5ee3f420 https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py38h709712a_2.tar.bz2#8ff0cdb63842c29388a34a6af7b5ce6f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.0-pyhd8ed1ab_0.tar.bz2#030048290bb28d917f82830d6e01d111 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_0.tar.bz2#107af20136422bcabf9f1195f6262117 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 @@ -229,7 +229,7 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.t https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.1-h7abd40a_0.tar.bz2#be92fad99cf5775c70f710057951275e +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py38hfa26641_0.tar.bz2#f151689583a3c94fd4bcf33cdbdd8fec From 6b1d39287ceec045216c48952334a7c36620bbb6 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 23 May 2022 16:07:31 +0100 Subject: [PATCH 113/319] Permit fast percentile with mdtol=0 (#4755) * Permit fast percentile with mdtol=0 * What's new * Check arrays like they're masked, and make the expected arrays masked * Bigger arrays to avoid np quirk, bug fix, check masking in 2d_multi * Suppress numpy masked percentile warning * Use a larger test array that numpy handles how it handles most of them (the worse case) * Broadcast the mask in the non-lazy case with added dim * Bug fix on ndim test * Review fixes --- docs/src/whatsnew/latest.rst | 7 +++ lib/iris/analysis/__init__.py | 62 +++++++++++++------ .../tests/unit/analysis/test_PERCENTILE.py | 50 ++++++++++++--- 3 files changed, 93 insertions(+), 26 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 14d5d0c924..1f9d4d35b4 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -108,6 +108,9 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ fixed plotting of circular coordinates to extend kwarg arrays as well as the data. (:issue:`466`, :pull:`4649`) +#. `@wjbenfold`_ corrected the axis on which masking is applied when an + aggregator adds a trailing dimension. (:pull:`4755`) + 💣 Incompatible Changes ======================= @@ -121,6 +124,7 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ added caching to the calculation of the points array in a :class:`~iris.coords.DimCoord` created using :meth:`~iris.coords.DimCoord.from_regular`. (:pull:`4698`) + #. `@wjbenfold`_ introduced caching in :func:`_lazy_data._optimum_chunksize` and :func:`iris.fileformats.pp_load_rules._epoch_date_hours` to reduce time spent repeating calculations. (:pull:`4716`) @@ -128,6 +132,9 @@ This document explains the changes made to Iris for this release #. `@pp-mo`_ made :meth:`~iris.cube.Cube.add_aux_factory` faster. (:pull:`4718`) +#. `@wjbenfold`_ permitted the fast percentile aggregation method to be used on + masked data when the missing data tolerance is set to 0. (:issue:`4735`, + :pull:`4755`) 🔥 Deprecations diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 60994fb6c2..6d2ee599c8 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -39,6 +39,7 @@ from collections.abc import Iterable import functools from functools import wraps +import warnings import dask.array as da import numpy as np @@ -591,7 +592,13 @@ def aggregate(self, data, axis, **kwargs): and result is not ma.masked ): fraction_not_missing = data.count(axis=axis) / data.shape[axis] - mask_update = 1 - mdtol > fraction_not_missing + mask_update = np.array(1 - mdtol > fraction_not_missing) + if np.array(result).ndim > mask_update.ndim: + # call_func created trailing dimension. + mask_update = np.broadcast_to( + mask_update.reshape(mask_update.shape + (1,)), + np.array(result).shape, + ) if ma.isMaskedArray(result): result.mask = result.mask | mask_update else: @@ -720,6 +727,25 @@ def __init__(self, units_func=None, **kwargs): **kwargs, ) + def _base_aggregate(self, data, axis, lazy, **kwargs): + """ + Method to avoid duplication of checks in aggregate and lazy_aggregate. + """ + msg = "{} aggregator requires the mandatory keyword argument {!r}." + for arg in self._args: + if arg not in kwargs: + raise ValueError(msg.format(self.name(), arg)) + + if kwargs.get("fast_percentile_method", False) and ( + kwargs.get("mdtol", 1) != 0 + ): + kwargs["error_on_masked"] = True + + if lazy: + return _Aggregator.lazy_aggregate(self, data, axis, **kwargs) + else: + return _Aggregator.aggregate(self, data, axis, **kwargs) + def aggregate(self, data, axis, **kwargs): """ Perform the percentile aggregation over the given data. @@ -755,12 +781,7 @@ def aggregate(self, data, axis, **kwargs): """ - msg = "{} aggregator requires the mandatory keyword argument {!r}." - for arg in self._args: - if arg not in kwargs: - raise ValueError(msg.format(self.name(), arg)) - - return _Aggregator.aggregate(self, data, axis, **kwargs) + return self._base_aggregate(data, axis, lazy=False, **kwargs) def lazy_aggregate(self, data, axis, **kwargs): """ @@ -794,12 +815,7 @@ def lazy_aggregate(self, data, axis, **kwargs): """ - msg = "{} aggregator requires the mandatory keyword argument {!r}." - for arg in self._args: - if arg not in kwargs: - raise ValueError(msg.format(self.name(), arg)) - - return _Aggregator.lazy_aggregate(self, data, axis, **kwargs) + return self._base_aggregate(data, axis, lazy=True, **kwargs) def post_process(self, collapsed_cube, data_result, coords, **kwargs): """ @@ -1281,10 +1297,19 @@ def _calc_percentile(data, percent, fast_percentile_method=False, **kwargs): """ if fast_percentile_method: - msg = "Cannot use fast np.percentile method with masked array." - if ma.is_masked(data): - raise TypeError(msg) - result = np.percentile(data, percent, axis=-1) + if kwargs.pop("error_on_masked", False): + msg = ( + "Cannot use fast np.percentile method with masked array unless" + " mdtol is 0." + ) + if ma.is_masked(data): + raise TypeError(msg) + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Warning: 'partition' will ignore the 'mask' of the MaskedArray.", + ) + result = np.percentile(data, percent, axis=-1) result = result.T else: quantiles = percent / 100.0 @@ -1965,7 +1990,8 @@ def interp_order(length): * fast_percentile_method (boolean): When set to True, uses :func:`numpy.percentile` method as a faster alternative to the :func:`scipy.stats.mstats.mquantiles` method. alphap and - betap are ignored. An exception is raised if the data are masked. + betap are ignored. An exception is raised if the data are masked and the + missing data tolerance is not 0. Defaults to False. **For example**: diff --git a/lib/iris/tests/unit/analysis/test_PERCENTILE.py b/lib/iris/tests/unit/analysis/test_PERCENTILE.py index d56d1ffb1f..a74dd891ba 100644 --- a/lib/iris/tests/unit/analysis/test_PERCENTILE.py +++ b/lib/iris/tests/unit/analysis/test_PERCENTILE.py @@ -32,7 +32,7 @@ def check_percentile_calc( if self.lazy: data = as_lazy_data(data) - expected = np.array(expected) + expected = ma.array(expected) actual = self.agg_method( data, @@ -52,9 +52,9 @@ def check_percentile_calc( self.assertFalse(is_lazy) if approx: - self.assertArrayAlmostEqual(actual, expected) + self.assertMaskedArrayAlmostEqual(actual, expected) else: - self.assertArrayEqual(actual, expected) + self.assertMaskedArrayEqual(actual, expected) def test_1d_single(self): data = np.arange(11) @@ -131,7 +131,7 @@ def test_masked_2d_single(self): def test_masked_2d_multi(self): shape = (3, 10) data = ma.arange(np.prod(shape)).reshape(shape) - data[1] = ma.masked + data[1, ::2] = ma.masked percent = np.array([10, 50, 70, 80]) axis = 0 mdtol = 0.1 @@ -140,10 +140,11 @@ def test_masked_2d_multi(self): # linear interpolation. expected = percent / 100 * 20 # Other columns are first column plus column number. - expected = ( + expected = ma.array( np.broadcast_to(expected, (shape[-1], percent.size)) + np.arange(shape[-1])[:, np.newaxis] ) + expected[::2] = ma.masked self.check_percentile_calc( data, axis, percent, expected, mdtol=mdtol, approx=True @@ -205,15 +206,32 @@ def setUp(self): self.agg_method = PERCENTILE.aggregate def test_masked(self): - shape = (2, 11) + # Using (3,11) because np.percentile returns a masked array anyway with + # (2, 11) + shape = (3, 11) data = ma.arange(np.prod(shape)).reshape(shape) data[0, ::2] = ma.masked - emsg = "Cannot use fast np.percentile method with masked array." + emsg = ( + "Cannot use fast np.percentile method with masked array unless " + "mdtol is 0." + ) with self.assertRaisesRegex(TypeError, emsg): PERCENTILE.aggregate( data, axis=0, percent=50, fast_percentile_method=True ) + def test_masked_mdtol_0(self): + # Using (3,11) because np.percentile returns a masked array anyway with + # (2, 11) + shape = (3, 11) + axis = 0 + percent = 50 + data = ma.arange(np.prod(shape)).reshape(shape) + data[0, ::2] = ma.masked + expected = ma.arange(shape[-1]) + 11 + expected[::2] = ma.masked + self.check_percentile_calc(data, axis, percent, expected, mdtol=0) + @mock.patch("numpy.percentile") def test_numpy_percentile_called(self, mocked_percentile): # Basic check that numpy.percentile is called. @@ -286,10 +304,26 @@ def test_masked(self): actual = PERCENTILE.lazy_aggregate( data, axis=0, percent=50, fast_percentile_method=True ) - emsg = "Cannot use fast np.percentile method with masked array." + emsg = ( + "Cannot use fast np.percentile method with masked array unless " + "mdtol is 0." + ) with self.assertRaisesRegex(TypeError, emsg): as_concrete_data(actual) + def test_masked_mdtol_0(self): + # Using (3,11) because np.percentile returns a masked array anyway with + # (2, 11) + shape = (3, 11) + axis = 0 + percent = 50 + data = ma.arange(np.prod(shape)).reshape(shape) + data[0, ::2] = ma.masked + data = as_lazy_data(data) + expected = ma.arange(shape[-1]) + 11 + expected[::2] = ma.masked + self.check_percentile_calc(data, axis, percent, expected, mdtol=0) + @mock.patch("numpy.percentile", return_value=np.array([2, 4])) def test_numpy_percentile_called(self, mocked_percentile): # Basic check that numpy.percentile is called. From 76a16f1f1db2bef8ec138b6c35f58c64a5a89cfe Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 23 May 2022 16:51:33 +0100 Subject: [PATCH 114/319] Credit @rcomer on PR #4755 review (#4761) --- docs/src/whatsnew/latest.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 1f9d4d35b4..05d1e3fcdd 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -108,8 +108,8 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ fixed plotting of circular coordinates to extend kwarg arrays as well as the data. (:issue:`466`, :pull:`4649`) -#. `@wjbenfold`_ corrected the axis on which masking is applied when an - aggregator adds a trailing dimension. (:pull:`4755`) +#. `@wjbenfold`_ and `@rcomer`_ (reviewer) corrected the axis on which masking + is applied when an aggregator adds a trailing dimension. (:pull:`4755`) 💣 Incompatible Changes @@ -132,9 +132,9 @@ This document explains the changes made to Iris for this release #. `@pp-mo`_ made :meth:`~iris.cube.Cube.add_aux_factory` faster. (:pull:`4718`) -#. `@wjbenfold`_ permitted the fast percentile aggregation method to be used on - masked data when the missing data tolerance is set to 0. (:issue:`4735`, - :pull:`4755`) +#. `@wjbenfold`_ and `@rcomer`_ (reviewer) permitted the fast percentile + aggregation method to be used on masked data when the missing data tolerance + is set to 0. (:issue:`4735`, :pull:`4755`) 🔥 Deprecations From f2113a2666007c864bcd77504149bd5c1286e143 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Mon, 23 May 2022 18:15:12 +0100 Subject: [PATCH 115/319] CPerf and SPerf Benchmark Fixes (#4758) * CSPerf fixes. * Correct argument hashing in file names. * Warn if BENCHMARK_DATA is not set. * SPerf combine_regions better file handling. * SPerf don't store FileMixin files long term - too much space. * More realistic SPerf file sizes, allow larger-than-memory files. * Add SPerf comment about total disk space. * Corrected CSPerf directories printout. * Remove namespace conflict in SPerf combine_regions. --- benchmarks/README.md | 4 ++- .../benchmarks/generate_data/__init__.py | 6 +++++ benchmarks/benchmarks/generate_data/stock.py | 16 +++++++++--- benchmarks/benchmarks/sperf/__init__.py | 6 ++++- .../benchmarks/sperf/combine_regions.py | 25 ++++++++++++------- benchmarks/benchmarks/sperf/load.py | 3 --- lib/iris/tests/stock/netcdf.py | 8 ++++-- noxfile.py | 9 +++++-- 8 files changed, 56 insertions(+), 21 deletions(-) diff --git a/benchmarks/README.md b/benchmarks/README.md index 6ea53c3ae8..8dffd473f3 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -30,7 +30,9 @@ used to generate benchmark test objects/files; see but will defer to any value already set in the shell. * `BENCHMARK_DATA` - optional - path to a directory for benchmark synthetic test data, which the benchmark scripts will create if it doesn't already -exist. Defaults to `/benchmarks/.data/` if not set. +exist. Defaults to `/benchmarks/.data/` if not set. Note that some of +the generated files, especially in the 'SPerf' suite, are many GB in size so +plan accordingly. * `ON_DEMAND_BENCHMARKS` - optional - when set (to any value): benchmarks decorated with `@on_demand_benchmark` are included in the ASV run. Usually coupled with the ASV `--bench` argument to only run the benchmark(s) of diff --git a/benchmarks/benchmarks/generate_data/__init__.py b/benchmarks/benchmarks/generate_data/__init__.py index 8874a2c214..78b971d9de 100644 --- a/benchmarks/benchmarks/generate_data/__init__.py +++ b/benchmarks/benchmarks/generate_data/__init__.py @@ -22,6 +22,7 @@ from pathlib import Path from subprocess import CalledProcessError, check_output, run from textwrap import dedent +from warnings import warn from iris._lazy_data import as_concrete_data from iris.fileformats import netcdf @@ -47,6 +48,11 @@ BENCHMARK_DATA = Path(environ.get("BENCHMARK_DATA", default_data_dir)) if BENCHMARK_DATA == default_data_dir: BENCHMARK_DATA.mkdir(exist_ok=True) + message = ( + f"No BENCHMARK_DATA env var, defaulting to {BENCHMARK_DATA}. " + "Note that some benchmark files are GB in size." + ) + warn(message) elif not BENCHMARK_DATA.is_dir(): message = f"Not a directory: {BENCHMARK_DATA} ." raise ValueError(message) diff --git a/benchmarks/benchmarks/generate_data/stock.py b/benchmarks/benchmarks/generate_data/stock.py index bbc7dc0a63..eaf46bb405 100644 --- a/benchmarks/benchmarks/generate_data/stock.py +++ b/benchmarks/benchmarks/generate_data/stock.py @@ -9,6 +9,8 @@ See :mod:`benchmarks.generate_data` for an explanation of this structure. """ +from hashlib import sha256 +import json from pathlib import Path from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD, load_mesh @@ -16,6 +18,14 @@ from . import BENCHMARK_DATA, REUSE_DATA, load_realised, run_function_elsewhere +def hash_args(*args, **kwargs): + """Convert arguments into a short hash - for preserving args in filenames.""" + arg_string = str(args) + kwarg_string = json.dumps(kwargs) + full_string = arg_string + kwarg_string + return sha256(full_string.encode()).hexdigest()[:10] + + def _create_file__xios_common(func_name, **kwargs): def _external(func_name_, temp_file_dir, **kwargs_): from iris.tests.stock import netcdf @@ -23,7 +33,7 @@ def _external(func_name_, temp_file_dir, **kwargs_): func = getattr(netcdf, func_name_) print(func(temp_file_dir, **kwargs_), end="") - args_hash = hash(str(kwargs)) + args_hash = hash_args(**kwargs) save_path = (BENCHMARK_DATA / f"{func_name}_{args_hash}").with_suffix( ".nc" ) @@ -95,7 +105,7 @@ def _external(*args, **kwargs): save_mesh(new_mesh, save_path_) arg_list = [n_nodes, n_faces, n_edges] - args_hash = hash(str(arg_list)) + args_hash = hash_args(*arg_list) save_path = (BENCHMARK_DATA / f"sample_mesh_{args_hash}").with_suffix( ".nc" ) @@ -139,7 +149,7 @@ def _external(sample_mesh_kwargs_, save_path_): new_meshcoord = sample_meshcoord(mesh=input_mesh) save_mesh(new_meshcoord.mesh, save_path_) - args_hash = hash(str(sample_mesh_kwargs)) + args_hash = hash_args(**sample_mesh_kwargs) save_path = ( BENCHMARK_DATA / f"sample_mesh_coord_{args_hash}" ).with_suffix(".nc") diff --git a/benchmarks/benchmarks/sperf/__init__.py b/benchmarks/benchmarks/sperf/__init__.py index 696c8ef4df..eccad56f6f 100644 --- a/benchmarks/benchmarks/sperf/__init__.py +++ b/benchmarks/benchmarks/sperf/__init__.py @@ -20,10 +20,14 @@ class FileMixin: """For use in any benchmark classes that work on a file.""" + # Allows time for large file generation. + timeout = 3600.0 + # Largest file with these params: ~90GB. + # Total disk space: ~410GB. params = [ [12, 384, 640, 960, 1280, 1668], [1, 36, 72], - [1, 3, 36, 72], + [1, 3, 10], ] param_names = ["cubesphere_C", "N levels", "N time steps"] # cubesphere_C: notation refers to faces per panel. diff --git a/benchmarks/benchmarks/sperf/combine_regions.py b/benchmarks/benchmarks/sperf/combine_regions.py index fd2c95c7fc..e68382c06e 100644 --- a/benchmarks/benchmarks/sperf/combine_regions.py +++ b/benchmarks/benchmarks/sperf/combine_regions.py @@ -16,20 +16,21 @@ from iris.experimental.ugrid.utils import recombine_submeshes from .. import TrackAddedMemoryAllocation, on_demand_benchmark -from ..generate_data.ugrid import make_cube_like_2d_cubesphere +from ..generate_data.ugrid import BENCHMARK_DATA, make_cube_like_2d_cubesphere class Mixin: # Characterise time taken + memory-allocated, for various stages of combine # operations on cubesphere-like test data. - timeout = 180.0 + timeout = 300.0 params = [100, 200, 300, 500, 1000, 1668] param_names = ["cubesphere_C"] # Fix result units for the tracking benchmarks. unit = "Mb" + temp_save_path = BENCHMARK_DATA / "tmp.nc" def _parametrised_cache_filename(self, n_cubesphere, content_name): - return f"cube_C{n_cubesphere}_{content_name}.nc" + return BENCHMARK_DATA / f"cube_C{n_cubesphere}_{content_name}.nc" def _make_region_cubes(self, full_mesh_cube): """Make a fixed number of region cubes from a full meshcube.""" @@ -139,6 +140,9 @@ def setup( # Fix dask usage mode for all the subsequent performance tests. self.fix_dask_settings() + def teardown(self, _): + self.temp_save_path.unlink(missing_ok=True) + def fix_dask_settings(self): """ Fix "standard" dask behaviour for time+space testing. @@ -165,6 +169,9 @@ def recombine(self): ) return result + def save_recombined_cube(self): + save(self.recombined_cube, self.temp_save_path) + @on_demand_benchmark class CreateCube(Mixin): @@ -215,15 +222,15 @@ class SaveData(Mixin): def time_save(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. - save(self.recombined_cube, "tmp.nc") + self.save_recombined_cube() @TrackAddedMemoryAllocation.decorator() def track_addedmem_save(self, n_cubesphere): - save(self.recombined_cube, "tmp.nc") + self.save_recombined_cube() def track_filesize_saved(self, n_cubesphere): - save(self.recombined_cube, "tmp.nc") - return os.path.getsize("tmp.nc") * 1.0e-6 + self.save_recombined_cube() + return self.temp_save_path.stat().st_size * 1.0e-6 @on_demand_benchmark @@ -243,8 +250,8 @@ def setup( def time_stream_file2file(self, n_cubesphere): # Save to disk, which must compute data + stream it to file. - save(self.recombined_cube, "tmp.nc") + self.save_recombined_cube() @TrackAddedMemoryAllocation.decorator() def track_addedmem_stream_file2file(self, n_cubesphere): - save(self.recombined_cube, "tmp.nc") + self.save_recombined_cube() diff --git a/benchmarks/benchmarks/sperf/load.py b/benchmarks/benchmarks/sperf/load.py index c1d1db43a9..6a60355976 100644 --- a/benchmarks/benchmarks/sperf/load.py +++ b/benchmarks/benchmarks/sperf/load.py @@ -18,9 +18,6 @@ def time_load_cube(self, _, __, ___): @on_demand_benchmark class Realise(FileMixin): - # The larger files take a long time to realise. - timeout = 600.0 - def setup(self, c_size, n_levels, n_times): super().setup(c_size, n_levels, n_times) self.loaded_cube = self.load_cube() diff --git a/lib/iris/tests/stock/netcdf.py b/lib/iris/tests/stock/netcdf.py index 030e90a0f3..4e12850ef1 100644 --- a/lib/iris/tests/stock/netcdf.py +++ b/lib/iris/tests/stock/netcdf.py @@ -9,6 +9,8 @@ from string import Template import subprocess +import dask +from dask import array as da import netCDF4 import numpy as np @@ -79,11 +81,13 @@ def _add_standard_data(nc_path, unlimited_dim_size=0): # so it can be a dim-coord. data_size = np.prod(shape) data = np.arange(1, data_size + 1, dtype=var.dtype).reshape(shape) + var[:] = data else: # Fill with a plain value. But avoid zeros, so we can simulate # valid ugrid connectivities even when start_index=1. - data = np.ones(shape, dtype=var.dtype) # Do not use zero - var[:] = data + with dask.config.set({"array.chunk-size": "2048MiB"}): + data = da.ones(shape, dtype=var.dtype) # Do not use zero + da.store(data, var) ds.close() diff --git a/noxfile.py b/noxfile.py index 00a866f814..7919dfdfde 100755 --- a/noxfile.py +++ b/noxfile.py @@ -9,6 +9,7 @@ import hashlib import os from pathlib import Path +import re from tempfile import NamedTemporaryFile from typing import Literal @@ -314,7 +315,7 @@ def benchmarks( ---------- session: object A `nox.sessions.Session` object. - run_type: {"overnight", "branch", "custom"} + run_type: {"overnight", "branch", "cperf", "sperf", "custom"} * ``overnight``: benchmarks all commits between the input **first commit** to ``HEAD``, comparing each to its parent for performance shifts. If a commit causes shifts, the output is saved to a file: @@ -501,6 +502,11 @@ def asv_compare(*commits): asv_command = ( asv_harness.format(posargs=commit_range) + f" --bench={run_type}" ) + # C/SPerf benchmarks are much bigger than the CI ones: + # Don't fail the whole run if memory blows on 1 benchmark. + asv_command = asv_command.replace(" --strict", "") + # Only do a single round. + asv_command = re.sub(r"rounds=\d", "rounds=1", asv_command) session.run(*asv_command.split(" "), *asv_args) asv_command = f"asv publish {commit_range} --html-dir={publish_subdir}" @@ -511,7 +517,6 @@ def asv_compare(*commits): print( f'New ASV results for "{run_type}".\n' f'See "{publish_subdir}",' - f'\n html in "{location / "html"}".' f'\n or JSON files under "{location / "results"}".' ) From 6c8ccac7958e2d3069200b2553187bae82807ac4 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 24 May 2022 15:44:40 +0100 Subject: [PATCH 116/319] don't use np.float or np.int (#4763) --- .../ugrid/cf/test_CFUGridReader.py | 18 ++++++++---------- .../tests/unit/fileformats/cf/test_CFReader.py | 8 ++++---- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py index 8f029c1b7b..e44aee730a 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py @@ -16,8 +16,6 @@ from unittest import mock -import numpy as np - from iris.experimental.ugrid.cf import ( CFUGridAuxiliaryCoordinateVariable, CFUGridConnectivityVariable, @@ -56,17 +54,17 @@ class Test_build_cf_groups(tests.IrisTest): @classmethod def setUpClass(cls): # Replicating syntax from test_CFReader.Test_build_cf_groups__formula_terms. - cls.mesh = netcdf_ugrid_variable("mesh", "", np.int) - cls.node_x = netcdf_ugrid_variable("node_x", "node", np.float) - cls.node_y = netcdf_ugrid_variable("node_y", "node", np.float) - cls.face_x = netcdf_ugrid_variable("face_x", "face", np.float) - cls.face_y = netcdf_ugrid_variable("face_y", "face", np.float) + cls.mesh = netcdf_ugrid_variable("mesh", "", int) + cls.node_x = netcdf_ugrid_variable("node_x", "node", float) + cls.node_y = netcdf_ugrid_variable("node_y", "node", float) + cls.face_x = netcdf_ugrid_variable("face_x", "face", float) + cls.face_y = netcdf_ugrid_variable("face_y", "face", float) cls.face_nodes = netcdf_ugrid_variable( - "face_nodes", "face vertex", np.int + "face_nodes", "face vertex", int ) - cls.levels = netcdf_ugrid_variable("levels", "levels", np.int) + cls.levels = netcdf_ugrid_variable("levels", "levels", int) cls.data = netcdf_ugrid_variable( - "data", "levels face", np.float, coordinates="face_x face_y" + "data", "levels face", float, coordinates="face_x face_y" ) # Add necessary attributes for mesh recognition. diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py index 70173bb2ac..dee28e98cc 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py @@ -83,13 +83,13 @@ def setUp(self): "delta", "height", np.float64, bounds="delta_bnds" ) self.delta_bnds = netcdf_variable( - "delta_bnds", "height bnds", np.float + "delta_bnds", "height bnds", np.float64 ) self.sigma = netcdf_variable( "sigma", "height", np.float64, bounds="sigma_bnds" ) self.sigma_bnds = netcdf_variable( - "sigma_bnds", "height bnds", np.float + "sigma_bnds", "height bnds", np.float64 ) self.orography = netcdf_variable("orography", "lat lon", np.float64) formula_terms = "a: delta b: sigma orog: orography" @@ -185,13 +185,13 @@ def setUp(self): "delta", "height", np.float64, bounds="delta_bnds" ) self.delta_bnds = netcdf_variable( - "delta_bnds", "height bnds", np.float + "delta_bnds", "height bnds", np.float64 ) self.sigma = netcdf_variable( "sigma", "height", np.float64, bounds="sigma_bnds" ) self.sigma_bnds = netcdf_variable( - "sigma_bnds", "height bnds", np.float + "sigma_bnds", "height bnds", np.float64 ) self.orography = netcdf_variable("orography", "lat lon", np.float64) formula_terms = "a: delta b: sigma orog: orography" From 904a4c866c5b58f37fed32f164aebfe1361ebb05 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 24 May 2022 17:18:20 +0100 Subject: [PATCH 117/319] Test warnings (#4764) * import from collections.abc * deprecated csr module * unittest deprecations * random_integers -> randint * collections again * inspect deprecation * reduce redunancy * non test class --- lib/iris/tests/integration/test_regridding.py | 2 +- lib/iris/tests/test_cdm.py | 2 +- lib/iris/tests/test_coordsystem.py | 6 +- lib/iris/tests/test_file_save.py | 2 +- lib/iris/tests/test_util.py | 2 +- .../test__RegularGridInterpolator.py | 2 +- lib/iris/tests/unit/cube/test_CubeList.py | 2 +- .../tests/unit/fileformats/pp/test_PPField.py | 68 +++++++++---------- 8 files changed, 43 insertions(+), 43 deletions(-) diff --git a/lib/iris/tests/integration/test_regridding.py b/lib/iris/tests/integration/test_regridding.py index 4ceac6ab1e..3e87a8d0aa 100644 --- a/lib/iris/tests/integration/test_regridding.py +++ b/lib/iris/tests/integration/test_regridding.py @@ -112,7 +112,7 @@ def test_nearest(self): class TestZonalMean_global(tests.IrisTest): def setUp(self): np.random.seed(0) - self.src = iris.cube.Cube(np.random.random_integers(0, 10, (140, 1))) + self.src = iris.cube.Cube(np.random.randint(0, 10, size=(140, 1))) s_crs = iris.coord_systems.GeogCS(6371229.0) sy_coord = iris.coords.DimCoord( np.linspace(-90, 90, 140), diff --git a/lib/iris/tests/test_cdm.py b/lib/iris/tests/test_cdm.py index 64a7924ce4..0615dc39bf 100644 --- a/lib/iris/tests/test_cdm.py +++ b/lib/iris/tests/test_cdm.py @@ -692,7 +692,7 @@ def test_cube_iteration(self): pass def test_not_iterable(self): - self.assertFalse(isinstance(self.t, collections.Iterable)) + self.assertFalse(isinstance(self.t, collections.abc.Iterable)) class Test2dSlicing(TestCube2d): diff --git a/lib/iris/tests/test_coordsystem.py b/lib/iris/tests/test_coordsystem.py index e4c776a063..212c04bd7e 100644 --- a/lib/iris/tests/test_coordsystem.py +++ b/lib/iris/tests/test_coordsystem.py @@ -87,7 +87,7 @@ def test_simple(self): def test_different_class(self): a = self.cs1 b = self.cs3 - self.assertNotEquals(a, b) + self.assertNotEqual(a, b) def test_different_public_attributes(self): a = self.cs1 @@ -98,7 +98,7 @@ def test_different_public_attributes(self): self.assertEqual(a.foo, "a") # a and b should not be the same - self.assertNotEquals(a, b) + self.assertNotEqual(a, b) # a and b should be the same b.foo = "a" @@ -106,7 +106,7 @@ def test_different_public_attributes(self): b.foo = "b" # a and b should not be the same - self.assertNotEquals(a, b) + self.assertNotEqual(a, b) class Test_CoordSystem_xml_element(tests.IrisTest): diff --git a/lib/iris/tests/test_file_save.py b/lib/iris/tests/test_file_save.py index 3b751cfcbe..216637202a 100644 --- a/lib/iris/tests/test_file_save.py +++ b/lib/iris/tests/test_file_save.py @@ -201,7 +201,7 @@ def test_bytesio(self): data = infile.read() # Compare files - self.assertEquals( + self.assertEqual( data, sio.getvalue(), "Mismatch in data when comparing iris bytesio save " diff --git a/lib/iris/tests/test_util.py b/lib/iris/tests/test_util.py index ec7f8d1023..db182ae3f3 100644 --- a/lib/iris/tests/test_util.py +++ b/lib/iris/tests/test_util.py @@ -144,7 +144,7 @@ def test_invalid_clip_lengths(self): def test_default_values(self): # Get the default values specified in the function - argspec = inspect.getargspec(iris.util.clip_string) + argspec = inspect.getfullargspec(iris.util.clip_string) arg_dict = dict(zip(argspec.args[-2:], argspec.defaults)) result = iris.util.clip_string( diff --git a/lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py b/lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py index f4c6623ad1..f0aa027baa 100644 --- a/lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py +++ b/lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py @@ -11,7 +11,7 @@ import iris.tests as tests # isort:skip import numpy as np -from scipy.sparse.csr import csr_matrix +from scipy.sparse import csr_matrix from iris.analysis._scipy_interpolate import _RegularGridInterpolator import iris.tests.stock as stock diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py index eb4c6c4f3f..50c1e553bc 100644 --- a/lib/iris/tests/unit/cube/test_CubeList.py +++ b/lib/iris/tests/unit/cube/test_CubeList.py @@ -565,7 +565,7 @@ def setUp(self): self.scalar_cubes.append(Cube(i, long_name=letter)) def test_iterable(self): - self.assertTrue(isinstance(self.scalar_cubes, collections.Iterable)) + self.assertIsInstance(self.scalar_cubes, collections.abc.Iterable) def test_iteration(self): letters = "abcd" * 5 diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPField.py b/lib/iris/tests/unit/fileformats/pp/test_PPField.py index 5ce41402ad..266c26aa59 100644 --- a/lib/iris/tests/unit/fileformats/pp/test_PPField.py +++ b/lib/iris/tests/unit/fileformats/pp/test_PPField.py @@ -43,7 +43,7 @@ ] -class TestPPField(PPField): +class DummyPPField(PPField): HEADER_DEFN = DUMMY_HEADER HEADER_DICT = dict(DUMMY_HEADER) @@ -81,7 +81,7 @@ def test_float64(self): # Tests down-casting of >f8 data to >f4. def field_checksum(data): - field = TestPPField()._ready_for_save() + field = DummyPPField()._ready_for_save() field.data = data with self.temp_filename(".pp") as temp_filename: with open(temp_filename, "wb") as pp_file: @@ -98,7 +98,7 @@ def field_checksum(data): def test_masked_mdi_value_warning(self): # Check that an unmasked MDI value raises a warning. - field = TestPPField()._ready_for_save() + field = DummyPPField()._ready_for_save() field.bmdi = -123.4 # Make float32 data, as float64 default produces an extra warning. field.data = np.ma.masked_array( @@ -112,7 +112,7 @@ def test_masked_mdi_value_warning(self): def test_unmasked_mdi_value_warning(self): # Check that MDI in *unmasked* data raises a warning. - field = TestPPField()._ready_for_save() + field = DummyPPField()._ready_for_save() field.bmdi = -123.4 # Make float32 data, as float64 default produces an extra warning. field.data = np.array([1.0, field.bmdi, 3.0], dtype=np.float32) @@ -124,7 +124,7 @@ def test_unmasked_mdi_value_warning(self): def test_mdi_masked_value_nowarning(self): # Check that a *masked* MDI value does not raise a warning. - field = TestPPField()._ready_for_save() + field = DummyPPField()._ready_for_save() field.bmdi = -123.4 # Make float32 data, as float64 default produces an extra warning. field.data = np.ma.masked_array( @@ -141,24 +141,24 @@ def test_mdi_masked_value_nowarning(self): class Test_calendar(tests.IrisTest): def test_greg(self): - field = TestPPField() + field = DummyPPField() field.lbtim = SplittableInt(1, {"ia": 2, "ib": 1, "ic": 0}) self.assertEqual(field.calendar, "gregorian") def test_360(self): - field = TestPPField() + field = DummyPPField() field.lbtim = SplittableInt(2, {"ia": 2, "ib": 1, "ic": 0}) self.assertEqual(field.calendar, "360_day") def test_365(self): - field = TestPPField() + field = DummyPPField() field.lbtim = SplittableInt(4, {"ia": 2, "ib": 1, "ic": 0}) self.assertEqual(field.calendar, "365_day") class Test_coord_system(tests.IrisTest): def _check_cs(self, bplat, bplon, rotated): - field = TestPPField() + field = DummyPPField() field.bplat = bplat field.bplon = bplon with mock.patch( @@ -203,29 +203,29 @@ def setUp(self): self.header = list(header_longs) + list(header_floats) def test_no_headers(self): - field = TestPPField() + field = DummyPPField() self.assertIsNone(field._raw_header) self.assertIsNone(field.raw_lbtim) self.assertIsNone(field.raw_lbpack) def test_lbtim_lookup(self): - self.assertEqual(TestPPField.HEADER_DICT["lbtim"], (12,)) + self.assertEqual(DummyPPField.HEADER_DICT["lbtim"], (12,)) def test_lbpack_lookup(self): - self.assertEqual(TestPPField.HEADER_DICT["lbpack"], (20,)) + self.assertEqual(DummyPPField.HEADER_DICT["lbpack"], (20,)) def test_raw_lbtim(self): raw_lbtim = 4321 - (loc,) = TestPPField.HEADER_DICT["lbtim"] + (loc,) = DummyPPField.HEADER_DICT["lbtim"] self.header[loc] = raw_lbtim - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.raw_lbtim, raw_lbtim) def test_raw_lbpack(self): raw_lbpack = 4321 - (loc,) = TestPPField.HEADER_DICT["lbpack"] + (loc,) = DummyPPField.HEADER_DICT["lbpack"] self.header[loc] = raw_lbpack - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.raw_lbpack, raw_lbpack) @@ -237,39 +237,39 @@ def setUp(self): def test_attr_singular_long(self): lbrow = 1234 - (loc,) = TestPPField.HEADER_DICT["lbrow"] + (loc,) = DummyPPField.HEADER_DICT["lbrow"] self.header[loc] = lbrow - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.lbrow, lbrow) def test_attr_multi_long(self): lbuser = (100, 101, 102, 103, 104, 105, 106) - loc = TestPPField.HEADER_DICT["lbuser"] + loc = DummyPPField.HEADER_DICT["lbuser"] self.header[loc[0] : loc[-1] + 1] = lbuser - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.lbuser, lbuser) def test_attr_singular_float(self): bdatum = 1234 - (loc,) = TestPPField.HEADER_DICT["bdatum"] + (loc,) = DummyPPField.HEADER_DICT["bdatum"] self.header[loc] = bdatum - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.bdatum, bdatum) def test_attr_multi_float(self): brsvd = (100, 101, 102, 103) - loc = TestPPField.HEADER_DICT["brsvd"] + loc = DummyPPField.HEADER_DICT["brsvd"] start = loc[0] stop = loc[-1] + 1 self.header[start:stop] = brsvd - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.brsvd, brsvd) def test_attr_lbtim(self): raw_lbtim = 4321 - (loc,) = TestPPField.HEADER_DICT["lbtim"] + (loc,) = DummyPPField.HEADER_DICT["lbtim"] self.header[loc] = raw_lbtim - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) result = field.lbtim self.assertEqual(result, raw_lbtim) self.assertIsInstance(result, SplittableInt) @@ -279,9 +279,9 @@ def test_attr_lbtim(self): def test_attr_lbpack(self): raw_lbpack = 4321 - (loc,) = TestPPField.HEADER_DICT["lbpack"] + (loc,) = DummyPPField.HEADER_DICT["lbpack"] self.header[loc] = raw_lbpack - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) result = field.lbpack self.assertEqual(result, raw_lbpack) self.assertIsInstance(result, SplittableInt) @@ -290,7 +290,7 @@ def test_attr_lbpack(self): self.assertIsInstance(result, SplittableInt) def test_attr_raw_lbtim_assign(self): - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.raw_lbpack, 0) self.assertEqual(field.lbtim, 0) raw_lbtim = 4321 @@ -299,7 +299,7 @@ def test_attr_raw_lbtim_assign(self): self.assertNotIsInstance(field.raw_lbtim, SplittableInt) def test_attr_raw_lbpack_assign(self): - field = TestPPField(header=self.header) + field = DummyPPField(header=self.header) self.assertEqual(field.raw_lbpack, 0) self.assertEqual(field.lbpack, 0) raw_lbpack = 4321 @@ -309,14 +309,14 @@ def test_attr_raw_lbpack_assign(self): def test_attr_unknown(self): with self.assertRaises(AttributeError): - TestPPField().x + DummyPPField().x class Test_lbtim(tests.IrisTest): def test_get_splittable(self): headers = [0] * 64 headers[12] = 12345 - field = TestPPField(headers) + field = DummyPPField(headers) self.assertIsInstance(field.lbtim, SplittableInt) self.assertEqual(field.lbtim.ia, 123) self.assertEqual(field.lbtim.ib, 4) @@ -325,7 +325,7 @@ def test_get_splittable(self): def test_set_int(self): headers = [0] * 64 headers[12] = 12345 - field = TestPPField(headers) + field = DummyPPField(headers) field.lbtim = 34567 self.assertIsInstance(field.lbtim, SplittableInt) self.assertEqual(field.lbtim.ia, 345) @@ -339,7 +339,7 @@ def test_set_splittable(self): # arbitrary SplittableInt with crazy named attributes. headers = [0] * 64 headers[12] = 12345 - field = TestPPField(headers) + field = DummyPPField(headers) si = SplittableInt(34567, {"foo": 0}) field.lbtim = si self.assertIsInstance(field.lbtim, SplittableInt) From b1b77b919f2a17f4caf3bc8c20738f2a65f4f477 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Wed, 25 May 2022 14:53:39 +0100 Subject: [PATCH 118/319] Increase speed of iris.analysis.trajectory.interpolate with linear interpolation (#4366) * Interpolate all points at once rather than sequentially * This splits the internals of cube.interpolate in an effort not to make so much superfluous data, but is slow * Generate the extra values then discard them, but with minimal looping * Updated docstrings to clarify behaviour * Benchmarks for trajectory (linear and nearest neighbour) * Update error thrown for aux coord over interpolated and uninterpolated dimensions, and add unit tests * Update cml * What's new has moved * What's new fix for rebase --- benchmarks/benchmarks/trajectory.py | 48 +++++++ docs/src/whatsnew/latest.rst | 4 + lib/iris/analysis/__init__.py | 4 + lib/iris/analysis/trajectory.py | 67 +++++++-- lib/iris/cube.py | 2 + .../tests/results/netcdf/save_load_traj.cml | 4 +- .../results/trajectory/constant_latitude.cml | 10 +- .../tests/results/trajectory/single_point.cml | 8 +- lib/iris/tests/results/trajectory/zigzag.cml | 29 ++-- .../analysis/trajectory/test_interpolate.py | 127 ++++++++++++++++++ 10 files changed, 267 insertions(+), 36 deletions(-) create mode 100644 benchmarks/benchmarks/trajectory.py diff --git a/benchmarks/benchmarks/trajectory.py b/benchmarks/benchmarks/trajectory.py new file mode 100644 index 0000000000..5c1d10d218 --- /dev/null +++ b/benchmarks/benchmarks/trajectory.py @@ -0,0 +1,48 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Trajectory benchmark test + +""" + +# import iris tests first so that some things can be initialised before +# importing anything else +from iris import tests # isort:skip + +import numpy as np + +import iris +from iris.analysis.trajectory import interpolate + + +class TrajectoryInterpolation: + def setup(self) -> None: + # Prepare a cube and a template + + cube_file_path = tests.get_data_path( + ["NetCDF", "regrid", "regrid_xyt.nc"] + ) + self.cube = iris.load_cube(cube_file_path) + + trajectory = np.array( + [np.array((-50 + i, -50 + i)) for i in range(100)] + ) + self.sample_points = [ + ("longitude", trajectory[:, 0]), + ("latitude", trajectory[:, 1]), + ] + + def time_trajectory_linear(self) -> None: + # Regrid the cube onto the template. + out_cube = interpolate(self.cube, self.sample_points, method="linear") + # Realise the data + out_cube.data + + def time_trajectory_nearest(self) -> None: + # Regrid the cube onto the template. + out_cube = interpolate(self.cube, self.sample_points, method="nearest") + # Realise the data + out_cube.data diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 05d1e3fcdd..75d24697f5 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -136,6 +136,10 @@ This document explains the changes made to Iris for this release aggregation method to be used on masked data when the missing data tolerance is set to 0. (:issue:`4735`, :pull:`4755`) +#. `@wjbenfold`_ improved the speed of linear interpolation using + :meth:`iris.analysis.trajectory.interpolate` (:pull:`4366`) + + 🔥 Deprecations =============== diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 6d2ee599c8..41b6ec389c 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -2696,6 +2696,10 @@ def interpolator(self, cube, coords): dimensions in the result cube caused by scalar values in `sample_points`. + The N arrays of values within `sample_points` will be used to + create an N-d grid of points that will then be sampled (rather than + just N points) + The values for coordinates that correspond to date/times may optionally be supplied as datetime.datetime or cftime.datetime instances. diff --git a/lib/iris/analysis/trajectory.py b/lib/iris/analysis/trajectory.py index d5fac9d108..946ae1cb2c 100644 --- a/lib/iris/analysis/trajectory.py +++ b/lib/iris/analysis/trajectory.py @@ -320,20 +320,59 @@ def interpolate(cube, sample_points, method=None): break if method in ["linear", None]: - for i in range(trajectory_size): - point = [(coord, values[i]) for coord, values in sample_points] - column = cube.interpolate(point, Linear()) - new_cube.data[..., i] = column.data - # Fill in the empty squashed (non derived) coords. - for column_coord in column.dim_coords + column.aux_coords: - src_dims = cube.coord_dims(column_coord) - if not squish_my_dims.isdisjoint(src_dims): - if len(column_coord.points) != 1: - msg = "Expected to find exactly one point. Found {}." - raise Exception(msg.format(column_coord.points)) - new_cube.coord(column_coord.name()).points[ - i - ] = column_coord.points[0] + # Using cube.interpolate will generate extra values that we don't need + # as it makes a grid from the provided coordinates (like a meshgrid) + # and then does interpolation for all of them. This is memory + # inefficient, but significantly more time efficient than calling + # cube.interpolate (or the underlying method on the interpolator) + # repeatedly, so using this approach for now. In future, it would be + # ideal if we only interpolated at the points we care about + columns = cube.interpolate(sample_points, Linear()) + # np.einsum(a, [0, 0], [0]) is like np.diag(a) + # We're using einsum here to do an n-dimensional diagonal, leaving the + # other dimensions unaffected and putting the diagonal's direction on + # the final axis + initial_inds = list(range(1, columns.ndim + 1)) + for ind in squish_my_dims: + initial_inds[ind] = 0 + final_inds = list(filter(lambda x: x != 0, initial_inds)) + [0] + new_cube.data = np.einsum(columns.data, initial_inds, final_inds) + + # Fill in the empty squashed (non derived) coords. + # We're using the same einstein summation plan as for the cube, but + # redoing those indices to match the indices in the coordinates + for columns_coord in columns.dim_coords + columns.aux_coords: + src_dims = cube.coord_dims(columns_coord) + if not squish_my_dims.isdisjoint(src_dims): + # Mapping the cube indicies onto the coord + initial_coord_inds = [initial_inds[ind] for ind in src_dims] + # Making the final ones the same way as for the cube + # 0 will always appear in the initial ones because we know this + # coord overlaps the squish dims + final_coord_inds = list( + filter(lambda x: x != 0, initial_coord_inds) + ) + [0] + new_coord_points = np.einsum( + columns_coord.points, initial_coord_inds, final_coord_inds + ) + # Check we're not overwriting coord.points with the wrong shape + if ( + not new_cube.coord(columns_coord.name()).points.shape + == new_coord_points.shape + ): + msg = ( + "Coord {} was expected to have new points of shape {}. " + "Found shape of {}." + ) + raise ValueError( + msg.format( + columns_coord.name(), + new_cube.coord(columns_coord.name()).points.shape, + new_coord_points.shape, + ) + ) + # Replace the points + new_cube.coord(columns_coord.name()).points = new_coord_points elif method == "nearest": # Use a cache with _nearest_neighbour_indices_ndcoords() diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 6211f0a727..f0a79d0965 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -4359,6 +4359,8 @@ def interpolate(self, sample_points, scheme, collapse_scalar=True): interpolate. The values for coordinates that correspond to dates or times may optionally be supplied as datetime.datetime or cftime.datetime instances. + The N pairs supplied will be used to create an N-d grid of points + that will then be sampled (rather than just N points). * scheme: An instance of the type of interpolation to use to interpolate from this :class:`~iris.cube.Cube` to the given sample points. The diff --git a/lib/iris/tests/results/netcdf/save_load_traj.cml b/lib/iris/tests/results/netcdf/save_load_traj.cml index 7f8b3d7e99..9b225d127f 100644 --- a/lib/iris/tests/results/netcdf/save_load_traj.cml +++ b/lib/iris/tests/results/netcdf/save_load_traj.cml @@ -1,6 +1,6 @@ - + @@ -36,6 +36,6 @@ - + diff --git a/lib/iris/tests/results/trajectory/constant_latitude.cml b/lib/iris/tests/results/trajectory/constant_latitude.cml index 7990edada5..f07f1a131f 100644 --- a/lib/iris/tests/results/trajectory/constant_latitude.cml +++ b/lib/iris/tests/results/trajectory/constant_latitude.cml @@ -1,6 +1,6 @@ - + @@ -16,13 +16,13 @@ + -0.1188, -0.1188]" shape="(100,)" standard_name="grid_latitude" units="Unit('degrees')" value_type="float64"> - + @@ -94,6 +94,6 @@ - + diff --git a/lib/iris/tests/results/trajectory/single_point.cml b/lib/iris/tests/results/trajectory/single_point.cml index 393ad5e335..a1cf83a03b 100644 --- a/lib/iris/tests/results/trajectory/single_point.cml +++ b/lib/iris/tests/results/trajectory/single_point.cml @@ -1,6 +1,6 @@ - + @@ -15,12 +15,12 @@ 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="forecast_reference_time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> - + - + @@ -92,6 +92,6 @@ - + diff --git a/lib/iris/tests/results/trajectory/zigzag.cml b/lib/iris/tests/results/trajectory/zigzag.cml index 250500786c..3bdb3e9013 100644 --- a/lib/iris/tests/results/trajectory/zigzag.cml +++ b/lib/iris/tests/results/trajectory/zigzag.cml @@ -1,6 +1,6 @@ - + @@ -14,19 +14,26 @@ - + - + @@ -52,6 +59,6 @@ - + diff --git a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py b/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py index 038019611c..dad781ed74 100644 --- a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py +++ b/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py @@ -181,5 +181,132 @@ def test_metadata(self): self.assertEqual(result, expected) +class TestLinear(tests.IrisTest): + # Test interpolation with 'linear' method. + # This is basically a wrapper to 'analysis._scipy_interpolate''s + # _RegulardGridInterpolator. That has its own test, so we don't test the + # basic calculation exhaustively here. Instead we check the way it + # handles the source and result cubes (especially coordinates). + + def setUp(self): + cube = iris.tests.stock.simple_3d() + # Actually, this cube *isn't* terribly realistic, as the lat+lon coords + # have integer type, which in this case produces some peculiar results. + # Let's fix that (and not bother to test the peculiar behaviour). + for coord_name in ("longitude", "latitude"): + coord = cube.coord(coord_name) + coord.points = coord.points.astype(float) + self.test_cube = cube + # Set sample point to test single-point linear interpolation operation. + self.single_sample_point = [ + ("latitude", [9]), + ("longitude", [-120]), + ] + # Set expected results of single-point linear interpolation operation. + self.single_sample_result = np.array( + [ + 64 / 15, + 244 / 15, + ] + )[:, np.newaxis] + + def test_single_point_same_cube(self): + # Check exact result matching for a single point. + cube = self.test_cube + result = interpolate(cube, self.single_sample_point, method="linear") + # Check that the result is a single trajectory point, exactly equal to + # the expected part of the original data. + self.assertEqual(result.shape[-1], 1) + self.assertArrayAllClose(result.data, self.single_sample_result) + + def test_multi_point_same_cube(self): + # Check an exact result for multiple points. + cube = self.test_cube + # Use latitude selection to recreate a whole row of the original cube. + sample_points = [ + ("longitude", [-180, -90, 0, 90]), + ("latitude", [0, 0, 0, 0]), + ] + result = interpolate(cube, sample_points, method="linear") + + # The result should be identical to a single latitude section of the + # original, but with modified coords (latitude has 4 repeated zeros). + expected = cube[:, 1, :] + # Result 'longitude' is now an aux coord. + co_x = expected.coord("longitude") + expected.remove_coord(co_x) + expected.add_aux_coord(co_x, 1) + # Result 'latitude' is now an aux coord containing 4*[0]. + expected.remove_coord("latitude") + co_y = AuxCoord( + [0, 0, 0, 0], standard_name="latitude", units="degrees" + ) + expected.add_aux_coord(co_y, 1) + self.assertEqual(result, expected) + + def test_aux_coord_noninterpolation_dim(self): + # Check exact result with an aux-coord mapped to an uninterpolated dim. + cube = self.test_cube + cube.add_aux_coord(DimCoord([17, 19], long_name="aux0"), 0) + + # The result cube should exactly equal a single source point. + result = interpolate(cube, self.single_sample_point, method="linear") + self.assertEqual(result.shape[-1], 1) + self.assertArrayAllClose(result.data, self.single_sample_result) + + def test_aux_coord_one_interp_dim(self): + # Check exact result with an aux-coord over one interpolation dims. + cube = self.test_cube + cube.add_aux_coord(AuxCoord([11, 12, 13, 14], long_name="aux_x"), 2) + + # The result cube should exactly equal a single source point. + result = interpolate(cube, self.single_sample_point, method="linear") + self.assertEqual(result.shape[-1], 1) + self.assertArrayAllClose(result.data, self.single_sample_result) + + def test_aux_coord_both_interp_dims(self): + # Check exact result with an aux-coord over both interpolation dims. + cube = self.test_cube + cube.add_aux_coord( + AuxCoord( + [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]], + long_name="aux_xy", + ), + (1, 2), + ) + + # The result cube should exactly equal a single source point. + result = interpolate(cube, self.single_sample_point, method="linear") + self.assertEqual(result.shape[-1], 1) + self.assertArrayAllClose(result.data, self.single_sample_result) + + def test_aux_coord_fail_mixed_dims(self): + # Check behaviour with an aux-coord mapped over both interpolation and + # non-interpolation dims : not supported. + cube = self.test_cube + cube.add_aux_coord( + AuxCoord( + [[111, 112, 113, 114], [211, 212, 213, 214]], + long_name="aux_0x", + ), + (0, 2), + ) + msg = "Coord aux_0x was expected to have new points of shape .*\\. Found shape of .*\\." + with self.assertRaisesRegex(ValueError, msg): + interpolate(cube, self.single_sample_point, method="linear") + + def test_metadata(self): + # Check exact result matching for a single point, with additional + # attributes and cell-methods. + cube = self.test_cube + cube.attributes["ODD_ATTR"] = "string-value-example" + cube.add_cell_method(iris.coords.CellMethod("mean", "area")) + result = interpolate(cube, self.single_sample_point, method="linear") + # Check that the result is a single trajectory point, exactly equal to + # the expected part of the original data. + self.assertEqual(result.shape[-1], 1) + self.assertArrayAllClose(result.data, self.single_sample_result) + + if __name__ == "__main__": tests.main() From 9a561d6e3e66b403062c9b0d55f8f24176045795 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Wed, 25 May 2022 14:53:54 +0100 Subject: [PATCH 119/319] Prevent truncation of cell method descriptions with nested brackets (#4436) * Add failing test for brackets within comments * Fix test * Separate private function now splits the cell method before it's parsed * Add additional failing test for multiple axis methods * Handle multiple axis cell methods * Update docstring to reflect eventual choices * Add warnings to function, and test them * Significantly simpler warning checks --- lib/iris/fileformats/netcdf.py | 69 ++++++++++++++++++- .../netcdf/test_parse_cell_methods.py | 59 ++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 7cab939417..7a0e4e655d 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -19,6 +19,7 @@ import os.path import re import string +from typing import List import warnings import cf_units @@ -185,13 +186,14 @@ _CM_INTERVAL = "interval" _CM_METHOD = "method" _CM_NAME = "name" +_CM_PARSE_NAME = re.compile(r"([\w_]+\s*?:\s+)+") _CM_PARSE = re.compile( r""" (?P([\w_]+\s*?:\s+)+) (?P[\w_\s]+(?![\w_]*\s*?:))\s* (?: \(\s* - (?P[^\)]+) + (?P.+) \)\s* )? """, @@ -203,6 +205,69 @@ class UnknownCellMethodWarning(Warning): pass +def _split_cell_methods(nc_cell_methods: str) -> List[re.Match]: + """ + Split a CF cell_methods attribute string into a list of zero or more cell + methods, each of which is then parsed with a regex to return a list of match + objects. + + Args: + + * nc_cell_methods: The value of the cell methods attribute to be split. + + Returns: + + * nc_cell_methods_matches: A list of the re.Match objects associated with + each parsed cell method + + Splitting is done based on words followed by colons outside of any brackets. + Validation of anything other than being laid out in the expected format is + left to the calling function. + """ + + # Find name candidates + name_start_inds = [] + for m in _CM_PARSE_NAME.finditer(nc_cell_methods): + name_start_inds.append(m.start()) + + # Remove those that fall inside brackets + bracket_depth = 0 + for ind, cha in enumerate(nc_cell_methods): + if cha == "(": + bracket_depth += 1 + elif cha == ")": + bracket_depth -= 1 + if bracket_depth < 0: + msg = ( + "Cell methods may be incorrectly parsed due to mismatched " + "brackets" + ) + warnings.warn(msg, UserWarning, stacklevel=2) + if bracket_depth > 0 and ind in name_start_inds: + name_start_inds.remove(ind) + + # List tuples of indices of starts and ends of the cell methods in the string + method_indices = [] + for ii in range(len(name_start_inds) - 1): + method_indices.append((name_start_inds[ii], name_start_inds[ii + 1])) + method_indices.append((name_start_inds[-1], len(nc_cell_methods))) + + # Index the string and match against each substring + nc_cell_methods_matches = [] + for start_ind, end_ind in method_indices: + nc_cell_method_str = nc_cell_methods[start_ind:end_ind] + nc_cell_method_match = _CM_PARSE.match(nc_cell_method_str.strip()) + if not nc_cell_method_match: + msg = ( + f"Failed to fully parse cell method string: {nc_cell_methods}" + ) + warnings.warn(msg, UserWarning, stacklevel=2) + continue + nc_cell_methods_matches.append(nc_cell_method_match) + + return nc_cell_methods_matches + + def parse_cell_methods(nc_cell_methods): """ Parse a CF cell_methods attribute string into a tuple of zero or @@ -226,7 +291,7 @@ def parse_cell_methods(nc_cell_methods): cell_methods = [] if nc_cell_methods is not None: - for m in _CM_PARSE.finditer(nc_cell_methods): + for m in _split_cell_methods(nc_cell_methods): d = m.groupdict() method = d[_CM_METHOD] method = method.strip() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py b/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py index 9c4fbf622b..bbde2d0a2d 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py @@ -41,6 +41,20 @@ def test_with_interval(self): res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) + def test_multiple_axes(self): + cell_method_strings = [ + "lat: lon: standard_deviation", + "lat: lon : standard_deviation", + "lat : lon: standard_deviation", + "lat : lon : standard_deviation", + ] + expected = ( + CellMethod(method="standard_deviation", coords=["lat", "lon"]), + ) + for cell_method_str in cell_method_strings: + res = parse_cell_methods(cell_method_str) + self.assertEqual(res, expected) + def test_multiple(self): cell_method_strings = [ "time: maximum (interval: 1 hr) time: mean (interval: 1 day)", @@ -85,6 +99,51 @@ def test_comment(self): res = parse_cell_methods(cell_method_str) self.assertEqual(res, expected) + def test_comment_brackets(self): + cell_method_strings = [ + "time: minimum within days (comment: 18h(day-1)-18h)", + "time : minimum within days (comment: 18h(day-1)-18h)", + ] + expected = ( + CellMethod( + method="minimum within days", + coords="time", + intervals=None, + comments="18h(day-1)-18h", + ), + ) + for cell_method_str in cell_method_strings: + res = parse_cell_methods(cell_method_str) + self.assertEqual(res, expected) + + def test_comment_bracket_mismatch_warning(self): + cell_method_strings = [ + "time: minimum within days (comment: 18h day-1)-18h)", + "time : minimum within days (comment: 18h day-1)-18h)", + ] + for cell_method_str in cell_method_strings: + with self.assertWarns( + UserWarning, + msg="Cell methods may be incorrectly parsed due to mismatched brackets", + ): + _ = parse_cell_methods(cell_method_str) + + def test_badly_formatted_warning(self): + cell_method_strings = [ + # "time: maximum (interval: 1 hr comment: first bit " + # "time: mean (interval: 1 day comment: second bit)", + "time: (interval: 1 hr comment: first bit) " + "time: mean (interval: 1 day comment: second bit)", + "time: maximum (interval: 1 hr comment: first bit) " + "time: (interval: 1 day comment: second bit)", + ] + for cell_method_str in cell_method_strings: + with self.assertWarns( + UserWarning, + msg=f"Failed to fully parse cell method string: {cell_method_str}", + ): + _ = parse_cell_methods(cell_method_str) + def test_portions_of_cells(self): cell_method_strings = [ "area: mean where sea_ice over sea", From 49a510b3dbf93ff28859934a69d5972a8eb8e84e Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Wed, 25 May 2022 14:56:06 +0100 Subject: [PATCH 120/319] Developer documentation on available testing conveniences (#4600) * Developer documentation on available testing conveniences * what's new * Update to be easily usable without code links working * Rebase --- .../contributing_testing_index.rst | 1 + docs/src/developers_guide/testing_tools.rst | 79 +++++++++++++++++++ docs/src/whatsnew/latest.rst | 4 + 3 files changed, 84 insertions(+) create mode 100755 docs/src/developers_guide/testing_tools.rst diff --git a/docs/src/developers_guide/contributing_testing_index.rst b/docs/src/developers_guide/contributing_testing_index.rst index 7c6eb1b3cc..8684a08f27 100644 --- a/docs/src/developers_guide/contributing_testing_index.rst +++ b/docs/src/developers_guide/contributing_testing_index.rst @@ -7,6 +7,7 @@ Testing :maxdepth: 3 contributing_testing + testing_tools contributing_graphics_tests imagehash_index contributing_running_tests diff --git a/docs/src/developers_guide/testing_tools.rst b/docs/src/developers_guide/testing_tools.rst new file mode 100755 index 0000000000..537af8d795 --- /dev/null +++ b/docs/src/developers_guide/testing_tools.rst @@ -0,0 +1,79 @@ +.. include:: ../common_links.inc + +.. _testing_tools: + +Testing tools +************* + +Iris has various internal convenience functions and utilities available to +support writing tests. Using these makes tests quicker and easier to write, and +also consistent with the rest of Iris (which makes it easier to work with the +code). Most of these conveniences are accessed through the +:class:`iris.tests.IrisTest` class, from +which Iris' test classes then inherit. + +.. tip:: + + All functions listed on this page are defined within + :mod:`iris.tests.__init__.py` as methods of + :class:`iris.tests.IrisTest_nometa` (which :class:`iris.tests.IrisTest` + inherits from). They can be accessed within a test using + ``self.exampleFunction``. + +Custom assertions +================= + +:class:`iris.tests.IrisTest` supports a variety of custom unittest-style +assertions, such as :meth:`~iris.tests.IrisTest_nometa.assertStringEqual`, +:meth:`~iris.tests.IrisTest_nometa.assertArrayEqual`, +:meth:`~iris.tests.IrisTest_nometa.assertArrayAlmostEqual`. + +Saving results +-------------- + +Some tests compare the generated output to the expected result contained in a +file. Custom assertions for this include +:meth:`~iris.tests.IrisTest_nometa.assertCMLApproxData` +:meth:`~iris.tests.IrisTest_nometa.assertCDL` +:meth:`~iris.tests.IrisTest_nometa.assertCML` and +:meth:`~iris.tests.IrisTest_nometa.assertTextFile`. See docstrings for more +information. + +.. note:: + + Sometimes code changes alter the results expected from a test containing the + above methods. These can be updated by removing the existing result files + and then running the file containing the test with a ``--create-missing`` + command line argument, or setting the ``IRIS_TEST_CREATE_MISSING`` + environment variable to anything non-zero. This will create the files rather + than erroring, allowing you to commit the updated results. + +Context managers +================ + +Capturing exceptions and logging +-------------------------------- + +:class:`iris.tests.IrisTest` includes several context managers that can be used +to make test code tidier and easier to read. These include +:meth:`~iris.tests.IrisTest_nometa.assertWarnsRegexp` and +:meth:`~iris.tests.IrisTest_nometa.assertLogs`. + +Temporary files +--------------- + +It's also possible to generate temporary files in a concise fashion with +:meth:`~iris.tests.IrisTest_nometa.temp_filename`. + +Patching +======== + +:meth:`~iris.tests.IrisTest_nometa.patch` is a wrapper around ``unittest.patch`` +that will be automatically cleaned up at the end of the test. + +Graphic tests +============= + +As a package capable of generating graphical outputs, Iris has utilities for +creating and updating graphical tests - see :ref:`testing.graphics` for more +information. \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 75d24697f5..7ecce0c5fb 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -181,6 +181,10 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ updated the "Load a Time Series of Data From the NEMO Model" gallery example. (:pull:`4741`) +#. `@wjbenfold`_ added developer documentation to highlight some of the + utilities offered by :class:`iris.IrisTest` and how to update CML and other + output files. (:issue:`4544`, :pull:`4600`) + 💼 Internal =========== From 684cd2de07214bfb2d4f08e3ff712bf21f358737 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Fri, 27 May 2022 16:00:41 +0100 Subject: [PATCH 121/319] Cubelist contain only cubes -- resurrected (#4767) * ensure cubelists only contain cubes * address __iadd__ and __setitem__ * __setitem__ tests * test for setting more than 1 item * Fix __setitem__ and Py2 tweaks * implement __setslice__ for Python2.7 * change exceptions to warnings * stickler * duck type check; move helpers outside class * blank lines * proposed: revert warnings to exceptions * remove stray extra 'test_fail' * pass sequences through __init__; _assert_is_cube * Replace CubeList init with new. Cube testing duck-type. * Review change. * Provide new-style whatsnew. * Fix whatsnew Co-authored-by: Ruth Comer --- docs/src/whatsnew/latest.rst | 5 + lib/iris/cube.py | 76 +++++++++++---- lib/iris/tests/unit/cube/test_CubeList.py | 110 ++++++++++++++++++++++ 3 files changed, 173 insertions(+), 18 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 7ecce0c5fb..86623ed204 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -111,6 +111,11 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ and `@rcomer`_ (reviewer) corrected the axis on which masking is applied when an aggregator adds a trailing dimension. (:pull:`4755`) +* `@rcomer`_ and `@pp-mo`_ ensured that all methods to create or modify a + :class:`iris.cube.CubeList` check that it only contains cubes. According to + code comments, this was supposedly already the case, but there were several bugs + and loopholes. + 💣 Incompatible Changes ======================= diff --git a/lib/iris/cube.py b/lib/iris/cube.py index f0a79d0965..e3bacf08fc 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -152,19 +152,13 @@ class CubeList(list): """ - def __new__(cls, list_of_cubes=None): - """Given a :class:`list` of cubes, return a CubeList instance.""" - cube_list = list.__new__(cls, list_of_cubes) - - # Check that all items in the incoming list are cubes. Note that this - # checking does not guarantee that a CubeList instance *always* has - # just cubes in its list as the append & __getitem__ methods have not - # been overridden. - if not all([isinstance(cube, Cube) for cube in cube_list]): - raise ValueError( - "All items in list_of_cubes must be Cube " "instances." - ) - return cube_list + def __init__(self, *args, **kwargs): + """Given an iterable of cubes, return a CubeList instance.""" + # Do whatever a list does, to initialise ourself "as a list" + super().__init__(*args, **kwargs) + # Check that all items in the list are cubes. + for cube in self: + self._assert_is_cube(cube) def __str__(self): """Runs short :meth:`Cube.summary` on every cube.""" @@ -182,13 +176,17 @@ def __repr__(self): """Runs repr on every cube.""" return "[%s]" % ",\n".join([repr(cube) for cube in self]) - def _repr_html_(self): - from iris.experimental.representation import CubeListRepresentation - - representer = CubeListRepresentation(self) - return representer.repr_html() + @staticmethod + def _assert_is_cube(obj): + if not hasattr(obj, "add_aux_coord"): + msg = ( + r"Object {obj} cannot be put in a cubelist, " + "as it is not a Cube." + ) + raise ValueError(msg) # TODO #370 Which operators need overloads? + def __add__(self, other): return CubeList(list.__add__(self, other)) @@ -210,6 +208,48 @@ def __getslice__(self, start, stop): result = CubeList(result) return result + def __iadd__(self, other_cubes): + """ + Add a sequence of cubes to the cubelist in place. + """ + return super(CubeList, self).__iadd__(CubeList(other_cubes)) + + def __setitem__(self, key, cube_or_sequence): + """Set self[key] to cube or sequence of cubes""" + if isinstance(key, int): + # should have single cube. + self._assert_is_cube(cube_or_sequence) + else: + # key is a slice (or exception will come from list method). + cube_or_sequence = CubeList(cube_or_sequence) + + super(CubeList, self).__setitem__(key, cube_or_sequence) + + def append(self, cube): + """ + Append a cube. + """ + self._assert_is_cube(cube) + super(CubeList, self).append(cube) + + def extend(self, other_cubes): + """ + Extend cubelist by appending the cubes contained in other_cubes. + + Args: + + * other_cubes: + A cubelist or other sequence of cubes. + """ + super(CubeList, self).extend(CubeList(other_cubes)) + + def insert(self, index, cube): + """ + Insert a cube before index. + """ + self._assert_is_cube(cube) + super(CubeList, self).insert(index, cube) + def xml(self, checksum=False, order=True, byteorder=True): """Return a string of the XML that this list of cubes represents.""" diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py index 50c1e553bc..771b214fe4 100644 --- a/lib/iris/tests/unit/cube/test_CubeList.py +++ b/lib/iris/tests/unit/cube/test_CubeList.py @@ -10,6 +10,7 @@ import iris.tests as tests # isort:skip import collections +import copy from unittest import mock from cf_units import Unit @@ -23,6 +24,26 @@ from iris.fileformats.pp import STASH import iris.tests.stock +NOT_CUBE_MSG = "cannot be put in a cubelist, as it is not a Cube." +NON_ITERABLE_MSG = "object is not iterable" + + +class Test_append(tests.IrisTest): + def setUp(self): + self.cubelist = iris.cube.CubeList() + self.cube1 = iris.cube.Cube(1, long_name="foo") + self.cube2 = iris.cube.Cube(1, long_name="bar") + + def test_pass(self): + self.cubelist.append(self.cube1) + self.assertEqual(self.cubelist[-1], self.cube1) + self.cubelist.append(self.cube2) + self.assertEqual(self.cubelist[-1], self.cube2) + + def test_fail(self): + with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + self.cubelist.append(None) + class Test_concatenate_cube(tests.IrisTest): def setUp(self): @@ -70,6 +91,29 @@ def test_empty(self): CubeList([]).concatenate_cube() +class Test_extend(tests.IrisTest): + def setUp(self): + self.cube1 = iris.cube.Cube(1, long_name="foo") + self.cube2 = iris.cube.Cube(1, long_name="bar") + self.cubelist1 = iris.cube.CubeList([self.cube1]) + self.cubelist2 = iris.cube.CubeList([self.cube2]) + + def test_pass(self): + cubelist = copy.copy(self.cubelist1) + cubelist.extend(self.cubelist2) + self.assertEqual(cubelist, self.cubelist1 + self.cubelist2) + cubelist.extend([self.cube2]) + self.assertEqual(cubelist[-1], self.cube2) + + def test_fail(self): + with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + self.cubelist1.extend(self.cube1) + with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + self.cubelist1.extend(None) + with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + self.cubelist1.extend(range(3)) + + class Test_extract_overlapping(tests.IrisTest): def setUp(self): shape = (6, 14, 19) @@ -130,6 +174,44 @@ def test_different_orders(self): self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) +class Test_iadd(tests.IrisTest): + def setUp(self): + self.cube1 = iris.cube.Cube(1, long_name="foo") + self.cube2 = iris.cube.Cube(1, long_name="bar") + self.cubelist1 = iris.cube.CubeList([self.cube1]) + self.cubelist2 = iris.cube.CubeList([self.cube2]) + + def test_pass(self): + cubelist = copy.copy(self.cubelist1) + cubelist += self.cubelist2 + self.assertEqual(cubelist, self.cubelist1 + self.cubelist2) + cubelist += [self.cube2] + self.assertEqual(cubelist[-1], self.cube2) + + def test_fail(self): + with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + self.cubelist1 += self.cube1 + with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + self.cubelist1 += 1.0 + with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + self.cubelist1 += range(3) + + +class Test_insert(tests.IrisTest): + def setUp(self): + self.cube1 = iris.cube.Cube(1, long_name="foo") + self.cube2 = iris.cube.Cube(1, long_name="bar") + self.cubelist = iris.cube.CubeList([self.cube1] * 3) + + def test_pass(self): + self.cubelist.insert(1, self.cube2) + self.assertEqual(self.cubelist[1], self.cube2) + + def test_fail(self): + with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + self.cubelist.insert(0, None) + + class Test_merge_cube(tests.IrisTest): def setUp(self): self.cube1 = Cube([1, 2, 3], "air_temperature", units="K") @@ -274,6 +356,34 @@ def test_combination_with_extra_triple(self): self.assertCML(cube, checksum=False) +class Test_setitem(tests.IrisTest): + def setUp(self): + self.cube1 = iris.cube.Cube(1, long_name="foo") + self.cube2 = iris.cube.Cube(1, long_name="bar") + self.cube3 = iris.cube.Cube(1, long_name="boo") + self.cubelist = iris.cube.CubeList([self.cube1] * 3) + + def test_pass(self): + self.cubelist[1] = self.cube2 + self.assertEqual(self.cubelist[1], self.cube2) + self.cubelist[:2] = (self.cube2, self.cube3) + self.assertEqual( + self.cubelist, + iris.cube.CubeList([self.cube2, self.cube3, self.cube1]), + ) + + def test_fail(self): + with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + self.cubelist[0] = None + with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): + self.cubelist[0:2] = [self.cube3, None] + + with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + self.cubelist[:1] = 2.5 + with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): + self.cubelist[:1] = self.cube1 + + class Test_xml(tests.IrisTest): def setUp(self): self.cubes = CubeList([Cube(np.arange(3)), Cube(np.arange(3))]) From b462ddd13590cd64f859e002f89ca4f40a537a25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 May 2022 10:53:47 +0100 Subject: [PATCH 122/319] Updated environment lockfiles (#4775) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 37 ++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 13d48983fc..2959abcd3e 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -29,9 +29,7 @@ https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.2-h9c3ff4c_0.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed -https://conda.anaconda.org/conda-forge/linux-64/jbig-2.1-h7f98852_2003.tar.bz2#1aa0cee79792fa97b7ff4545110b60bf https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 -https://conda.anaconda.org/conda-forge/linux-64/json-c-0.15-h98cffda_0.tar.bz2#f32d45a88e7462be446824654dbcf4a4 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c @@ -46,9 +44,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_2.tar.bz2#efda4f2be23a2643b56bb67c91bd4b69 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.11-h166bdaf_1014.tar.bz2#757138ba3ddc6777b82e91d9ff62e7b9 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_0.tar.bz2#6c06394781511bdc3c37512ed7b16730 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 @@ -71,10 +70,10 @@ https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.t https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-14_linux64_openblas.tar.bz2#fb31fbbde682414550bbe15e3964420f https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.51-h166bdaf_1.tar.bz2#875e5931fb2faa6dacb4c13672630ae3 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 -https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.3-he0ac6c6_0.tar.bz2#8ab9f9c0fc641b53c5f87368e41af18d +https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.4-he0ac6c6_0.tar.bz2#0fb26f5cfdc80bdb2a5c5b70b83fd392 https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 @@ -82,20 +81,20 @@ https://conda.anaconda.org/conda-forge/linux-64/readline-8.1-h46c0cb4_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.11-h166bdaf_1014.tar.bz2#def3b82d1a03aa695bb38ac1dd072ff2 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_0.tar.bz2#c6b89248778ae9a05320f19eb212ad90 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_1.tar.bz2#3db63b53bb194dbaa7dc3d8833e98da2 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-14_linux64_openblas.tar.bz2#1b41ea4c32014d878e84de4e5690df7a -https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.3-default_h3a83d3e_0.tar.bz2#1a571eb098b894a66642cd8b638f5f1d +https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.4-default_h3a83d3e_0.tar.bz2#454c8f1a35979adc9e84dbfb0b8ce0d1 https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-14_linux64_openblas.tar.bz2#13367ebd0243a949cee7564b13c3cd42 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h542a066_3.tar.bz2#1a0efb4dfd880b0376da8e1ba39fa838 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h0fcbabc_4.tar.bz2#2cdb3944029c2cb99cbe981b182a2e9a https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 @@ -108,14 +107,14 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.b https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.2-hd4edc92_1.tar.bz2#c16a9b2773180a641583f1d3690e3ff6 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.3-default_h2e3cab8_0.tar.bz2#2343b7c11c031c5415b28641eb81275d +https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.4-default_h2e3cab8_0.tar.bz2#7a9b095444e89e8d5d2bc0afb5119615 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.3-hd77ab85_0.tar.bz2#13980beea7c55e6b68db1349f25bd454 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/nss-3.77-h2350873_0.tar.bz2#260617b7829b86e9e939b01c9cad1526 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb @@ -170,21 +169,21 @@ https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.5.18.1-py38h578d9bd_0.tar.bz2#429a49d95358a078211aad97c4fc286c https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.15.2-py38h578d9bd_3.tar.bz2#e3165d234a00a89a7ce018283963f3f3 -https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.3-py38h578d9bd_1.tar.bz2#21862e22238907d485e460f9c2e9ae4d +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 +https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.2-py38h43d8883_1.tar.bz2#34c284cb94bd8c5118ccfe6d6fc3dd3f https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.3-py38h99721a1_2.tar.bz2#e6ed43e96f813b184fe9bb677476e56d +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.4-py38h99721a1_0.tar.bz2#fc4f99d9d9296861d09d487c7c32069f https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.0-py38h0a891b7_1.tar.bz2#92045570d1da14b3f90621c54f8afb12 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-hb166930_4.tar.bz2#7c165f1b39385b9fee1f279005ee512e +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-hbc9ff1d_7.tar.bz2#866bdb7e78da047f0fac7b6b8cd03b79 https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 @@ -196,15 +195,15 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30_0.tar.bz2#bcc387154aae535f8b4f84822621b5f7 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.5.0-pyhd8ed1ab_0.tar.bz2#3aef8ad6f9af56117e959a53cb9b9fd1 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.5.2-pyhd8ed1ab_0.tar.bz2#7ec2aa5c3b67b07e24c9304e07831fd5 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.2.1-hf9f4e7c_0.tar.bz2#842ac05681ac1ed099fad99bb717c641 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.3.0-hf9f4e7c_0.tar.bz2#2a9c6660562d7e3fdeda0f0159e1046d https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_0.tar.bz2#2d307d13155817b5f5da36cc69358fe6 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_1.tar.bz2#0cdb1150994e0c449b25d6f92b69eda2 -https://conda.anaconda.org/conda-forge/noarch/pip-22.0.4-pyhd8ed1ab_0.tar.bz2#b1239ce8ef2a1eec485c398a683c5bff +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_2.tar.bz2#64cca81aa93bbf0a5ab3c6d5d956b976 +https://conda.anaconda.org/conda-forge/noarch/pip-22.1.1-pyhd8ed1ab_0.tar.bz2#6affaf2f490f479c73d819735f80a104 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 From 6ea8af9be303bc9ff0113226fa9ee26c80188f82 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 6 Jun 2022 11:51:46 +0100 Subject: [PATCH 123/319] Redirect whatsnew to latest (#4727) * Redirect whatsnew * Get the top bar working too * Clean build with right links * Update release docs * Review fixes * update section name for clarity --- .../src/developers_guide/contributing_codebase_index.rst | 4 ++-- .../developers_guide/contributing_getting_involved.rst | 2 +- docs/src/developers_guide/release.rst | 5 +++++ docs/src/index.rst | 4 ++-- docs/src/whatsnew/latest.rst | 2 +- docs/src/whatsnew/{index.rst => whatsnew.rst} | 9 +++++---- 6 files changed, 16 insertions(+), 10 deletions(-) rename docs/src/whatsnew/{index.rst => whatsnew.rst} (76%) mode change 100644 => 100755 diff --git a/docs/src/developers_guide/contributing_codebase_index.rst b/docs/src/developers_guide/contributing_codebase_index.rst index 88986c0c7a..b59a196ff0 100644 --- a/docs/src/developers_guide/contributing_codebase_index.rst +++ b/docs/src/developers_guide/contributing_codebase_index.rst @@ -1,7 +1,7 @@ .. _contributing.documentation.codebase: -Contributing to the Code Base -============================= +Working with the Code Base +========================== .. toctree:: :maxdepth: 3 diff --git a/docs/src/developers_guide/contributing_getting_involved.rst b/docs/src/developers_guide/contributing_getting_involved.rst index f4e677cea2..535b3d13bc 100644 --- a/docs/src/developers_guide/contributing_getting_involved.rst +++ b/docs/src/developers_guide/contributing_getting_involved.rst @@ -59,7 +59,7 @@ If you are new to using GitHub we recommend reading the :hidden: ../generated/api/iris - ../whatsnew/index + ../whatsnew/whatsnew ../techpapers/index ../copyright ../voted_issues diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index a918253ef7..dd32381a27 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -221,6 +221,8 @@ Release Steps * Use ``git`` to rename ``docs/src/whatsnew/latest.rst`` to the release version file ``v1.9.rst`` + * Update ``docs/src/whatsnews/whatsnew.rst`` to rename ``latest.rst`` in the + include statement and toctree. * Use ``git`` to delete the ``docs/src/whatsnew/latest.rst.template`` file * In ``v1.9.rst`` remove the ``[unreleased]`` caption from the page title. Note that, the Iris version and release date are updated automatically @@ -255,6 +257,9 @@ Post Release Steps `Read The Docs`_ to ensure that the appropriate versions are ``Active`` and/or ``Hidden``. To do this ``Edit`` the appropriate version e.g., see `Editing v3.0.0rc0`_ (must be logged into Read the Docs). +#. Make a new ``latest.rst`` from ``latest.rst.template`` and update the include + statement and the toctree in ``whatsnew.rst`` to point at the new + ``latest.rst``. #. Merge back to ``main`` diff --git a/docs/src/index.rst b/docs/src/index.rst index fb0e93b1ae..274892f082 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -139,11 +139,11 @@ The legacy support resources: .. toctree:: - :caption: Developers Guide + :caption: What's New in Iris :maxdepth: 1 :name: whats_new_index :hidden: - whatsnew/index + whatsnew/whatsnew .. todolist:: \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 86623ed204..ba76a6fcd0 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -218,4 +218,4 @@ This document explains the changes made to Iris for this release Whatsnew resources in alphabetical order: .. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries -.. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html +.. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html \ No newline at end of file diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/whatsnew.rst old mode 100644 new mode 100755 similarity index 76% rename from docs/src/whatsnew/index.rst rename to docs/src/whatsnew/whatsnew.rst index 949d727a99..78c0134463 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/whatsnew.rst @@ -1,14 +1,15 @@ +.. include:: ../common_links.inc + .. _iris_whatsnew: What's New in Iris -****************** - -These "What's new" pages describe the important changes between major -Iris versions. +------------------ +.. include:: latest.rst .. toctree:: :maxdepth: 1 + :hidden: latest.rst 3.2.rst From bd7f414982bc1c6bef7948fb851845b184c7944f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 12:23:41 +0100 Subject: [PATCH 124/319] Bump peter-evans/create-pull-request from 4.0.3 to 4.0.4 (#4783) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.0.3 to 4.0.4. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/f094b77505fb89581e68a1163fbd2fffece39da1...923ad837f191474af6b1721408744feb989a4c27) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 98cd977804..1f41c8c265 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -82,7 +82,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@f094b77505fb89581e68a1163fbd2fffece39da1 + uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 with: commit-message: Updated environment lockfiles committer: "Lockfile bot " From d05c0d147e8a3e07b9e80ac53fbe2d2e61a10057 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 13:25:25 +0100 Subject: [PATCH 125/319] Updated environment lockfiles (#4782) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 2959abcd3e..007e9502e9 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -128,7 +128,7 @@ https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#b https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.0-pyhd8ed1ab_0.tar.bz2#cfb94034fcdfc80b61164878dc677a8d +https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.2-hcf0ee16_0.tar.bz2#79d7fca692d224dc29a72bda90f78a7b @@ -203,13 +203,13 @@ https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007. https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_2.tar.bz2#64cca81aa93bbf0a5ab3c6d5d956b976 -https://conda.anaconda.org/conda-forge/noarch/pip-22.1.1-pyhd8ed1ab_0.tar.bz2#6affaf2f490f479c73d819735f80a104 +https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.3-hf97cb25_1.tar.bz2#79853477ea006ccccb7a39c2d33f51b9 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.3-hf97cb25_2.tar.bz2#eea8ff5c3ca4715b5ea821310d6b91db https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38h1ee437e_0.tar.bz2#a0a8bc19d491ec659a534c9a11cf74a0 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h97f7145_1.tar.bz2#e0ada9ddff3a730b7dfc976a5ee3f420 https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py38h709712a_2.tar.bz2#8ff0cdb63842c29388a34a6af7b5ce6f @@ -223,7 +223,7 @@ https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bf https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py38hfa26641_0.tar.bz2#d69f44759f54d1b312d097cd6cf80588 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py38hfa26641_1.tar.bz2#40f4eeb2cb0f0ab25d0640f5f7a34de8 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h51d8e34_4.tar.bz2#9f23c80d08456c02ab284f974719b013 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 @@ -231,10 +231,10 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py38hfa26641_0.tar.bz2#f151689583a3c94fd4bcf33cdbdd8fec +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py38h7492b6b_1.tar.bz2#7a78a346ffd3297790b81cce35d32083 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-3.0.0-h5abf519_1.tar.bz2#fcaf13b2713335ff871ba551d5bda679 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c https://conda.anaconda.org/conda-forge/noarch/requests-2.27.1-pyhd8ed1ab_0.tar.bz2#7c1c427246b057b8fa97200ecdb2ed62 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 From f4fb6fa32ab77e7003a441360a127478bc9d7c46 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 6 Jun 2022 17:40:14 +0100 Subject: [PATCH 126/319] Fix whatsnew (#4784) * Fix whatsnew * Instead of changing 3.0.rst, rename 'whatsnew.rst' to 'index.rst' * Missed one --- docs/src/developers_guide/contributing_getting_involved.rst | 2 +- docs/src/developers_guide/release.rst | 4 ++-- docs/src/index.rst | 2 +- docs/src/whatsnew/{whatsnew.rst => index.rst} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename docs/src/whatsnew/{whatsnew.rst => index.rst} (100%) mode change 100755 => 100644 diff --git a/docs/src/developers_guide/contributing_getting_involved.rst b/docs/src/developers_guide/contributing_getting_involved.rst index 535b3d13bc..f4e677cea2 100644 --- a/docs/src/developers_guide/contributing_getting_involved.rst +++ b/docs/src/developers_guide/contributing_getting_involved.rst @@ -59,7 +59,7 @@ If you are new to using GitHub we recommend reading the :hidden: ../generated/api/iris - ../whatsnew/whatsnew + ../whatsnew/index ../techpapers/index ../copyright ../voted_issues diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index dd32381a27..b2de9106a2 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -221,7 +221,7 @@ Release Steps * Use ``git`` to rename ``docs/src/whatsnew/latest.rst`` to the release version file ``v1.9.rst`` - * Update ``docs/src/whatsnews/whatsnew.rst`` to rename ``latest.rst`` in the + * Update ``docs/src/whatsnews/index.rst`` to rename ``latest.rst`` in the include statement and toctree. * Use ``git`` to delete the ``docs/src/whatsnew/latest.rst.template`` file * In ``v1.9.rst`` remove the ``[unreleased]`` caption from the page title. @@ -258,7 +258,7 @@ Post Release Steps and/or ``Hidden``. To do this ``Edit`` the appropriate version e.g., see `Editing v3.0.0rc0`_ (must be logged into Read the Docs). #. Make a new ``latest.rst`` from ``latest.rst.template`` and update the include - statement and the toctree in ``whatsnew.rst`` to point at the new + statement and the toctree in ``index.rst`` to point at the new ``latest.rst``. #. Merge back to ``main`` diff --git a/docs/src/index.rst b/docs/src/index.rst index 274892f082..b9f7faaa03 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -144,6 +144,6 @@ The legacy support resources: :name: whats_new_index :hidden: - whatsnew/whatsnew + whatsnew/index .. todolist:: \ No newline at end of file diff --git a/docs/src/whatsnew/whatsnew.rst b/docs/src/whatsnew/index.rst old mode 100755 new mode 100644 similarity index 100% rename from docs/src/whatsnew/whatsnew.rst rename to docs/src/whatsnew/index.rst From 149e2d1814b82f13b62ca22b38ac9bb5ef46e023 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Tue, 7 Jun 2022 17:23:06 +0100 Subject: [PATCH 127/319] `PolarStereographic` implementation (#4773) * PolarStereographic implementation * Test data version * What's new * Accept suggestion to lib/iris/coord_systems.py Co-authored-by: Patrick Peglar * Permit Stereographic saving * Update what's new * Review fixes Co-authored-by: Patrick Peglar --- .cirrus.yml | 2 +- docs/src/whatsnew/latest.rst | 4 + lib/iris/coord_systems.py | 138 ++++++++-- .../fileformats/_nc_load_rules/actions.py | 6 +- .../fileformats/_nc_load_rules/helpers.py | 72 ++++- lib/iris/fileformats/netcdf.py | 51 ++-- .../coord_systems/PolarStereographic.xml | 2 + .../PolarStereographicScaleFactor.xml | 2 + .../PolarStereographicStandardParallel.xml | 2 + .../results/coord_systems/Stereographic.xml | 2 +- .../tests/results/netcdf/netcdf_polar.cml | 45 ++++ .../tests/results/netcdf/netcdf_stereo.cml | 4 +- lib/iris/tests/test_coordsystem.py | 90 ------- lib/iris/tests/test_netcdf.py | 10 + .../coord_systems/test_PolarStereographic.py | 251 ++++++++++++++++++ .../unit/coord_systems/test_Stereographic.py | 170 +++++++++++- .../actions/test__grid_mappings.py | 45 ++-- ...d_polar_stereographic_coordinate_system.py | 150 +++++++++++ ...upported_polar_stereographic_parameters.py | 242 +++++++++++++++++ ..._has_supported_stereographic_parameters.py | 75 ------ 20 files changed, 1116 insertions(+), 247 deletions(-) create mode 100644 lib/iris/tests/results/coord_systems/PolarStereographic.xml create mode 100644 lib/iris/tests/results/coord_systems/PolarStereographicScaleFactor.xml create mode 100644 lib/iris/tests/results/coord_systems/PolarStereographicStandardParallel.xml create mode 100644 lib/iris/tests/results/netcdf/netcdf_polar.cml create mode 100755 lib/iris/tests/unit/coord_systems/test_PolarStereographic.py create mode 100755 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py create mode 100755 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_stereographic_parameters.py diff --git a/.cirrus.yml b/.cirrus.yml index ea1a978b65..cbc8a146ef 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -38,7 +38,7 @@ env: # Conda packages to be installed. CONDA_CACHE_PACKAGES: "nox pip" # Git commit hash for iris test data. - IRIS_TEST_DATA_VERSION: "2.8" + IRIS_TEST_DATA_VERSION: "2.9" # Base directory for the iris-test-data. IRIS_TEST_DATA_DIR: ${HOME}/iris-test-data diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index ba76a6fcd0..e7edafb58d 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -64,6 +64,10 @@ This document explains the changes made to Iris for this release preserving the time of year. (:issue:`1422`, :issue:`4098`, :issue:`4665`, :pull:`4723`) +#. `@wjbenfold`_ and `@pp-mo`_ (reviewer) implemented the + :class:`~iris.coord_systems.PolarStereographic` CRS. (:issue:`4770`, + :pull:`4773`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 0bd7145a5f..802571925e 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -1061,32 +1061,39 @@ def __init__( false_northing=None, true_scale_lat=None, ellipsoid=None, + scale_factor_at_projection_origin=None, ): """ Constructs a Stereographic coord system. - Args: + Parameters + ---------- - * central_lat: + central_lat : float The latitude of the pole. - * central_lon: + central_lon : float The central longitude, which aligns with the y axis. - Kwargs: - - * false_easting: - X offset from planar origin in metres. Defaults to 0.0 . + false_easting : float, optional + X offset from planar origin in metres. - * false_northing: - Y offset from planar origin in metres. Defaults to 0.0 . + false_northing : float, optional + Y offset from planar origin in metres. - * true_scale_lat: + true_scale_lat : float, optional Latitude of true scale. - * ellipsoid (:class:`GeogCS`): + scale_factor_at_projection_origin : float, optional + Scale factor at the origin of the projection + + ellipsoid : :class:`GeogCS`, optional If given, defines the ellipsoid. + Notes + ----- + It is only valid to provide one of true_scale_lat and scale_factor_at_projection_origin + """ #: True latitude of planar origin in degrees. @@ -1105,27 +1112,42 @@ def __init__( self.true_scale_lat = _arg_default( true_scale_lat, None, cast_as=_float_or_None ) - # N.B. the way we use this parameter, we need it to default to None, + #: Scale factor at projection origin. + self.scale_factor_at_projection_origin = _arg_default( + scale_factor_at_projection_origin, None, cast_as=_float_or_None + ) + # N.B. the way we use these parameters, we need them to default to None, # and *not* to 0.0 . + if ( + self.true_scale_lat is not None + and self.scale_factor_at_projection_origin is not None + ): + raise ValueError( + "It does not make sense to provide both " + '"scale_factor_at_projection_origin" and "true_scale_latitude". ' + ) + #: Ellipsoid definition (:class:`GeogCS` or None). self.ellipsoid = ellipsoid - def __repr__(self): - return ( - "Stereographic(central_lat={!r}, central_lon={!r}, " - "false_easting={!r}, false_northing={!r}, " - "true_scale_lat={!r}, " - "ellipsoid={!r})".format( - self.central_lat, - self.central_lon, - self.false_easting, - self.false_northing, - self.true_scale_lat, - self.ellipsoid, + def _repr_attributes(self): + if self.scale_factor_at_projection_origin is None: + scale_info = "true_scale_lat={!r}, ".format(self.true_scale_lat) + else: + scale_info = "scale_factor_at_projection_origin={!r}, ".format( + self.scale_factor_at_projection_origin ) + return ( + f"(central_lat={self.central_lat}, central_lon={self.central_lon}, " + f"false_easting={self.false_easting}, false_northing={self.false_northing}, " + f"{scale_info}" + f"ellipsoid={self.ellipsoid})" ) + def __repr__(self): + return "Stereographic" + self._repr_attributes() + def as_cartopy_crs(self): globe = self._ellipsoid_to_globe(self.ellipsoid, ccrs.Globe()) @@ -1135,6 +1157,7 @@ def as_cartopy_crs(self): self.false_easting, self.false_northing, self.true_scale_lat, + self.scale_factor_at_projection_origin, globe=globe, ) @@ -1142,6 +1165,73 @@ def as_cartopy_projection(self): return self.as_cartopy_crs() +class PolarStereographic(Stereographic): + """ + A subclass of the stereographic map projection centred on a pole. + + """ + + grid_mapping_name = "polar_stereographic" + + def __init__( + self, + central_lat, + central_lon, + false_easting=None, + false_northing=None, + true_scale_lat=None, + scale_factor_at_projection_origin=None, + ellipsoid=None, + ): + """ + Construct a Polar Stereographic coord system. + + Parameters + ---------- + + central_lat : {90, -90} + The latitude of the pole. + + central_lon : float + The central longitude, which aligns with the y axis. + + false_easting : float, optional + X offset from planar origin in metres. + + false_northing : float, optional + Y offset from planar origin in metres. + + true_scale_lat : float, optional + Latitude of true scale. + + scale_factor_at_projection_origin : float, optional + Scale factor at the origin of the projection + + ellipsoid : :class:`GeogCS`, optional + If given, defines the ellipsoid. + + Notes + ----- + It is only valid to provide at most one of `true_scale_lat` and + `scale_factor_at_projection_origin`. + + + """ + + super().__init__( + central_lat=central_lat, + central_lon=central_lon, + false_easting=false_easting, + false_northing=false_northing, + true_scale_lat=true_scale_lat, + scale_factor_at_projection_origin=scale_factor_at_projection_origin, + ellipsoid=ellipsoid, + ) + + def __repr__(self): + return "PolarStereographic" + self._repr_attributes() + + class LambertConformal(CoordSystem): """ A coordinate system in the Lambert Conformal conic projection. diff --git a/lib/iris/fileformats/_nc_load_rules/actions.py b/lib/iris/fileformats/_nc_load_rules/actions.py index 4c5184deb1..09237d3f11 100644 --- a/lib/iris/fileformats/_nc_load_rules/actions.py +++ b/lib/iris/fileformats/_nc_load_rules/actions.py @@ -110,9 +110,13 @@ def action_default(engine): hh.build_transverse_mercator_coordinate_system, ), hh.CF_GRID_MAPPING_STEREO: ( - hh.has_supported_stereographic_parameters, + None, hh.build_stereographic_coordinate_system, ), + hh.CF_GRID_MAPPING_POLAR: ( + hh.has_supported_polar_stereographic_parameters, + hh.build_polar_stereographic_coordinate_system, + ), hh.CF_GRID_MAPPING_LAMBERT_CONFORMAL: ( None, hh.build_lambert_conformal_coordinate_system, diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index 34eecdd310..e94fe99185 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -145,6 +145,7 @@ CF_ATTR_GRID_SEMI_MINOR_AXIS = "semi_minor_axis" CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN = "latitude_of_projection_origin" CF_ATTR_GRID_LON_OF_PROJ_ORIGIN = "longitude_of_projection_origin" +CF_ATTR_GRID_STRAIGHT_VERT_LON = "straight_vertical_longitude_from_pole" CF_ATTR_GRID_STANDARD_PARALLEL = "standard_parallel" CF_ATTR_GRID_FALSE_EASTING = "false_easting" CF_ATTR_GRID_FALSE_NORTHING = "false_northing" @@ -418,8 +419,6 @@ def build_stereographic_coordinate_system(engine, cf_grid_var): ) false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None) false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None) - # Iris currently only supports Stereographic projections with a scale - # factor of 1.0. This is checked elsewhere. cs = iris.coord_systems.Stereographic( latitude_of_projection_origin, @@ -433,6 +432,42 @@ def build_stereographic_coordinate_system(engine, cf_grid_var): return cs +################################################################################ +def build_polar_stereographic_coordinate_system(engine, cf_grid_var): + """ + Create a polar stereographic coordinate system from the CF-netCDF + grid mapping variable. + + """ + ellipsoid = _get_ellipsoid(cf_grid_var) + + latitude_of_projection_origin = getattr( + cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None + ) + longitude_of_projection_origin = getattr( + cf_grid_var, CF_ATTR_GRID_STRAIGHT_VERT_LON, None + ) + true_scale_lat = getattr(cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None) + scale_factor_at_projection_origin = getattr( + cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None + ) + + false_easting = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_EASTING, None) + false_northing = getattr(cf_grid_var, CF_ATTR_GRID_FALSE_NORTHING, None) + + cs = iris.coord_systems.PolarStereographic( + latitude_of_projection_origin, + longitude_of_projection_origin, + false_easting, + false_northing, + true_scale_lat, + scale_factor_at_projection_origin, + ellipsoid=ellipsoid, + ) + + return cs + + ################################################################################ def build_mercator_coordinate_system(engine, cf_grid_var): """ @@ -1239,24 +1274,45 @@ def has_supported_mercator_parameters(engine, cf_name): ################################################################################ -def has_supported_stereographic_parameters(engine, cf_name): - """Determine whether the CF grid mapping variable has a value of 1.0 - for the scale_factor_at_projection_origin attribute.""" +def has_supported_polar_stereographic_parameters(engine, cf_name): + """Determine whether the CF grid mapping variable has the supported + values for the parameters of the Polar Stereographic projection.""" is_valid = True cf_grid_var = engine.cf_var.cf_group[cf_name] + latitude_of_projection_origin = getattr( + cf_grid_var, CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN, None + ) + + standard_parallel = getattr( + cf_grid_var, CF_ATTR_GRID_STANDARD_PARALLEL, None + ) scale_factor_at_projection_origin = getattr( cf_grid_var, CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN, None ) + if ( + latitude_of_projection_origin != 90 + and latitude_of_projection_origin != -90 + ): + warnings.warn('"latitude_of_projection_origin" must be +90 or -90.') + is_valid = False + if ( scale_factor_at_projection_origin is not None - and scale_factor_at_projection_origin != 1 + and standard_parallel is not None ): warnings.warn( - "Scale factors other than 1.0 not yet supported for " - "stereographic projections" + "It does not make sense to provide both " + '"scale_factor_at_projection_origin" and "standard_parallel".' + ) + is_valid = False + + if scale_factor_at_projection_origin is None and standard_parallel is None: + warnings.warn( + 'One of "scale_factor_at_projection_origin" and ' + '"standard_parallel" is required.' ) is_valid = False diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf.py index 7a0e4e655d..6a7b37a1cc 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf.py @@ -2685,27 +2685,46 @@ def add_ellipsoid(ellipsoid): cf_var_grid.false_easting = cs.false_easting cf_var_grid.false_northing = cs.false_northing - # stereo - elif isinstance(cs, iris.coord_systems.Stereographic): + # polar stereo (have to do this before Stereographic because it subclasses it) + elif isinstance(cs, iris.coord_systems.PolarStereographic): + if cs.ellipsoid: + add_ellipsoid(cs.ellipsoid) + cf_var_grid.latitude_of_projection_origin = cs.central_lat + cf_var_grid.straight_vertical_longitude_from_pole = ( + cs.central_lon + ) + cf_var_grid.false_easting = cs.false_easting + cf_var_grid.false_northing = cs.false_northing + # Only one of these should be set if cs.true_scale_lat is not None: - warnings.warn( - "Stereographic coordinate systems with " - "true scale latitude specified are not " - "yet handled" + cf_var_grid.true_scale_lat = cs.true_scale_lat + elif cs.scale_factor_at_projection_origin is not None: + cf_var_grid.scale_factor_at_projection_origin = ( + cs.scale_factor_at_projection_origin ) else: - if cs.ellipsoid: - add_ellipsoid(cs.ellipsoid) - cf_var_grid.longitude_of_projection_origin = ( - cs.central_lon + cf_var_grid.scale_factor_at_projection_origin = 1.0 + + # stereo + elif isinstance(cs, iris.coord_systems.Stereographic): + if cs.ellipsoid: + add_ellipsoid(cs.ellipsoid) + cf_var_grid.longitude_of_projection_origin = cs.central_lon + cf_var_grid.latitude_of_projection_origin = cs.central_lat + cf_var_grid.false_easting = cs.false_easting + cf_var_grid.false_northing = cs.false_northing + # Only one of these should be set + if cs.true_scale_lat is not None: + msg = ( + "It is not valid CF to save a true_scale_lat for " + "a Stereographic grid mapping." ) - cf_var_grid.latitude_of_projection_origin = ( - cs.central_lat + raise ValueError(msg) + elif cs.scale_factor_at_projection_origin is not None: + cf_var_grid.scale_factor_at_projection_origin = ( + cs.scale_factor_at_projection_origin ) - cf_var_grid.false_easting = cs.false_easting - cf_var_grid.false_northing = cs.false_northing - # The Stereographic class has an implicit scale - # factor + else: cf_var_grid.scale_factor_at_projection_origin = 1.0 # osgb (a specific tmerc) diff --git a/lib/iris/tests/results/coord_systems/PolarStereographic.xml b/lib/iris/tests/results/coord_systems/PolarStereographic.xml new file mode 100644 index 0000000000..85abfc892f --- /dev/null +++ b/lib/iris/tests/results/coord_systems/PolarStereographic.xml @@ -0,0 +1,2 @@ + + diff --git a/lib/iris/tests/results/coord_systems/PolarStereographicScaleFactor.xml b/lib/iris/tests/results/coord_systems/PolarStereographicScaleFactor.xml new file mode 100644 index 0000000000..2fc1554cd7 --- /dev/null +++ b/lib/iris/tests/results/coord_systems/PolarStereographicScaleFactor.xml @@ -0,0 +1,2 @@ + + diff --git a/lib/iris/tests/results/coord_systems/PolarStereographicStandardParallel.xml b/lib/iris/tests/results/coord_systems/PolarStereographicStandardParallel.xml new file mode 100644 index 0000000000..de7b5f902c --- /dev/null +++ b/lib/iris/tests/results/coord_systems/PolarStereographicStandardParallel.xml @@ -0,0 +1,2 @@ + + diff --git a/lib/iris/tests/results/coord_systems/Stereographic.xml b/lib/iris/tests/results/coord_systems/Stereographic.xml index bb12cd94cc..fb338a8e4d 100644 --- a/lib/iris/tests/results/coord_systems/Stereographic.xml +++ b/lib/iris/tests/results/coord_systems/Stereographic.xml @@ -1,2 +1,2 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_polar.cml b/lib/iris/tests/results/netcdf/netcdf_polar.cml new file mode 100644 index 0000000000..ef76a61699 --- /dev/null +++ b/lib/iris/tests/results/netcdf/netcdf_polar.cml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/netcdf/netcdf_stereo.cml b/lib/iris/tests/results/netcdf/netcdf_stereo.cml index b07304cd62..c2d0bab03f 100644 --- a/lib/iris/tests/results/netcdf/netcdf_stereo.cml +++ b/lib/iris/tests/results/netcdf/netcdf_stereo.cml @@ -56,13 +56,13 @@ - + - + diff --git a/lib/iris/tests/test_coordsystem.py b/lib/iris/tests/test_coordsystem.py index 212c04bd7e..4497e77903 100644 --- a/lib/iris/tests/test_coordsystem.py +++ b/lib/iris/tests/test_coordsystem.py @@ -14,7 +14,6 @@ GeogCS, LambertConformal, RotatedGeogCS, - Stereographic, TransverseMercator, ) import iris.coords @@ -33,16 +32,6 @@ def osgb(): ) -def stereo(): - return Stereographic( - central_lat=-90, - central_lon=-45, - false_easting=100, - false_northing=200, - ellipsoid=GeogCS(6377563.396, 6356256.909), - ) - - class TestCoordSystemLookup(tests.IrisTest): def setUp(self): self.cube = iris.tests.stock.lat_lon_cube() @@ -519,85 +508,6 @@ def test_as_cartopy_projection(self): self.assertEqual(res, expected) -class Test_Stereographic_construction(tests.IrisTest): - def test_stereo(self): - st = stereo() - self.assertXMLElement(st, ("coord_systems", "Stereographic.xml")) - - -class Test_Stereographic_repr(tests.IrisTest): - def test_stereo(self): - st = stereo() - expected = ( - "Stereographic(central_lat=-90.0, central_lon=-45.0, " - "false_easting=100.0, false_northing=200.0, true_scale_lat=None, " - "ellipsoid=GeogCS(semi_major_axis=6377563.396, semi_minor_axis=6356256.909))" - ) - self.assertEqual(expected, repr(st)) - - -class Test_Stereographic_as_cartopy_crs(tests.IrisTest): - def test_as_cartopy_crs(self): - latitude_of_projection_origin = -90.0 - longitude_of_projection_origin = -45.0 - false_easting = 100.0 - false_northing = 200.0 - ellipsoid = GeogCS(6377563.396, 6356256.909) - - st = Stereographic( - central_lat=latitude_of_projection_origin, - central_lon=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - ellipsoid=ellipsoid, - ) - expected = ccrs.Stereographic( - central_latitude=latitude_of_projection_origin, - central_longitude=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - ) - - res = st.as_cartopy_crs() - self.assertEqual(res, expected) - - -class Test_Stereographic_as_cartopy_projection(tests.IrisTest): - def test_as_cartopy_projection(self): - latitude_of_projection_origin = -90.0 - longitude_of_projection_origin = -45.0 - false_easting = 100.0 - false_northing = 200.0 - ellipsoid = GeogCS(6377563.396, 6356256.909) - - st = Stereographic( - central_lat=latitude_of_projection_origin, - central_lon=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - ellipsoid=ellipsoid, - ) - expected = ccrs.Stereographic( - central_latitude=latitude_of_projection_origin, - central_longitude=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - ) - - res = st.as_cartopy_projection() - self.assertEqual(res, expected) - - class Test_LambertConformal(tests.GraphicsTest): def test_fail_secant_latitudes_none(self): emsg = "secant latitudes" diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py index 4d130c0f0e..5029f822e7 100644 --- a/lib/iris/tests/test_netcdf.py +++ b/lib/iris/tests/test_netcdf.py @@ -248,6 +248,16 @@ def test_load_stereographic_grid(self): ) self.assertCML(cube, ("netcdf", "netcdf_stereo.cml")) + def test_load_polar_stereographic_grid(self): + # Test loading a single CF-netCDF file with a polar stereographic + # grid_mapping. + cube = iris.load_cube( + tests.get_data_path( + ("NetCDF", "polar", "toa_brightness_temperature.nc") + ) + ) + self.assertCML(cube, ("netcdf", "netcdf_polar.cml")) + def test_cell_methods(self): # Test exercising CF-netCDF cell method parsing. cubes = iris.load( diff --git a/lib/iris/tests/unit/coord_systems/test_PolarStereographic.py b/lib/iris/tests/unit/coord_systems/test_PolarStereographic.py new file mode 100755 index 0000000000..25f5d24800 --- /dev/null +++ b/lib/iris/tests/unit/coord_systems/test_PolarStereographic.py @@ -0,0 +1,251 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :class:`iris.coord_systems.PolarStereographic` class.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import cartopy.crs as ccrs + +from iris.coord_systems import GeogCS, PolarStereographic + + +class Test_PolarStereographic__basics(tests.IrisTest): + def setUp(self): + self.ps_blank = PolarStereographic( + central_lat=90.0, + central_lon=0, + ellipsoid=GeogCS(6377563.396, 6356256.909), + ) + self.ps_standard_parallel = PolarStereographic( + central_lat=90.0, + central_lon=0, + true_scale_lat=30, + ellipsoid=GeogCS(6377563.396, 6356256.909), + ) + self.ps_scale_factor = PolarStereographic( + central_lat=90.0, + central_lon=0, + scale_factor_at_projection_origin=1.1, + ellipsoid=GeogCS(6377563.396, 6356256.909), + ) + + def test_construction(self): + self.assertXMLElement( + self.ps_blank, ("coord_systems", "PolarStereographic.xml") + ) + + def test_construction_sp(self): + self.assertXMLElement( + self.ps_standard_parallel, + ("coord_systems", "PolarStereographicStandardParallel.xml"), + ) + + def test_construction_sf(self): + self.assertXMLElement( + self.ps_scale_factor, + ("coord_systems", "PolarStereographicScaleFactor.xml"), + ) + + def test_repr_blank(self): + expected = ( + "PolarStereographic(central_lat=90.0, central_lon=0.0, " + "false_easting=0.0, false_northing=0.0, " + "true_scale_lat=None, " + "ellipsoid=GeogCS(semi_major_axis=6377563.396, " + "semi_minor_axis=6356256.909))" + ) + self.assertEqual(expected, repr(self.ps_blank)) + + def test_repr_standard_parallel(self): + expected = ( + "PolarStereographic(central_lat=90.0, central_lon=0.0, " + "false_easting=0.0, false_northing=0.0, " + "true_scale_lat=30.0, " + "ellipsoid=GeogCS(semi_major_axis=6377563.396, " + "semi_minor_axis=6356256.909))" + ) + self.assertEqual(expected, repr(self.ps_standard_parallel)) + + def test_repr_scale_factor(self): + expected = ( + "PolarStereographic(central_lat=90.0, central_lon=0.0, " + "false_easting=0.0, false_northing=0.0, " + "scale_factor_at_projection_origin=1.1, " + "ellipsoid=GeogCS(semi_major_axis=6377563.396, " + "semi_minor_axis=6356256.909))" + ) + self.assertEqual(expected, repr(self.ps_scale_factor)) + + +class Test_init_defaults(tests.IrisTest): + def test_set_optional_args(self): + # Check that setting the optional (non-ellipse) args works. + crs = PolarStereographic( + central_lat=90, + central_lon=50, + false_easting=13, + false_northing=12, + true_scale_lat=32, + ) + self.assertEqualAndKind(crs.central_lat, 90.0) + self.assertEqualAndKind(crs.central_lon, 50.0) + self.assertEqualAndKind(crs.false_easting, 13.0) + self.assertEqualAndKind(crs.false_northing, 12.0) + self.assertEqualAndKind(crs.true_scale_lat, 32.0) + + def test_set_optional_scale_factor_alternative(self): + # Check that setting the optional (non-ellipse) args works. + crs = PolarStereographic( + central_lat=-90, + central_lon=50, + false_easting=13, + false_northing=12, + scale_factor_at_projection_origin=3.1, + ) + self.assertEqualAndKind(crs.central_lat, -90.0) + self.assertEqualAndKind(crs.central_lon, 50.0) + self.assertEqualAndKind(crs.false_easting, 13.0) + self.assertEqualAndKind(crs.false_northing, 12.0) + self.assertEqualAndKind(crs.scale_factor_at_projection_origin, 3.1) + + def _check_crs_defaults(self, crs): + # Check for property defaults when no kwargs options were set. + # NOTE: except ellipsoid, which is done elsewhere. + self.assertEqualAndKind(crs.false_easting, 0.0) + self.assertEqualAndKind(crs.false_northing, 0.0) + self.assertEqualAndKind(crs.true_scale_lat, None) + self.assertEqualAndKind(crs.scale_factor_at_projection_origin, None) + + def test_no_optional_args(self): + # Check expected defaults with no optional args. + crs = PolarStereographic( + central_lat=-90, + central_lon=50, + ) + self._check_crs_defaults(crs) + + def test_optional_args_None(self): + # Check expected defaults with optional args=None. + crs = PolarStereographic( + central_lat=-90, + central_lon=50, + true_scale_lat=None, + scale_factor_at_projection_origin=None, + false_easting=None, + false_northing=None, + ) + self._check_crs_defaults(crs) + + +class AsCartopyMixin: + def test_simple(self): + # Check that a projection set up with all the defaults is correctly + # converted to a cartopy CRS. + central_lat = -90 + central_lon = 50 + polar_cs = PolarStereographic( + central_lat=central_lat, + central_lon=central_lon, + ) + res = self.as_cartopy_method(polar_cs) + expected = ccrs.Stereographic( + central_latitude=central_lat, + central_longitude=central_lon, + globe=ccrs.Globe(), + ) + self.assertEqual(res, expected) + + def test_extra_kwargs_scale_factor(self): + # Check that a projection with non-default values is correctly + # converted to a cartopy CRS. + central_lat = -90 + central_lon = 50 + scale_factor_at_projection_origin = 1.3 + false_easting = 13 + false_northing = 15 + ellipsoid = GeogCS( + semi_major_axis=6377563.396, semi_minor_axis=6356256.909 + ) + + polar_cs = PolarStereographic( + central_lat=central_lat, + central_lon=central_lon, + scale_factor_at_projection_origin=scale_factor_at_projection_origin, + false_easting=false_easting, + false_northing=false_northing, + ellipsoid=ellipsoid, + ) + + expected = ccrs.Stereographic( + central_latitude=central_lat, + central_longitude=central_lon, + false_easting=false_easting, + false_northing=false_northing, + scale_factor=scale_factor_at_projection_origin, + globe=ccrs.Globe( + semimajor_axis=6377563.396, + semiminor_axis=6356256.909, + ellipse=None, + ), + ) + + res = self.as_cartopy_method(polar_cs) + self.assertEqual(res, expected) + + def test_extra_kwargs_true_scale_lat_alternative(self): + # Check that a projection with non-default values is correctly + # converted to a cartopy CRS. + central_lat = -90 + central_lon = 50 + true_scale_lat = 80 + false_easting = 13 + false_northing = 15 + ellipsoid = GeogCS( + semi_major_axis=6377563.396, semi_minor_axis=6356256.909 + ) + + polar_cs = PolarStereographic( + central_lat=central_lat, + central_lon=central_lon, + true_scale_lat=true_scale_lat, + false_easting=false_easting, + false_northing=false_northing, + ellipsoid=ellipsoid, + ) + + expected = ccrs.Stereographic( + central_latitude=central_lat, + central_longitude=central_lon, + false_easting=false_easting, + false_northing=false_northing, + true_scale_latitude=true_scale_lat, + globe=ccrs.Globe( + semimajor_axis=6377563.396, + semiminor_axis=6356256.909, + ellipse=None, + ), + ) + + res = self.as_cartopy_method(polar_cs) + self.assertEqual(res, expected) + + +class Test_PolarStereographic__as_cartopy_crs(tests.IrisTest, AsCartopyMixin): + def setUp(self): + self.as_cartopy_method = PolarStereographic.as_cartopy_crs + + +class Test_PolarStereographic__as_cartopy_projection( + tests.IrisTest, AsCartopyMixin +): + def setUp(self): + self.as_cartopy_method = PolarStereographic.as_cartopy_projection + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_Stereographic.py b/lib/iris/tests/unit/coord_systems/test_Stereographic.py index fac411f9d5..acd77112c1 100644 --- a/lib/iris/tests/unit/coord_systems/test_Stereographic.py +++ b/lib/iris/tests/unit/coord_systems/test_Stereographic.py @@ -9,12 +9,29 @@ # importing anything else. import iris.tests as tests # isort:skip -from iris.coord_systems import Stereographic +import cartopy.crs as ccrs + +from iris.coord_systems import GeogCS, Stereographic + + +def stereo(**kwargs): + return Stereographic( + central_lat=-90, + central_lon=-45, + false_easting=100, + false_northing=200, + ellipsoid=GeogCS(6377563.396, 6356256.909), + **kwargs, + ) + + +class Test_Stereographic_construction(tests.IrisTest): + def test_stereo(self): + st = stereo() + self.assertXMLElement(st, ("coord_systems", "Stereographic.xml")) class Test_init_defaults(tests.IrisTest): - # NOTE: most of the testing for Stereographic is in the legacy test module - # 'iris.tests.test_coordsystem'. # This class *only* tests the defaults for optional constructor args. def test_set_optional_args(self): @@ -26,12 +43,26 @@ def test_set_optional_args(self): self.assertEqualAndKind(crs.false_northing, -203.7) self.assertEqualAndKind(crs.true_scale_lat, 77.0) + def test_set_optional_args_scale_factor_alternative(self): + # Check that setting the optional (non-ellipse) args works. + crs = Stereographic( + 0, + 0, + false_easting=100, + false_northing=-203.7, + scale_factor_at_projection_origin=1.3, + ) + self.assertEqualAndKind(crs.false_easting, 100.0) + self.assertEqualAndKind(crs.false_northing, -203.7) + self.assertEqualAndKind(crs.scale_factor_at_projection_origin, 1.3) + def _check_crs_defaults(self, crs): # Check for property defaults when no kwargs options were set. # NOTE: except ellipsoid, which is done elsewhere. self.assertEqualAndKind(crs.false_easting, 0.0) self.assertEqualAndKind(crs.false_northing, 0.0) self.assertIsNone(crs.true_scale_lat) + self.assertIsNone(crs.scale_factor_at_projection_origin) def test_no_optional_args(self): # Check expected defaults with no optional args. @@ -41,10 +72,141 @@ def test_no_optional_args(self): def test_optional_args_None(self): # Check expected defaults with optional args=None. crs = Stereographic( - 0, 0, false_easting=None, false_northing=None, true_scale_lat=None + 0, + 0, + false_easting=None, + false_northing=None, + true_scale_lat=None, + scale_factor_at_projection_origin=None, ) self._check_crs_defaults(crs) +class Test_Stereographic_repr(tests.IrisTest): + def test_stereo(self): + st = stereo() + expected = ( + "Stereographic(central_lat=-90.0, central_lon=-45.0, " + "false_easting=100.0, false_northing=200.0, true_scale_lat=None, " + "ellipsoid=GeogCS(semi_major_axis=6377563.396, semi_minor_axis=6356256.909))" + ) + self.assertEqual(expected, repr(st)) + + def test_stereo_scale_factor(self): + st = stereo(scale_factor_at_projection_origin=0.9) + expected = ( + "Stereographic(central_lat=-90.0, central_lon=-45.0, " + "false_easting=100.0, false_northing=200.0, " + "scale_factor_at_projection_origin=0.9, " + "ellipsoid=GeogCS(semi_major_axis=6377563.396, semi_minor_axis=6356256.909))" + ) + self.assertEqual(expected, repr(st)) + + +class AsCartopyMixin: + def test_basic(self): + latitude_of_projection_origin = -90.0 + longitude_of_projection_origin = -45.0 + false_easting = 100.0 + false_northing = 200.0 + ellipsoid = GeogCS(6377563.396, 6356256.909) + + st = Stereographic( + central_lat=latitude_of_projection_origin, + central_lon=longitude_of_projection_origin, + false_easting=false_easting, + false_northing=false_northing, + ellipsoid=ellipsoid, + ) + expected = ccrs.Stereographic( + central_latitude=latitude_of_projection_origin, + central_longitude=longitude_of_projection_origin, + false_easting=false_easting, + false_northing=false_northing, + globe=ccrs.Globe( + semimajor_axis=6377563.396, + semiminor_axis=6356256.909, + ellipse=None, + ), + ) + + res = self.as_cartopy_method(st) + self.assertEqual(res, expected) + + def test_true_scale_lat(self): + latitude_of_projection_origin = -90.0 + longitude_of_projection_origin = -45.0 + false_easting = 100.0 + false_northing = 200.0 + true_scale_lat = 30 + ellipsoid = GeogCS(6377563.396, 6356256.909) + + st = Stereographic( + central_lat=latitude_of_projection_origin, + central_lon=longitude_of_projection_origin, + false_easting=false_easting, + false_northing=false_northing, + true_scale_lat=true_scale_lat, + ellipsoid=ellipsoid, + ) + expected = ccrs.Stereographic( + central_latitude=latitude_of_projection_origin, + central_longitude=longitude_of_projection_origin, + false_easting=false_easting, + false_northing=false_northing, + true_scale_latitude=true_scale_lat, + globe=ccrs.Globe( + semimajor_axis=6377563.396, + semiminor_axis=6356256.909, + ellipse=None, + ), + ) + + res = self.as_cartopy_method(st) + self.assertEqual(res, expected) + + def test_scale_factor(self): + latitude_of_projection_origin = -90.0 + longitude_of_projection_origin = -45.0 + false_easting = 100.0 + false_northing = 200.0 + scale_factor_at_projection_origin = 0.9 + ellipsoid = GeogCS(6377563.396, 6356256.909) + + st = Stereographic( + central_lat=latitude_of_projection_origin, + central_lon=longitude_of_projection_origin, + false_easting=false_easting, + false_northing=false_northing, + scale_factor_at_projection_origin=scale_factor_at_projection_origin, + ellipsoid=ellipsoid, + ) + expected = ccrs.Stereographic( + central_latitude=latitude_of_projection_origin, + central_longitude=longitude_of_projection_origin, + false_easting=false_easting, + false_northing=false_northing, + scale_factor=scale_factor_at_projection_origin, + globe=ccrs.Globe( + semimajor_axis=6377563.396, + semiminor_axis=6356256.909, + ellipse=None, + ), + ) + + res = self.as_cartopy_method(st) + self.assertEqual(res, expected) + + +class Test_Stereographic_as_cartopy_crs(tests.IrisTest, AsCartopyMixin): + def setUp(self): + self.as_cartopy_method = Stereographic.as_cartopy_crs + + +class Test_Stereographic_as_cartopy_projection(tests.IrisTest, AsCartopyMixin): + def setUp(self): + self.as_cartopy_method = Stereographic.as_cartopy_projection + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py index 1bf9226092..a56ef5e754 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py @@ -144,7 +144,6 @@ def _make_testcase_cdl( # Add a specified scale-factor, if requested. if mapping_scalefactor is not None: # Add a specific scale-factor term to the grid mapping. - # (Non-unity scale is not supported for Mercator/Stereographic). sfapo_name = hh.CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN g_string += f""" {g_varname}:{sfapo_name} = {mapping_scalefactor} ; @@ -197,6 +196,22 @@ def _make_testcase_cdl( g_string += f""" {g_varname}:{saa_name} = "y" ; """ + # Polar stereo needs a special 'latitude of projection origin', a + # 'straight_vertical_longitude_from_pole' and a `standard_parallel` or + # `scale_factor_at_projection_origin` so treat it specially + if mapping_type_name in (hh.CF_GRID_MAPPING_POLAR,): + latpo_name = hh.CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN + g_string += f""" + {g_varname}:{latpo_name} = 90.0 ; + """ + svl_name = hh.CF_ATTR_GRID_STRAIGHT_VERT_LON + g_string += f""" + {g_varname}:{svl_name} = 0.0 ; + """ + stanpar_name = hh.CF_ATTR_GRID_STANDARD_PARALLEL + g_string += f""" + {g_varname}:{stanpar_name} = 1.0 ; + """ # y-coord values if yco_values is None: @@ -445,8 +460,7 @@ def test_mapping_rotated(self): # # All non-latlon coordinate systems ... # These all have projection-x/y coordinates with units of metres. - # They all work the same way, except that Stereographic has - # parameter checking routines that can fail. + # They all work the same way. # NOTE: various mapping types *require* certain addtional properties # - without which an error will occur during translation. # - run_testcase/_make_testcase_cdl know how to provide these @@ -494,28 +508,9 @@ def test_mapping_stereographic(self): result = self.run_testcase(mapping_type_name=hh.CF_GRID_MAPPING_STEREO) self.check_result(result, cube_cstype=ics.Stereographic) - def test_mapping_stereographic__fail_unsupported(self): - # Provide a non-unity scale factor, which we cannot handle. - # Result : fails to convert into a coord-system, and emits a warning. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(stereographic) --(FAILED check has_supported_stereographic_parameters) - # 003 : fc_provides_coordinate_(projection_y) - # 004 : fc_provides_coordinate_(projection_x) - # 005 : fc_build_coordinate_(projection_y)(FAILED projected coord with non-projected cs) - # 006 : fc_build_coordinate_(projection_x)(FAILED projected coord with non-projected cs) - # Notes: - # * grid-mapping identified : NONE - # * dim-coords identified : proj-x and -y - # * coords built : NONE (no dim or aux coords: cube has no coords) - warning = "not yet supported for stereographic" - result = self.run_testcase( - warning=warning, - mapping_type_name=hh.CF_GRID_MAPPING_STEREO, - mapping_scalefactor=2.0, - ) - self.check_result(result, cube_no_cs=True, cube_no_xycoords=True) + def test_mapping_polar_stereographic(self): + result = self.run_testcase(mapping_type_name=hh.CF_GRID_MAPPING_POLAR) + self.check_result(result, cube_cstype=ics.PolarStereographic) def test_mapping_transverse_mercator(self): result = self.run_testcase( diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py new file mode 100755 index 0000000000..09cfde9d5b --- /dev/null +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py @@ -0,0 +1,150 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Test function :func:`iris.fileformats._nc_load_rules.helpers.\ +build_polar_stereographic_coordinate_system`. + +""" + +# import iris tests first so that some things can be initialised before +# importing anything else +import iris.tests as tests # isort:skip + +from unittest import mock + +import iris +from iris.coord_systems import PolarStereographic +from iris.fileformats._nc_load_rules.helpers import ( + build_polar_stereographic_coordinate_system, +) + + +class TestBuildPolarStereographicCoordinateSystem(tests.IrisTest): + def test_valid_north(self): + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + + cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) + + expected = PolarStereographic( + central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), + central_lat=(cf_grid_var.latitude_of_projection_origin), + scale_factor_at_projection_origin=( + cf_grid_var.scale_factor_at_projection_origin + ), + ellipsoid=iris.coord_systems.GeogCS( + cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis + ), + ) + self.assertEqual(cs, expected) + + def test_valid_south(self): + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=-90, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + + cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) + + expected = PolarStereographic( + central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), + central_lat=(cf_grid_var.latitude_of_projection_origin), + scale_factor_at_projection_origin=( + cf_grid_var.scale_factor_at_projection_origin + ), + ellipsoid=iris.coord_systems.GeogCS( + cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis + ), + ) + self.assertEqual(cs, expected) + + def test_valid_with_standard_parallel(self): + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + standard_parallel=30, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + + cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) + + expected = PolarStereographic( + central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), + central_lat=(cf_grid_var.latitude_of_projection_origin), + true_scale_lat=(cf_grid_var.standard_parallel), + ellipsoid=iris.coord_systems.GeogCS( + cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis + ), + ) + self.assertEqual(cs, expected) + + def test_valid_with_false_easting_northing(self): + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + scale_factor_at_projection_origin=1, + false_easting=30, + false_northing=40, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + + cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) + + expected = PolarStereographic( + central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), + central_lat=(cf_grid_var.latitude_of_projection_origin), + scale_factor_at_projection_origin=( + cf_grid_var.scale_factor_at_projection_origin + ), + false_easting=(cf_grid_var.false_easting), + false_northing=(cf_grid_var.false_northing), + ellipsoid=iris.coord_systems.GeogCS( + cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis + ), + ) + self.assertEqual(cs, expected) + + def test_valid_nonzero_veritcal_lon(self): + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=30, + latitude_of_projection_origin=90, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + + cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) + + expected = PolarStereographic( + central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), + central_lat=(cf_grid_var.latitude_of_projection_origin), + scale_factor_at_projection_origin=( + cf_grid_var.scale_factor_at_projection_origin + ), + ellipsoid=iris.coord_systems.GeogCS( + cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis + ), + ) + self.assertEqual(cs, expected) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py new file mode 100755 index 0000000000..6e6d6e4e81 --- /dev/null +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py @@ -0,0 +1,242 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Test function :func:`iris.fileformats._nc_load_rules.helpers.\ +has_supported_polar_stereographic_parameters`. + +""" + +from unittest import mock +import warnings + +from iris.fileformats._nc_load_rules.helpers import ( + has_supported_polar_stereographic_parameters, +) + +# import iris tests first so that some things can be initialised before +# importing anything else +import iris.tests as tests # isort:skip + + +def _engine(cf_grid_var, cf_name): + cf_group = {cf_name: cf_grid_var} + cf_var = mock.Mock(cf_group=cf_group) + return mock.Mock(cf_var=cf_var) + + +class TestHasSupportedPolarStereographicParameters(tests.IrisTest): + def test_valid_base_north(self): + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + false_easting=0, + false_northing=0, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertTrue(is_valid) + + def test_valid_base_south(self): + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=-90, + false_easting=0, + false_northing=0, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertTrue(is_valid) + + def test_valid_straight_vertical_longitude(self): + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=30, + latitude_of_projection_origin=90, + false_easting=0, + false_northing=0, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertTrue(is_valid) + + def test_valid_false_easting_northing(self): + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + false_easting=15, + false_northing=10, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertTrue(is_valid) + + def test_valid_standard_parallel(self): + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + false_easting=0, + false_northing=0, + standard_parallel=15, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertTrue(is_valid) + + def test_valid_scale_factor(self): + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + false_easting=0, + false_northing=0, + scale_factor_at_projection_origin=0.9, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertTrue(is_valid) + + def test_invalid_scale_factor_and_standard_parallel(self): + # Scale factor and standard parallel cannot both be specified for + # Polar Stereographic projections + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + false_easting=0, + false_northing=0, + scale_factor_at_projection_origin=0.9, + standard_parallel=20, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertFalse(is_valid) + self.assertEqual(len(warns), 1) + self.assertRegex( + str(warns[0]), + "both " + '"scale_factor_at_projection_origin" and "standard_parallel"', + ) + + def test_absent_scale_factor_and_standard_parallel(self): + # Scale factor and standard parallel cannot both be specified for + # Polar Stereographic projections + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=90, + false_easting=0, + false_northing=0, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertFalse(is_valid) + self.assertEqual(len(warns), 1) + self.assertRegex( + str(warns[0]), + 'One of "scale_factor_at_projection_origin" and ' + '"standard_parallel" is required.', + ) + + def test_invalid_latitude_of_projection_origin(self): + # Scale factor and standard parallel cannot both be specified for + # Polar Stereographic projections + cf_name = "polar_stereographic" + cf_grid_var = mock.Mock( + spec=[], + straight_vertical_longitude_from_pole=0, + latitude_of_projection_origin=45, + false_easting=0, + false_northing=0, + scale_factor_at_projection_origin=1, + semi_major_axis=6377563.396, + semi_minor_axis=6356256.909, + ) + engine = _engine(cf_grid_var, cf_name) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter("always") + is_valid = has_supported_polar_stereographic_parameters( + engine, cf_name + ) + + self.assertFalse(is_valid) + self.assertEqual(len(warns), 1) + self.assertRegex( + str(warns[0]), + r'"latitude_of_projection_origin" must be \+90 or -90\.', + ) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_stereographic_parameters.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_stereographic_parameters.py deleted file mode 100644 index 8bec823f4b..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_stereographic_parameters.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -has_supported_stereographic_parameters`. - -""" - -from unittest import mock -import warnings - -from iris.fileformats._nc_load_rules.helpers import ( - has_supported_stereographic_parameters, -) - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - - -def _engine(cf_grid_var, cf_name): - cf_group = {cf_name: cf_grid_var} - cf_var = mock.Mock(cf_group=cf_group) - return mock.Mock(cf_var=cf_var) - - -class TestHasSupportedStereographicParameters(tests.IrisTest): - def test_valid(self): - cf_name = "stereographic" - cf_grid_var = mock.Mock( - spec=[], - latitude_of_projection_origin=0, - longitude_of_projection_origin=0, - false_easting=-100, - false_northing=200, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_stereographic_parameters(engine, cf_name) - - self.assertTrue(is_valid) - - def test_invalid_scale_factor(self): - # Iris does not yet support scale factors other than one for - # stereographic projections - cf_name = "stereographic" - cf_grid_var = mock.Mock( - spec=[], - latitude_of_projection_origin=0, - longitude_of_projection_origin=0, - false_easting=-100, - false_northing=200, - scale_factor_at_projection_origin=0.9, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_stereographic_parameters(engine, cf_name) - - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex(str(warns[0]), "Scale factor") - - -if __name__ == "__main__": - tests.main() From 4fa3f06caf2175b8654fdb4a2bcd83080b7a1418 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Tue, 7 Jun 2022 17:40:23 +0100 Subject: [PATCH 128/319] Add mesh details to cube printout. (#4778) * Slightly refactor repr-html code to rationalise section handling. * Test repr-html of string attributes. * Add mesh details to cube printout. * Update meshcube printouts in docs. * Add Mesh section to repr-html. * Review change : make test clearer. * Review change : fix string truncation/quoting. --- docs/src/further_topics/ugrid/data_model.rst | 3 + docs/src/further_topics/ugrid/operations.rst | 9 ++ lib/iris/_representation/cube_printout.py | 6 +- lib/iris/_representation/cube_summary.py | 59 ++++++++++-- lib/iris/experimental/representation.py | 78 +++++++-------- .../_make_content/mesh_result.txt | 24 +++++ .../embedded_newlines_string_attribute.txt | 8 ++ .../long_string_attribute.txt | 8 ++ .../multi_string_attribute.txt | 8 ++ .../simple_string_attribute.txt | 8 ++ .../representation/test_CubeRepresentation.py | 96 +++++++++++++------ .../cube_printout/test_CubePrintout.py | 9 +- .../cube_summary/test_CubeSummary.py | 3 +- 13 files changed, 225 insertions(+), 94 deletions(-) create mode 100644 lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content/mesh_result.txt create mode 100644 lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/embedded_newlines_string_attribute.txt create mode 100644 lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/long_string_attribute.txt create mode 100644 lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/multi_string_attribute.txt create mode 100644 lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/simple_string_attribute.txt diff --git a/docs/src/further_topics/ugrid/data_model.rst b/docs/src/further_topics/ugrid/data_model.rst index 55e4f79a96..cc3cc7b793 100644 --- a/docs/src/further_topics/ugrid/data_model.rst +++ b/docs/src/further_topics/ugrid/data_model.rst @@ -405,6 +405,9 @@ the :class:`~iris.cube.Cube`\'s unstructured dimension. Mesh coordinates: latitude x - longitude x - + Mesh: + name my_mesh + location edge >>> print(edge_cube.location) edge diff --git a/docs/src/further_topics/ugrid/operations.rst b/docs/src/further_topics/ugrid/operations.rst index c636043640..a4e0e593d7 100644 --- a/docs/src/further_topics/ugrid/operations.rst +++ b/docs/src/further_topics/ugrid/operations.rst @@ -189,6 +189,9 @@ Creating a :class:`~iris.cube.Cube` is unchanged; the Mesh coordinates: latitude x - longitude x - + Mesh: + name my_mesh + location edge Save @@ -392,6 +395,9 @@ etcetera: Mesh coordinates: latitude x - longitude x - + Mesh: + name my_mesh + location face Attributes: Conventions 'CF-1.7' @@ -620,6 +626,9 @@ the link between :class:`~iris.cube.Cube` and Mesh coordinates: latitude x - longitude x - + Mesh: + name my_mesh + location edge # Sub-setted MeshCoords have become AuxCoords. >>> print(edge_cube[:-1]) diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index 81d46bb29f..b0b569512d 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -252,15 +252,15 @@ def add_scalar_row(name, value=""): # Add a row for each item # NOTE: different section types need different handling title = sect_name.lower() - if "scalar coordinate" in title: + if title == "scalar coordinates:": for item in sect.contents: add_scalar_row(item.name, item.content) if item.extra: add_scalar_row(item_to_extra_indent + item.extra) - elif "attribute" in title or "cell method" in title: + elif title in ("attributes:", "cell methods:", "mesh:"): for title, value in zip(sect.names, sect.values): add_scalar_row(title, value) - elif "scalar cell measure" in title: + elif title == "scalar cell measures:": # These are just strings: nothing in the 'value' column. for name in sect.contents: add_scalar_row(name) diff --git a/lib/iris/_representation/cube_summary.py b/lib/iris/_representation/cube_summary.py index 1e78a92fd1..885de9acf3 100644 --- a/lib/iris/_representation/cube_summary.py +++ b/lib/iris/_representation/cube_summary.py @@ -48,11 +48,25 @@ def __init__(self, cube, name_padding=35): self.dimension_header = DimensionHeader(cube) -def string_repr(text, quote_strings=False): +def string_repr(text, quote_strings=False, clip_strings=False): """Produce a one-line printable form of a text string.""" - if re.findall("[\n\t]", text) or quote_strings: + force_quoted = re.findall("[\n\t]", text) or quote_strings + if force_quoted: # Replace the string with its repr (including quotes). text = repr(text) + if clip_strings: + # First check for quotes. + # N.B. not just 'quote_strings', but also array values-as-strings + has_quotes = text[0] in "\"'" + if has_quotes: + # Strip off (and store) any outer quotes before clipping. + pre_quote, post_quote = text[0], text[-1] + text = text[1:-1] + # clipping : use 'rider' with extra space in case it ends in a '.' + text = iris.util.clip_string(text, rider=" ...") + if has_quotes: + # Replace in original quotes + text = pre_quote + text + post_quote return text @@ -62,17 +76,20 @@ def array_repr(arr): text = repr(arr) # ..then reduce any multiple spaces and newlines. text = re.sub("[ \t\n]+", " ", text) + text = string_repr(text, quote_strings=False, clip_strings=True) return text -def value_repr(value, quote_strings=False): +def value_repr(value, quote_strings=False, clip_strings=False): """ Produce a single-line printable version of an attribute or scalar value. """ if hasattr(value, "dtype"): value = array_repr(value) elif isinstance(value, str): - value = string_repr(value, quote_strings=quote_strings) + value = string_repr( + value, quote_strings=quote_strings, clip_strings=clip_strings + ) value = str(value) return value @@ -132,7 +149,7 @@ def __init__(self, cube, vector, iscoord): self.extra = "" -class ScalarSummary(CoordSummary): +class ScalarCoordSummary(CoordSummary): def __init__(self, cube, coord): self.name = coord.name() if ( @@ -188,10 +205,12 @@ def __init__(self, title, cube, vectors, iscoord): ] -class ScalarSection(Section): +class ScalarCoordSection(Section): def __init__(self, title, cube, scalars): self.title = title - self.contents = [ScalarSummary(cube, scalar) for scalar in scalars] + self.contents = [ + ScalarCoordSummary(cube, scalar) for scalar in scalars + ] class ScalarCellMeasureSection(Section): @@ -207,14 +226,32 @@ def __init__(self, title, attributes): self.values = [] self.contents = [] for name, value in sorted(attributes.items()): - value = value_repr(value, quote_strings=True) - value = iris.util.clip_string(value) + value = value_repr(value, quote_strings=True, clip_strings=True) self.names.append(name) self.values.append(value) content = "{}: {}".format(name, value) self.contents.append(content) +class ScalarMeshSection(AttributeSection): + # This happens to behave just like an attribute sections, but it + # initialises direct from the cube. + def __init__(self, title, cube): + self.title = title + self.names = [] + self.values = [] + self.contents = [] + if cube.mesh is not None: + self.names.extend(["name", "location"]) + self.values.extend([cube.mesh.name(), cube.location]) + self.contents.extend( + [ + "{}: {}".format(name, value) + for name, value in zip(self.names, self.values) + ] + ) + + class CellMethodSection(Section): def __init__(self, title, cell_methods): self.title = title @@ -322,8 +359,10 @@ def add_vector_section(title, contents, iscoord=True): def add_scalar_section(section_class, title, *args): self.scalar_sections[title] = section_class(title, *args) + add_scalar_section(ScalarMeshSection, "Mesh:", cube) + add_scalar_section( - ScalarSection, "Scalar coordinates:", cube, scalar_coords + ScalarCoordSection, "Scalar coordinates:", cube, scalar_coords ) add_scalar_section( ScalarCellMeasureSection, diff --git a/lib/iris/experimental/representation.py b/lib/iris/experimental/representation.py index 48e11e1fb0..116b340592 100644 --- a/lib/iris/experimental/representation.py +++ b/lib/iris/experimental/representation.py @@ -85,28 +85,32 @@ def __init__(self, cube): self.cube_id = id(self.cube) self.cube_str = escape(str(self.cube)) - self.str_headings = { - "Dimension coordinates:": None, - "Auxiliary coordinates:": None, - "Mesh coordinates:": None, - "Derived coordinates:": None, - "Cell measures:": None, - "Ancillary variables:": None, - "Scalar coordinates:": None, - "Scalar cell measures:": None, - "Cell methods:": None, - "Attributes:": None, - } - self.dim_desc_coords = [ + # Define the expected vector and scalar sections in output, in expected + # order of appearance. + # NOTE: if we recoded this to use a CubeSummary, these section titles + # would be available from that. + self.vector_section_names = [ "Dimension coordinates:", - "Auxiliary coordinates:", "Mesh coordinates:", + "Auxiliary coordinates:", "Derived coordinates:", "Cell measures:", "Ancillary variables:", ] - - self.two_cell_headers = ["Scalar coordinates:", "Attributes:"] + self.scalar_section_names = [ + "Mesh:", + "Scalar coordinates:", + "Scalar cell measures:", + "Cell methods:", + "Attributes:", + ] + self.sections_data = { + name: None + for name in self.vector_section_names + self.scalar_section_names + } + # 'Scalar-cell-measures' is currently alone amongst the scalar sections, + # in displaying only a 'name' and no 'value' field. + self.single_cell_section_names = ["Scalar cell measures:"] # Important content that summarises a cube is defined here. self.shapes = self.cube.shape @@ -160,7 +164,7 @@ def _get_bits(self, bits): # Get heading indices within the printout. start_inds = [] - for hdg in self.str_headings.keys(): + for hdg in self.sections_data.keys(): heading = "{}{}".format(left_indent, hdg) try: start_ind = bits.index(heading) @@ -178,7 +182,7 @@ def _get_bits(self, bits): content = bits[i0 + 1 : i1] else: content = bits[i0 + 1 :] - self.str_headings[str_heading_name] = content + self.sections_data[str_heading_name] = content def _make_header(self): """ @@ -272,47 +276,29 @@ def _make_row(self, title, body=None, col_span=0): row.append("

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

") return row - def _expand_last_cell(self, element, body): - """Expand an element containing a cell by adding a new line.""" - split_point = element.index("") - element = element[:split_point] + "
" + body + element[split_point:] - return element - def _make_content(self): elements = [] - for k, v in self.str_headings.items(): + for k, v in self.sections_data.items(): if v is not None: # Add the sub-heading title. elements.extend(self._make_row(k)) for line in v: # Add every other row in the sub-heading. - if k in self.dim_desc_coords: + if k in self.vector_section_names: body = re.findall(r"[\w-]+", line) title = body.pop(0) colspan = 0 - elif k in self.two_cell_headers: - try: - split_point = line.index(":") - except ValueError: - # When a line exists in v without a ':', we expect - # that this is due to the value of some attribute - # containing multiple lines. We collect all these - # lines in the same cell. - body = line.strip() - # We choose the element containing the last cell - # in the last row. - element = elements[-2] - element = self._expand_last_cell(element, body) - elements[-2] = element - continue + else: + colspan = self.ndims + if k in self.single_cell_section_names: + title = line.strip() + body = "" else: + line = line.strip() + split_point = line.index(" ") title = line[:split_point].strip() body = line[split_point + 2 :].strip() - colspan = self.ndims - else: - title = line.strip() - body = "" - colspan = self.ndims + elements.extend( self._make_row(title, body=body, col_span=colspan) ) diff --git a/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content/mesh_result.txt b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content/mesh_result.txt new file mode 100644 index 0000000000..e20527cb49 --- /dev/null +++ b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content/mesh_result.txt @@ -0,0 +1,24 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/embedded_newlines_string_attribute.txt b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/embedded_newlines_string_attribute.txt new file mode 100644 index 0000000000..e886d25e60 --- /dev/null +++ b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/embedded_newlines_string_attribute.txt @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/long_string_attribute.txt b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/long_string_attribute.txt new file mode 100644 index 0000000000..e972e1d6df --- /dev/null +++ b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/long_string_attribute.txt @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/multi_string_attribute.txt b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/multi_string_attribute.txt new file mode 100644 index 0000000000..1736a083d6 --- /dev/null +++ b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/multi_string_attribute.txt @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/simple_string_attribute.txt b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/simple_string_attribute.txt new file mode 100644 index 0000000000..8726d1f6ea --- /dev/null +++ b/lib/iris/tests/results/unit/experimental/representation/CubeRepresentation/_make_content__string_attrs/simple_string_attribute.txt @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index eab3e7942d..99f7e7f2dd 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -31,7 +31,7 @@ def test_cube_attributes(self): self.assertStringEqual(str(self.cube), self.representer.cube_str) def test__heading_contents(self): - content = set(self.representer.str_headings.values()) + content = set(self.representer.sections_data.values()) self.assertEqual(len(content), 1) self.assertIsNone(list(content)[0]) @@ -131,21 +131,21 @@ def setUp(self): def test_population(self): nonmesh_values = [ value - for key, value in self.representer.str_headings.items() + for key, value in self.representer.sections_data.items() if "Mesh" not in key ] for v in nonmesh_values: self.assertIsNotNone(v) def test_headings__dimcoords(self): - contents = self.representer.str_headings["Dimension coordinates:"] + contents = self.representer.sections_data["Dimension coordinates:"] content_str = ",".join(content for content in contents) dim_coords = [c.name() for c in self.cube.dim_coords] for coord in dim_coords: self.assertIn(coord, content_str) def test_headings__auxcoords(self): - contents = self.representer.str_headings["Auxiliary coordinates:"] + contents = self.representer.sections_data["Auxiliary coordinates:"] content_str = ",".join(content for content in contents) aux_coords = [ c.name() for c in self.cube.aux_coords if c.shape != (1,) @@ -154,14 +154,14 @@ def test_headings__auxcoords(self): self.assertIn(coord, content_str) def test_headings__derivedcoords(self): - contents = self.representer.str_headings["Derived coordinates:"] + contents = self.representer.sections_data["Derived coordinates:"] content_str = ",".join(content for content in contents) derived_coords = [c.name() for c in self.cube.derived_coords] for coord in derived_coords: self.assertIn(coord, content_str) def test_headings__cellmeasures(self): - contents = self.representer.str_headings["Cell measures:"] + contents = self.representer.sections_data["Cell measures:"] content_str = ",".join(content for content in contents) cell_measures = [ c.name() for c in self.cube.cell_measures() if c.shape != (1,) @@ -170,7 +170,7 @@ def test_headings__cellmeasures(self): self.assertIn(coord, content_str) def test_headings__ancillaryvars(self): - contents = self.representer.str_headings["Ancillary variables:"] + contents = self.representer.sections_data["Ancillary variables:"] content_str = ",".join(content for content in contents) ancillary_variables = [ c.name() for c in self.cube.ancillary_variables() @@ -179,7 +179,7 @@ def test_headings__ancillaryvars(self): self.assertIn(coord, content_str) def test_headings__scalarcellmeasures(self): - contents = self.representer.str_headings["Scalar cell measures:"] + contents = self.representer.sections_data["Scalar cell measures:"] content_str = ",".join(content for content in contents) scalar_cell_measures = [ c.name() for c in self.cube.cell_measures() if c.shape == (1,) @@ -188,7 +188,7 @@ def test_headings__scalarcellmeasures(self): self.assertIn(coord, content_str) def test_headings__scalarcoords(self): - contents = self.representer.str_headings["Scalar coordinates:"] + contents = self.representer.sections_data["Scalar coordinates:"] content_str = ",".join(content for content in contents) scalar_coords = [ c.name() for c in self.cube.coords() if c.shape == (1,) @@ -197,14 +197,14 @@ def test_headings__scalarcoords(self): self.assertIn(coord, content_str) def test_headings__attributes(self): - contents = self.representer.str_headings["Attributes:"] + contents = self.representer.sections_data["Attributes:"] content_str = ",".join(content for content in contents) for attr_name, attr_value in self.cube.attributes.items(): self.assertIn(attr_name, content_str) self.assertIn(attr_value, content_str) def test_headings__cellmethods(self): - contents = self.representer.str_headings["Cell methods:"] + contents = self.representer.sections_data["Cell methods:"] content_str = ",".join(content for content in contents) for method in self.cube.cell_methods: name = method.method @@ -328,22 +328,6 @@ def test__attribute_row(self): self.assertIn(colspan_str, row_str) -@tests.skip_data -class Test__expand_last_cell(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_3d() - self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) - col_span = self.representer.ndims - self.row = self.representer._make_row( - "title", body="first", col_span=col_span - ) - - def test_add_line(self): - cell = self.representer._expand_last_cell(self.row[-2], "second") - self.assertIn("first
second", cell) - - @tests.skip_data class Test__make_content(tests.IrisTest): def setUp(self): @@ -372,15 +356,21 @@ def test_included(self): def test_not_included(self): # `stock.simple_3d()` only contains the `Dimension coordinates` attr. - not_included = list(self.representer.str_headings.keys()) + not_included = list(self.representer.sections_data.keys()) not_included.pop(not_included.index("Dimension coordinates:")) for heading in not_included: self.assertNotIn(heading, self.result) def test_mesh_included(self): # self.mesh_cube contains a `Mesh coordinates` section. - included = "Mesh coordinates" - self.assertIn(included, self.mesh_result) + self.assertIn( + '
', + self.mesh_result, + ) + # and a `Mesh:` section. + self.assertIn( + '', self.mesh_result + ) mesh_coord_names = [ c.name() for c in self.mesh_cube.coords(mesh_coords=True) ] @@ -389,11 +379,55 @@ def test_mesh_included(self): def test_mesh_not_included(self): # self.mesh_cube _only_ contains a `Mesh coordinates` section. - not_included = list(self.representer.str_headings.keys()) + not_included = list(self.representer.sections_data.keys()) not_included.pop(not_included.index("Mesh coordinates:")) for heading in not_included: self.assertNotIn(heading, self.result) + def test_mesh_result(self): + # A plain snapshot of a simple meshcube case. + self.assertString(self.mesh_result) + + +class Test__make_content__string_attrs(tests.IrisTest): + # Check how we handle "multi-line" string attributes. + # NOTE: before the adoption of iris._representation.CubeSummary, these + # used to appear as extra items in sections_data, identifiable by + # their not containing a ":", and which required to be combined into a + # single cell. + # This case no longer occurs. For now, just snapshot some current + # 'correct' behaviours, for change security and any future refactoring. + + @staticmethod + def _cube_stringattribute_html(name, attr): + cube = Cube([0]) + cube.attributes[name] = attr + representer = CubeRepresentation(cube) + representer._get_bits(representer._get_lines()) + result = representer._make_content() + return result + + def test_simple_string_attribute(self): + html = self._cube_stringattribute_html( + "single-string", "single string" + ) + self.assertString(html) + + def test_long_string_attribute(self): + attr = "long string.. " * 20 + html = self._cube_stringattribute_html("long-string", attr) + self.assertString(html) + + def test_embedded_newlines_string_attribute(self): + attr = "string\nwith\nnewlines" + html = self._cube_stringattribute_html("newlines-string", attr) + self.assertString(html) + + def test_multi_string_attribute(self): + attr = ["vector", "of", "strings"] + html = self._cube_stringattribute_html("multi-string", attr) + self.assertString(html) + @tests.skip_data class Test_repr_html(tests.IrisTest): diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py index 40a932b9e0..ff42acf566 100644 --- a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py +++ b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py @@ -465,11 +465,11 @@ def test_section_cube_attributes__string_extras(self): " escaped 'escaped\\tstring'", ( " long 'this is very very very " - "very very very very very very very very very very..." + "very very very very very very very very very very ...'" ), ( " long_multi 'multi\\nline, " - "this is very very very very very very very very very very..." + "this is very very very very very very very very very very ...'" ), ] self.assertEqual(rep, expected) @@ -488,7 +488,7 @@ def test_section_cube_attributes__array(self): " array array([1.2, 3.4])", ( " bigarray array([[ 0, 1], [ 2, 3], " - "[ 4, 5], [ 6, 7], [ 8, 9], [10, 11], [12, 13],..." + "[ 4, 5], [ 6, 7], [ 8, 9], [10, 11], [12, 13], ..." ), ] self.assertEqual(rep, expected) @@ -528,6 +528,9 @@ def test_unstructured_cube(self): " longitude - x", " Auxiliary coordinates:", " mesh_face_aux - x", + " Mesh:", + " name unknown", + " location face", ] self.assertEqual(rep, expected) diff --git a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py index 3e411c020d..8314c5c9ae 100644 --- a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py +++ b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py @@ -72,6 +72,7 @@ def test_blank_cube(self): self.assertTrue(vector_section.is_empty()) expected_scalar_sections = [ + "Mesh:", "Scalar coordinates:", "Scalar cell measures:", "Cell methods:", @@ -221,7 +222,7 @@ def test_scalar_cube(self): self.assertTrue( all(sect.is_empty() for sect in rep.vector_sections.values()) ) - self.assertEqual(len(rep.scalar_sections), 4) + self.assertEqual(len(rep.scalar_sections), 5) self.assertEqual( len(rep.scalar_sections["Scalar coordinates:"].contents), 1 ) From 8aa57d7451ac0a8b691a8988a48956feac377381 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 15 Jun 2022 14:48:33 +0100 Subject: [PATCH 129/319] Retire use of the New labels. (#4801) --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- .github/ISSUE_TEMPLATE/documentation.md | 2 +- .github/ISSUE_TEMPLATE/feature-request.md | 1 - .github/ISSUE_TEMPLATE/issue.md | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 5f65470c82..134b6ff8da 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,7 +2,7 @@ name: "\U0001F41B Bug Report" about: Submit a bug report to help us improve Iris title: '' -labels: 'New: Issue, Type: Bug' +labels: 'Type: Bug' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md index 8caa62a1c7..01eb2a6734 100644 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -2,7 +2,7 @@ name: "\U0001F4DA Documentation" about: Report an issue with the Iris documentation title: '' -labels: 'New: Documentation, Type: Documentation' +labels: 'Type: Documentation' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index b17b6066e4..2f66321405 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -2,7 +2,6 @@ name: "✨ Feature Request" about: Submit a request for a new feature in Iris title: '' -labels: 'New: Feature' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md index e66042609c..63de163743 100644 --- a/.github/ISSUE_TEMPLATE/issue.md +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -2,7 +2,6 @@ name: "\U0001F4F0 Custom Issue" about: Submit a generic issue to help us improve Iris title: '' -labels: 'New: Issue' assignees: '' --- From cb94a2ecef9247c9b5ae3a853dfafafd5062cfd8 Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Thu, 16 Jun 2022 09:56:18 +0100 Subject: [PATCH 130/319] pinned pydata-sphinx-theme until the docs are dark mode friendly. (#4796) --- requirements/ci/py38.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 62eb936506..b79d4638f3 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -47,4 +47,4 @@ dependencies: - sphinx-copybutton - sphinx-gallery - sphinx-panels - - pydata-sphinx-theme + - pydata-sphinx-theme = 0.8.1 From 724f1b9fc65b387bc5ccbf5500d174893b9d5755 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 17 Jun 2022 10:38:10 +0100 Subject: [PATCH 131/319] adopt ci gha (#4503) * adopt ci gha * bump iris-test-data to latest version * ci noxify * remove dummy cache key name * remove precommit nox task * added whatsnew entry * remove mpl cfg for linkcheck * trial composite nox-cache * composite iris-data-cache action * composite conda-env-cache action * composite conda-pkg-cache action * composite cartopy-cache action * required lock-file composite action input * review action * review action * nox inputs.lock_file * env_name composite action input * replace HOME with tilde usage * cancel in-progress workflow jobs * refresh-lockfiles gha note --- .cirrus.yml | 198 ------------------ .github/workflows/ci-docs-linkcheck.yml | 94 +++++++++ .github/workflows/ci-docs-tests.yml | 127 +++++++++++ .github/workflows/ci-tests.yml | 122 +++++++++++ .../composite/cartopy-cache/action.yml | 34 +++ .../composite/conda-env-cache/action.yml | 35 ++++ .../composite/conda-pkg-cache/action.yml | 22 ++ .../composite/iris-data-cache/action.yml | 30 +++ .../workflows/composite/nox-cache/action.yml | 22 ++ .github/workflows/refresh-lockfiles.yml | 6 +- docs/src/whatsnew/latest.rst | 5 +- noxfile.py | 35 ---- 12 files changed, 495 insertions(+), 235 deletions(-) delete mode 100644 .cirrus.yml create mode 100644 .github/workflows/ci-docs-linkcheck.yml create mode 100644 .github/workflows/ci-docs-tests.yml create mode 100644 .github/workflows/ci-tests.yml create mode 100644 .github/workflows/composite/cartopy-cache/action.yml create mode 100644 .github/workflows/composite/conda-env-cache/action.yml create mode 100644 .github/workflows/composite/conda-pkg-cache/action.yml create mode 100644 .github/workflows/composite/iris-data-cache/action.yml create mode 100644 .github/workflows/composite/nox-cache/action.yml diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index cbc8a146ef..0000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,198 +0,0 @@ -# Reference: -# - https://cirrus-ci.org/guide/writing-tasks/ -# - https://cirrus-ci.org/guide/writing-tasks/#environment-variables -# - https://cirrus-ci.org/guide/tips-and-tricks/#sharing-configuration-between-tasks -# - https://cirrus-ci.org/guide/linux/ -# - https://hub.docker.com/_/gcc/ -# - https://hub.docker.com/_/python/ - -# -# Global defaults. -# -container: - image: gcc:latest - cpu: 2 - memory: 4G - - -env: - # Skip specific tasks by name. Set to a non-empty string to skip. - SKIP_LINT_TASK: "" - SKIP_TEST_TASK: "" - SKIP_DOCTEST_TASK: "" - SKIP_LINKCHECK_TASK: "" - # Skip task groups by type. Set to a non-empty string to skip. - SKIP_ALL_DOC_TASKS: "" - # Maximum cache period (in weeks) before forcing a new cache upload. - CACHE_PERIOD: "2" - # Increment the build number to force new cartopy cache upload. - CARTOPY_CACHE_BUILD: "3" - # Increment the build number to force new conda cache upload. - CONDA_CACHE_BUILD: "0" - # Increment the build number to force new nox cache upload. - NOX_CACHE_BUILD: "2" - # Increment the build number to force new pip cache upload. - PIP_CACHE_BUILD: "0" - # Pip packages to be upgraded/installed. - PIP_CACHE_PACKAGES: "nox pip pyyaml setuptools wheel" - # Conda packages to be installed. - CONDA_CACHE_PACKAGES: "nox pip" - # Git commit hash for iris test data. - IRIS_TEST_DATA_VERSION: "2.9" - # Base directory for the iris-test-data. - IRIS_TEST_DATA_DIR: ${HOME}/iris-test-data - - -# -# YAML alias for common linux test infra-structure. -# -linux_task_template: &LINUX_TASK_TEMPLATE - auto_cancellation: true - env: - PATH: ${HOME}/miniconda/bin:${PATH} - SITE_CFG: ${CIRRUS_WORKING_DIR}/lib/iris/etc/site.cfg - conda_cache: - folder: ${HOME}/miniconda - fingerprint_script: - - wget --quiet https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - - echo "${CIRRUS_OS} $(sha256sum miniconda.sh)" - - echo "${CONDA_CACHE_PACKAGES}" - - echo "$(date +%Y).$(expr $(date +%U) / ${CACHE_PERIOD}):${CONDA_CACHE_BUILD}" - - uname -r - populate_script: - - bash miniconda.sh -b -p ${HOME}/miniconda - - conda config --set always_yes yes --set changeps1 no - - conda config --set show_channel_urls True - - conda config --add channels conda-forge - - conda update --quiet --name base conda - - conda install --quiet --name base ${CONDA_CACHE_PACKAGES} - cartopy_cache: - folder: ${HOME}/.local/share/cartopy - fingerprint_script: - - echo "${CIRRUS_OS}" - - echo "$(date +%Y).$(expr $(date +%U) / ${CACHE_PERIOD}):${CARTOPY_CACHE_BUILD}" - populate_script: - - conda create --quiet --name cartopy-cache cartopy - - source ${HOME}/miniconda/etc/profile.d/conda.sh >/dev/null 2>&1 - - conda activate cartopy-cache >/dev/null 2>&1 - - cd $(mktemp -d) - - wget --quiet https://raw.githubusercontent.com/SciTools/cartopy/v0.20.0/tools/cartopy_feature_download.py - - python cartopy_feature_download.py physical --output ${HOME}/.local/share/cartopy --no-warn - - conda deactivate >/dev/null 2>&1 - nox_cache: - folder: ${CIRRUS_WORKING_DIR}/.nox - reupload_on_changes: true - fingerprint_script: - - echo "${CIRRUS_TASK_NAME}" - - echo "${NOX_CACHE_BUILD}" - - -# -# YAML alias for compute credits. -# -compute_credits_template: &CREDITS_TEMPLATE - # Restrict where compute credits are used. - use_compute_credits: ${CIRRUS_REPO_FULL_NAME} == "SciTools/iris" && ${CIRRUS_USER_COLLABORATOR} == "true" && ${CIRRUS_PR_DRAFT} == "false" && ${CIRRUS_PR} != "" - - -# -# YAML alias for the iris-test-data cache. -# -iris_test_data_template: &IRIS_TEST_DATA_TEMPLATE - data_cache: - folder: ${IRIS_TEST_DATA_DIR} - fingerprint_script: - - echo "iris-test-data v${IRIS_TEST_DATA_VERSION}" - populate_script: - - wget --quiet https://github.com/SciTools/iris-test-data/archive/v${IRIS_TEST_DATA_VERSION}.zip -O iris-test-data.zip - - unzip -q iris-test-data.zip - - mv iris-test-data-${IRIS_TEST_DATA_VERSION} ${IRIS_TEST_DATA_DIR} - - -# -# Linting -# -task: - only_if: ${SKIP_LINT_TASK} == "" - << : *CREDITS_TEMPLATE - auto_cancellation: true - container: - image: python:3.8 - cpu: 2 - memory: 4G - name: "${CIRRUS_OS}: pre-commit hooks" - pip_cache: - folder: ~/.cache/pip - fingerprint_script: - - echo "${CIRRUS_TASK_NAME} py${PYTHON_VERSION}" - - echo "$(date +%Y).$(expr $(date +%U) / ${CACHE_PERIOD}):${PIP_CACHE_BUILD} ${PIP_CACHE_PACKAGES}" - precommit_script: - - pip list - - python -m pip install --retries 3 --upgrade ${PIP_CACHE_PACKAGES} - - pip list - - nox --session precommit - - -# -# Testing (Linux) -# -task: - only_if: ${SKIP_TEST_TASK} == "" - << : *CREDITS_TEMPLATE - matrix: - env: - PY_VER: 3.8 - name: "${CIRRUS_OS}: py${PY_VER} tests" - container: - image: gcc:latest - cpu: 6 - memory: 8G - << : *IRIS_TEST_DATA_TEMPLATE - << : *LINUX_TASK_TEMPLATE - tests_script: - - echo "[Resources]" > ${SITE_CFG} - - echo "test_data_dir = ${IRIS_TEST_DATA_DIR}/test_data" >> ${SITE_CFG} - - echo "doc_dir = ${CIRRUS_WORKING_DIR}/docs" >> ${SITE_CFG} - - nox --session tests -- --verbose - - -# -# Documentation Testing and Gallery (Linux) -# -task: - only_if: ${SKIP_DOCTEST_TASK} == "" && ${SKIP_ALL_DOC_TASKS} == "" - << : *CREDITS_TEMPLATE - env: - PY_VER: 3.8 - MPL_RC_DIR: ${HOME}/.config/matplotlib - MPL_RC_FILE: ${HOME}/.config/matplotlib/matplotlibrc - name: "${CIRRUS_OS}: py${PY_VER} doctests and gallery" - << : *IRIS_TEST_DATA_TEMPLATE - << : *LINUX_TASK_TEMPLATE - tests_script: - - echo "[Resources]" > ${SITE_CFG} - - echo "test_data_dir = ${IRIS_TEST_DATA_DIR}/test_data" >> ${SITE_CFG} - - echo "doc_dir = ${CIRRUS_WORKING_DIR}/docs" >> ${SITE_CFG} - - mkdir -p ${MPL_RC_DIR} - - echo "backend : agg" > ${MPL_RC_FILE} - - echo "image.cmap : viridis" >> ${MPL_RC_FILE} - - nox --session doctest -- --verbose - - -# -# Documentation Link Check (Linux) -# -task: - only_if: ${SKIP_LINKCHECK_TASK} == "" && ${SKIP_ALL_DOC_TASKS} == "" - << : *CREDITS_TEMPLATE - env: - PY_VER: 3.8 - MPL_RC_DIR: ${HOME}/.config/matplotlib - MPL_RC_FILE: ${HOME}/.config/matplotlib/matplotlibrc - name: "${CIRRUS_OS}: py${PY_VER} link check" - << : *LINUX_TASK_TEMPLATE - tests_script: - - mkdir -p ${MPL_RC_DIR} - - echo "backend : agg" > ${MPL_RC_FILE} - - echo "image.cmap : viridis" >> ${MPL_RC_FILE} - - nox --session linkcheck -- --verbose diff --git a/.github/workflows/ci-docs-linkcheck.yml b/.github/workflows/ci-docs-linkcheck.yml new file mode 100644 index 0000000000..cf67e99129 --- /dev/null +++ b/.github/workflows/ci-docs-linkcheck.yml @@ -0,0 +1,94 @@ +# reference: +# - https://github.com/actions/cache +# - https://github.com/actions/checkout +# - https://github.com/marketplace/actions/setup-miniconda + +name: ci-docs-linkcheck + +on: + push: + branches: + - "main" + - "v*x" + tags: + - "v*" + pull_request: + branches: + - "*" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests: + name: "linkcheck ${{ matrix.os }} ${{ matrix.python-version }}" + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash -l {0} + + strategy: + matrix: + os: ["ubuntu-latest"] + python-version: ["3.8"] + + env: + ENV_NAME: "ci-docs-linkcheck" + + steps: + - name: "checkout" + uses: actions/checkout@v3 + + - name: "environment configure" + env: + # Maximum cache period (in weeks) before forcing a cache refresh. + CACHE_WEEKS: 2 + run: | + echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} + echo "LOCK_FILE=requirements/ci/nox.lock/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} + + - name: "conda package cache" + uses: ./.github/workflows/composite/conda-pkg-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + + - name: "conda install" + uses: conda-incubator/setup-miniconda@v2 + with: + miniforge-version: latest + channels: conda-forge,defaults + activate-environment: ${{ env.ENV_NAME }} + auto-update-conda: false + use-only-tar-bz2: true + + - name: "conda environment cache" + uses: ./.github/workflows/composite/conda-env-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + install_packages: "nox pip" + + - name: "conda info" + run: | + conda info + conda list + + - name: "nox cache" + uses: ./.github/workflows/composite/nox-cache + with: + cache_build: 0 + env_name: ${{ env.ENV_NAME }} + lock_file: ${{ env.LOCK_FILE }} + + - name: "iris linkcheck" + env: + PY_VER: ${{ matrix.python-version }} + run: | + nox --session linkcheck -- --verbose diff --git a/.github/workflows/ci-docs-tests.yml b/.github/workflows/ci-docs-tests.yml new file mode 100644 index 0000000000..faadf56204 --- /dev/null +++ b/.github/workflows/ci-docs-tests.yml @@ -0,0 +1,127 @@ +# reference: +# - https://github.com/actions/cache +# - https://github.com/actions/checkout +# - https://github.com/marketplace/actions/setup-miniconda + +name: ci-docs-tests + +on: + push: + branches: + - "main" + - "v*x" + tags: + - "v*" + pull_request: + branches: + - "*" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests: + name: "doctests ${{ matrix.os }} ${{ matrix.python-version }}" + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash -l {0} + + strategy: + matrix: + os: ["ubuntu-latest"] + python-version: ["3.8"] + + env: + IRIS_TEST_DATA_VERSION: "2.9" + ENV_NAME: "ci-docs-tests" + + steps: + - name: "checkout" + uses: actions/checkout@v3 + + - name: "environment configure" + env: + # Maximum cache period (in weeks) before forcing a cache refresh. + CACHE_WEEKS: 2 + run: | + echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} + echo "LOCK_FILE=requirements/ci/nox.lock/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} + + - name: "data cache" + uses: ./.github/workflows/composite/iris-data-cache + with: + cache_build: 0 + env_name: ${{ env.ENV_NAME }} + version: ${{ env.IRIS_TEST_DATA_VERSION }} + + - name: "conda package cache" + uses: ./.github/workflows/composite/conda-pkg-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + + - name: "conda install" + uses: conda-incubator/setup-miniconda@v2 + with: + miniforge-version: latest + channels: conda-forge,defaults + activate-environment: ${{ env.ENV_NAME }} + auto-update-conda: false + use-only-tar-bz2: true + + - name: "conda environment cache" + uses: ./.github/workflows/composite/conda-env-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + install_packages: "cartopy nox pip" + + - name: "conda info" + run: | + conda info + conda list + + - name: "cartopy cache" + uses: ./.github/workflows/composite/cartopy-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + + - name: "nox cache" + uses: ./.github/workflows/composite/nox-cache + with: + cache_build: 0 + env_name: ${{ env.ENV_NAME }} + lock_file: ${{ env.LOCK_FILE }} + + # TODO: drop use of site.cfg and explicit use of mplrc + - name: "iris configure" + env: + SITE_CFG: lib/iris/etc/site.cfg + MPL_RC: ${HOME}/.config/matplotlib/matplotlibrc + run: | + mkdir -p $(dirname ${SITE_CFG}) + echo ${SITE_CFG} + echo "[Resources]" >> ${SITE_CFG} + echo "test_data_dir = ${HOME}/iris-test-data/test_data" >> ${SITE_CFG} + echo "doc_dir = ${GITHUB_WORKSPACE}/docs" >> ${SITE_CFG} + cat ${SITE_CFG} + mkdir -p $(dirname ${MPL_RC}) + echo ${MPL_RC} + echo "backend : agg" >> ${MPL_RC} + echo "image.cmap : viridis" >> ${MPL_RC} + cat ${MPL_RC} + + - name: "iris doctests and gallery" + env: + PY_VER: ${{ matrix.python-version }} + run: | + nox --session doctest -- --verbose diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml new file mode 100644 index 0000000000..fbe738067c --- /dev/null +++ b/.github/workflows/ci-tests.yml @@ -0,0 +1,122 @@ +# reference: +# - https://github.com/actions/cache +# - https://github.com/actions/checkout +# - https://github.com/marketplace/actions/setup-miniconda + +name: ci-tests + +on: + push: + branches: + - "main" + - "v*x" + tags: + - "v*" + pull_request: + branches: + - "*" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + tests: + name: "tests ${{ matrix.os }} ${{ matrix.python-version }}" + + runs-on: ${{ matrix.os }} + + defaults: + run: + shell: bash -l {0} + + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest"] + python-version: ["3.8"] + + env: + IRIS_TEST_DATA_VERSION: "2.9" + ENV_NAME: "ci-tests" + + steps: + - name: "checkout" + uses: actions/checkout@v3 + + - name: "environment configure" + env: + # Maximum cache period (in weeks) before forcing a cache refresh. + CACHE_WEEKS: 2 + run: | + echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} + echo "LOCK_FILE=requirements/ci/nox.lock/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} + + - name: "data cache" + uses: ./.github/workflows/composite/iris-data-cache + with: + cache_build: 0 + env_name: ${{ env.ENV_NAME }} + version: ${{ env.IRIS_TEST_DATA_VERSION }} + + - name: "conda package cache" + uses: ./.github/workflows/composite/conda-pkg-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + + - name: "conda install" + uses: conda-incubator/setup-miniconda@v2 + with: + miniforge-version: latest + channels: conda-forge,defaults + activate-environment: ${{ env.ENV_NAME }} + auto-update-conda: false + use-only-tar-bz2: true + + - name: "conda environment cache" + uses: ./.github/workflows/composite/conda-env-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + install_packages: "cartopy nox pip" + + - name: "conda info" + run: | + conda info + conda list + + - name: "cartopy cache" + uses: ./.github/workflows/composite/cartopy-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + + - name: "nox cache" + uses: ./.github/workflows/composite/nox-cache + with: + cache_build: 0 + env_name: ${{ env.ENV_NAME }} + lock_file: ${{ env.LOCK_FILE }} + + # TODO: drop use of site.cfg and explicit use of mplrc + - name: "iris configure" + env: + SITE_CFG: lib/iris/etc/site.cfg + run: | + mkdir -p $(dirname ${SITE_CFG}) + echo ${SITE_CFG} + echo "[Resources]" >> ${SITE_CFG} + echo "test_data_dir = ${HOME}/iris-test-data/test_data" >> ${SITE_CFG} + echo "doc_dir = ${GITHUB_WORKSPACE}/docs" >> ${SITE_CFG} + cat ${SITE_CFG} + + - name: "iris tests" + env: + PY_VER: ${{ matrix.python-version }} + run: | + nox --session tests -- --verbose diff --git a/.github/workflows/composite/cartopy-cache/action.yml b/.github/workflows/composite/cartopy-cache/action.yml new file mode 100644 index 0000000000..35a90736bc --- /dev/null +++ b/.github/workflows/composite/cartopy-cache/action.yml @@ -0,0 +1,34 @@ +name: "cartopy-cache" +description: "create and cache cartopy assets" + +inputs: + cache_build: + description: "conda environment cache build number" + required: false + default: "0" + cache_period: + description: "conda environment cache timestamp" + required: true + env_name: + description: "environment name" + required: true + +runs: + using: "composite" + steps: + - uses: actions/cache@v3 + id: cartopy-cache + with: + path: ~/.local/share/cartopy + key: ${{ runner.os }}-cartopy-${{ inputs.env_name }}-p${{ inputs.cache_period }}-b${{ inputs.cache_build }} + + - if: steps.cartopy-cache.outputs.cache-hit != 'true' + env: + CARTOPY_SHARE_DIR: ~/.local/share/cartopy + CARTOPY_FEATURE: https://raw.githubusercontent.com/SciTools/cartopy/v0.20.0/tools/cartopy_feature_download.py + shell: bash + run: | + wget --quiet ${CARTOPY_FEATURE} + mkdir -p ${CARTOPY_SHARE_DIR} + # Requires a pre-installed version of cartopy within the environment. + python cartopy_feature_download.py physical --output ${CARTOPY_SHARE_DIR} --no-warn diff --git a/.github/workflows/composite/conda-env-cache/action.yml b/.github/workflows/composite/conda-env-cache/action.yml new file mode 100644 index 0000000000..6bfd6fff90 --- /dev/null +++ b/.github/workflows/composite/conda-env-cache/action.yml @@ -0,0 +1,35 @@ +name: "conda-env-cache" +description: "create and cache the conda environment" + +# +# Assumes the environment contains the following variables: +# - CONDA +# +inputs: + cache_build: + description: "conda environment cache build number" + required: false + default: "0" + cache_period: + description: "conda environment cache timestamp" + required: true + env_name: + description: "environment name" + required: true + install_packages: + description: "conda packages to install into environment" + required: true + +runs: + using: "composite" + steps: + - uses: actions/cache@v3 + id: conda-env-cache + with: + path: ${{ env.CONDA }}/envs/${{ inputs.env_name }} + key: ${{ runner.os }}-conda-env-${{ inputs.env_name }}-p${{ inputs.cache_period }}-b${{ inputs.cache_build }} + + - if: steps.conda-env-cache.outputs.cache-hit != 'true' + shell: bash + run: | + conda install --quiet --name ${{ inputs.env_name }} ${{ inputs.install_packages }} diff --git a/.github/workflows/composite/conda-pkg-cache/action.yml b/.github/workflows/composite/conda-pkg-cache/action.yml new file mode 100644 index 0000000000..4472d7e415 --- /dev/null +++ b/.github/workflows/composite/conda-pkg-cache/action.yml @@ -0,0 +1,22 @@ +name: "conda-pkg-cache" +description: "cache the conda environment packages" + +inputs: + cache_build: + description: "conda environment cache build number" + required: false + default: "0" + cache_period: + description: "conda environment cache timestamp" + required: true + env_name: + description: "environment name" + required: true + +runs: + using: "composite" + steps: + - uses: actions/cache@v3 + with: + path: ~/conda_pkgs_dir + key: ${{ runner.os }}-conda-pkgs-${{ inputs.env_name }}-p${{ inputs.cache_period }}-b${{ inputs.cache_build }} diff --git a/.github/workflows/composite/iris-data-cache/action.yml b/.github/workflows/composite/iris-data-cache/action.yml new file mode 100644 index 0000000000..7bf72fae8b --- /dev/null +++ b/.github/workflows/composite/iris-data-cache/action.yml @@ -0,0 +1,30 @@ +name: "iris-data-cache" +description: "create and cache the iris test data" + +inputs: + cache_build: + description: "data cache build number" + required: false + default: "0" + env_name: + description: "environment name" + required: true + version: + description: "iris test data version" + required: true + +runs: + using: "composite" + steps: + - uses: actions/cache@v3 + id: data-cache + with: + path: ~/iris-test-data + key: ${{ runner.os }}-iris-test-data-${{ inputs.env_name }}-v${{ inputs.version }}-b${{ inputs.cache_build }} + + - if: steps.data-cache.outputs.cache-hit != 'true' + shell: bash + run: | + wget --quiet https://github.com/SciTools/iris-test-data/archive/v${{ inputs.version }}.zip -O iris-test-data.zip + unzip -q iris-test-data.zip + mv iris-test-data-${{ inputs.version }} ~/iris-test-data diff --git a/.github/workflows/composite/nox-cache/action.yml b/.github/workflows/composite/nox-cache/action.yml new file mode 100644 index 0000000000..9d92ad7226 --- /dev/null +++ b/.github/workflows/composite/nox-cache/action.yml @@ -0,0 +1,22 @@ +name: "nox cache" +description: "cache the nox test environments" + +inputs: + cache_build: + description: "nox cache build number" + required: false + default: "0" + env_name: + description: "environment name" + required: true + lock_file: + description: "conda-lock environment requirements filename" + required: true + +runs: + using: "composite" + steps: + - uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/.nox + key: ${{ runner.os }}-nox-${{ inputs.env_name }}-py${{ matrix.python-version }}-b${{ inputs.cache_build }}-${{ hashFiles(inputs.lock_file) }} diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 1f41c8c265..f93858e03d 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -93,7 +93,11 @@ jobs: body: | Lockfiles updated to the latest resolvable environment. - If the CI test suite fails, create a new branch based of this pull request and add the required fixes to that branch. + ### If the CI tasks fail, create a new branch based on this PR and add the required fixes to that branch. + + ### If the PR CI tasks have not run, please close and re-open this PR to trigger them. + + Reference: create-pull-request GHA [triggering further workflow runs](https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs) labels: | New: Pull Request Bot diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index e7edafb58d..ff737d104a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -210,6 +210,9 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ made :func:`iris.tests.stock.simple_1d` respect the ``with_bounds`` argument. (:pull:`4658`) +#. `@bjlittle`_ migrated to GitHub Actions for Continuous-Integration. + (:pull:`4503`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, @@ -222,4 +225,4 @@ This document explains the changes made to Iris for this release Whatsnew resources in alphabetical order: .. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries -.. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html \ No newline at end of file +.. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html diff --git a/noxfile.py b/noxfile.py index 7919dfdfde..0de7b3a63c 100755 --- a/noxfile.py +++ b/noxfile.py @@ -173,41 +173,6 @@ def prepare_venv(session: nox.sessions.Session) -> None: ) -@nox.session -def precommit(session: nox.sessions.Session): - """ - Perform pre-commit hooks of iris codebase. - - Parameters - ---------- - session: object - A `nox.sessions.Session` object. - - """ - import yaml - - # Pip install the session requirements. - session.install("pre-commit") - - # Load the pre-commit configuration YAML file. - with open(".pre-commit-config.yaml", "r") as fi: - config = yaml.load(fi, Loader=yaml.FullLoader) - - # List of pre-commit hook ids that we don't want to run. - excluded = ["no-commit-to-branch"] - - # Enumerate the ids of pre-commit hooks we do want to run. - ids = [ - hook["id"] - for entry in config["repos"] - for hook in entry["hooks"] - if hook["id"] not in excluded - ] - - # Execute the pre-commit hooks. - [session.run("pre-commit", "run", "--all-files", id) for id in ids] - - @nox.session(python=PY_VER, venv_backend="conda") def tests(session: nox.sessions.Session): """ From 78437b0a703a49d407b939c3d663e31ff9a3e898 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 17 Jun 2022 11:31:07 +0100 Subject: [PATCH 132/319] Scripted Logo Generation, take two (#3935) * Added logo generation Python script. * Replace lambda scale_func.n * Matched current logo appearance. * Simplified gradient code. * Renamed land_mask to land_clip. * Make new svg instead of faffing with reformatting existing. * Refactor out 'style' in attributes. * File header. * Integrated logo generation script into Sphinx docs generation. * Environment variable logo names. * Removed banner width calculation to avoid font dependency. * Refactored to also generate rotating logos. * Replace plt.tightlayout with plt.subplots_adjust. * Replaced OrderedDicts with dicts (not needed >=Py3.6). * Numpy standard import form. * Replaced animated SVG with precusor files for animated GIF. * Corrected logo URL to source from ReadTheDocs site. * Changes to better match 'hand-created' logo. * Fixed logo rotate zip title. * Relocated logo generation script to SciTools/marketing. * update links (#3942) * update links * added s to http * Added logo generation Python script. * Replace lambda scale_func.n * Matched current logo appearance. * Simplified gradient code. * Renamed land_mask to land_clip. * Make new svg instead of faffing with reformatting existing. * Refactor out 'style' in attributes. * File header. * Integrated logo generation script into Sphinx docs generation. * Environment variable logo names. * Removed banner width calculation to avoid font dependency. * Refactored to also generate rotating logos. * Replace plt.tightlayout with plt.subplots_adjust. * Replaced OrderedDicts with dicts (not needed >=Py3.6). * Numpy standard import form. * Replaced animated SVG with precusor files for animated GIF. * Corrected logo URL to source from ReadTheDocs site. * Changes to better match 'hand-created' logo. * Fixed logo rotate zip title. * Relocated logo generation script to SciTools/marketing. * Include version string in logo generation. * Reference official logo generation code. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix README docs reference. * Include the logos, don't generate on the fly. Co-authored-by: Ruth Comer Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- README.md | 2 +- docs/src/_static/Iris7_1_trim_100.png | Bin 122042 -> 0 bytes docs/src/_static/Iris7_1_trim_full.png | Bin 2383177 -> 0 bytes docs/src/_static/README.md | 31 ++++ docs/src/_static/favicon.ico | Bin 1150 -> 0 bytes docs/src/_static/iris-logo-title.png | Bin 38785 -> 0 bytes docs/src/_static/iris-logo-title.svg | 197 ++++++++++++++----------- docs/src/_static/iris-logo.svg | 104 +++++++++++++ docs/src/conf.py | 4 +- 9 files changed, 245 insertions(+), 93 deletions(-) delete mode 100644 docs/src/_static/Iris7_1_trim_100.png delete mode 100644 docs/src/_static/Iris7_1_trim_full.png create mode 100644 docs/src/_static/README.md delete mode 100644 docs/src/_static/favicon.ico delete mode 100644 docs/src/_static/iris-logo-title.png create mode 100644 docs/src/_static/iris-logo.svg diff --git a/README.md b/README.md index 1b2a7b496f..ec1ce1185e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Iris
+ Iris

diff --git a/docs/src/_static/Iris7_1_trim_100.png b/docs/src/_static/Iris7_1_trim_100.png deleted file mode 100644 index 2f6f80eff92128c6ce765c4e51866c5400014c56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122042 zcmZr%cQ{+^`%fZ>y=PIoiM>mWCRVKyu~!>PQEC?*h}e4tHA?JJTaDUVsZqPbRz>Zi zHHu%}_x=9)yRLI`a?W*M=bY#B+~afK&wZkh`uC{F*~tL_0F}0uItl;)mi)6rNC;OL z_Fumy9Dv@ad#Zq{F^(<53D^;F9{~W=CQ$sfB_^Dcd1zU90|3;$|Lnj)_fmVpMOGgT za~~u3$3A|xUJd{cTW2>PF*gSvZW%F2G08_l*}4G0b+EQN!r1@8PCKc8s+sS;a@p;n zHzWSm09kdq{IX9maAI{j)zZAhm;0RHNTC<|?d(|(C`t5g7k?gTAL)BPG@mukfBVlz z{@X*NZ}_EupU-|>Z@Xx7%;n{=9Y5XKIJ?_po_&2@24VVlI^wtG~3 zwPirrKCVp{C@FiB*Sw2Q=H`P;994j-*GlS;!p``@#K%+jnel@tD0YI3O2R{PYd8It z<3sio^PsZ86P~||<+j1zA4#9>ym*!w0yqnR?Ztpgd!v= z=!8<(U-0xe6oTdlt-kIf@psVKd(+kYRgo?3hReBuIH%#4+VO|k*#EufrQEwIv%TM0 zxJ4)Ylc_tu6a_7b%g_X?1MhC%e&EBUGDY6~=*gZL-TU2Kni!$n z)K>HqE}rn_OeK5&o0+ACv@gvHc5Xw_xm1}JzeYc1E?<2sCP09=-qL`)ZOe6Ra=p^+ zv?!8R^nLTJ+Uh@y16VR28lMOo3Vgg8xs|_s!-n>{W0T3pbdm1tnw`V!VoAC8=+T|V z_B(3Uy#K@O<%6f!W!vKM@zqcNKt?ZFY-+AlVyu~Z&bPZh|9lv_{!ZN0{G~j~TJC@H z+m*9HPtFW92aE$W+z>3;3w3GtoEGOE(QjYN%I7t@9nLaL^*Q+bJ0IRnIZtZW-<%OS z;{Ha6L0Z?X319|tCQV)Kr=T*+Q&J4BbDAsur;W1bk4@ql_LIL}+C<9C+LV%^GS|HNwY z|3tG2W*v3(dvxWe=7>$3UYG`y{OCD&Z{qmzj_*wKRN#LP_RZWs#-66DK|LO~oN#D} z$)X>e+y(c~$;E0_o#tKGKKyUs6FuI{0?)(OokL2j#_x&ek&HnFk?kt71y4WG%qMyg z#@n>-ZS{U*-n#RP!&l7KY*RsC-uq?c%4NH?uWFT?Hw@aLZmIw3*tb6g0!@3DwSMZ| zEG+KC`MJsGmiylg-5=^!Jv0iDK63Zz{#3bN>|`^{C$jH;xq)knjdS);g#BwByHCjC zwl63N>a^bi*qrry_`UG_{^;d5gmjDxcJ0YS+NPO>`O$^>-P^5H*@kUBZ^kZ0Sr*h1 zKFxRj$+AH)=gc3fdA?jANQ^E79$7Bti%#U&KHu|MbWehyROrq+A+eRBW4rQy@2#5t zHv^YH-bIzgU9a_Pv$0OkpFd?#R^PqbdYtRX;>WnV(E5j_Pt@-}%XMo?9BH%&C_&MM zsx-ep`Icu^Y>&A9&~xFflYXDBY4N`oYyE~@bGd32drgoiGgFo7%*pB8{GBH~r>4&0 z@xL229E{9H2IeVz<^D12-b6A2J%9YTdzEB@>7SvEE_5h99r@c*?Gya5azBEgI^gPq z`KHOAv+)czJ^wxA+fz@6kE-o_zZwt(nRKNdFP>u8v#9U!Q$f&{i2nd@nz9o%3(Ti5 zb_=+BGWlKa_3N+44BQ=+7GrDE1&a9Fl>%CHCLvSnW5)PnBGMy2ZP{#&WCZ z5jR2V;)MeL(an+#(fQvY-|hV}$Nzcj7fo^ZU0Hvy>94PyV~#7Gq{coQiU$nxC+NN$ zSz<8MSSk(uS`uaQHatq~`@bG$_DEDJXhM>_`EGaDR8}3>*Nh1Z_b@rV7#CUC$W$Nf z3+>!5_nBM0Z3Bi?J3qgcfwe1e00Qs%2>fdfO*zi*fTd>YSEMa~X}|{sY0R_vmdU zKfrEnF@=&%k^-xhHrP$}^$oXv$f+r^>(Ofd-5W2$WMsF`v|i<&v3~Z+HPS^DCw9BEuJysDVnCRz#9Gid(nOvc zo7{j08JSNfKK{I>=;KeX?%V12=)eb4zuqCjqp*1<9^dOJa6A7@nPcmXON{?^c4>FzDunh9OD|p!c(-SW;s699i z9~ciFi+C?B?6GoPaMWZ?)27M)yuI&48ZS^??Kvvy!Mn9LcW9X4#b^VmkOApr9L<9%!z5zp zUj;StYpWSORNmZN6w~}&xFhslY_~2Z2KZVXDd@B!Yqv~4 z7sZe0uPOrxw=Mqqs_13<82*YyMzs2J|Km5m^T_{9XYYsQQS1lh^Av4o*7@%{)>_8d z?V&+}?Xkd#eAsdF9i8zcO725U3^|`C_lX0n*_HBh53!^pulfNU;FlfmGV8A#m`RuH zTiUR@jx5-O)ad4Xt<$B``p$9%_Kd)YORF=5VaF-I=i|&vPZg-esAW9cRykPpzC4F* zL?v&GPerb_M7pTY#a40JjH~Qe4!h{$SLduAyd*-ca@{X}={SG4C4EVtJ@Qws(RpY1 zfa!~9v5o&?d-F4njssQoMG!^ogF1Q|tQ54P2NOzY!5aLBr3TwjPZu?qO)4!J(?;M29!KaQ4eBf*NXP!K2)5&UbH;6>6j?qHv>3! zRRCUr-<>M(7^ix#Qzt$o%~6r5V(?4;Uh}Svpi@YoaCbc z;aiCnkx*rU17|Tw;3N;YA%vH%6z1d&$YXsK3icN{FvRo%_)^Wu)U=M)$Awsv)yseA z4BVAA3F&0KYFDdWdTY0u8myB} zRSjgF&j`uuG5~NNNNj3(;8Do383~yN%~Iy@;+Y-^)QdN?R|;xZY#Kqnh~Al{lJUfz z@#1~-l0Zj#d@v_PNDhE4Bm+|vnhs#`Mo$JLZPU-8@nGiRWUj&nDH0K&-;XN!V=D8* zm)&oe7H_uPGlju{Lbxx68FQtq*&PCK!-qHI3!Khq0+&htjdK}a7uyZ4xkXxcHz@TO z=gskJyxbG93w5Ua*>n_XAWbQads_-#V5Q11u}B48$}SytHxG_zZX9w9Bv0`JN`5fk zaDaCYP9W#RY6_}`mT`qUCb6T{8;$v5KhO;`*#)^tU42XX-ubb6ybf}071(jA@_S)b zUjBm?y}hi29#<>5IM$VyEH4XV=Y~|(Erc9eS}2OFRIyf^J`+TGnhCsNG9aG3T)|6C z2Fc;+oh@DN3BVn^jPE2Kr){#H>y7U8Rc-1Y{>!%}UiErkJ8@(CXL%0sj({jMZk+jI zh|};BZY&a87D`uCA1lkf6&}if;+ByKBlX;S@x)gCJqwc(7z~U|;tw6s3LX5Q^fx0x z2%kGji-bQ3#;Br%uPyk)t}Dx%??+x$Xk7*UW@BIR`{d`j@@G=AW{rmoFBv%_9T}HM z>kuE?D(>PGb)2WiCM4!U3z`R{iB_0L<0dg6(;u?JX;%d=p&Y3KC$~`V!V6aMbrttB zxQmFi-J^ugdc3}JakO|W*gW|+gJ_ejTDqMG$^z5t&PZ|}THG<1A@d9EPQvng-vjf3 zCX}|KKhWjf;FY`=3N$bN&KeooT7%K@RAHTG^G5=iPay|daBd}#HYBBsK5tTP8dN2v zv5ulRGsxOY zeaK`p^)FR>yL-IVdRN4%QFxk9L|Uf!m4$}p(RpsMT;-*+@3(}H!~bkI^6{?D3J5qvuBjwDi0+r`5Ti+>8gho$SP^U{UI*1O8MR})^wBtNO$&<2)y|E{A9q~RD97inUTpd6aZ~iH zQK5i>D+1kN8ysEuFbU=^cEnt`f=HX`h%HZzy3Qx_L0-rHx1^*~Uzx0$dS3F_FfH=^ zD7Uu_tJ8PzHLk#`2IDGDrK|5}@SL>G78h@b|Ls42l%Dhc;XjPoil#}qq>3ay7M9Co z^VM@fns>uJK%$uy$eS8fVmI0K(sJxBO|Dg)x7+hU?!)oc`B#qX)o3I=to5p%LgF)% z-6ypOnWopo2%QJNh(mKmq$c@B;OX$`FeE z^j?Avpk+d#z%NomSFSOW#H=P!7D%RZ#Tew)m0P8ArTE)%{pg^6OA-5|sL!u{4UXjL z!(3y*kQH@JS0(G+$y)FGk4>G~a4Ypl&)G?FTzvqWOc=KMn8u}DK z%WpDyN&HExxmT6aW`H90E)_cwQ~Cd;Kx6(&OC8W(d?YM{KUB6qSIQtQE9M9~Lk-`7%g#B(DZ84!Vow zW?kv+=CiE@V`;e%afQ^7Cq23Mj~Qm#7zKjJ8+;6Zb_xuQKkm6M-hUSvU(^eh1d1`V zv086&9KO;~@A<&=mFei3LL4J8Qpd3NODDzRGcu)nm5>`|j^*Z|5JhmEJ1}7R#!4YAveUeF z_lwcozZ9sj@~fYaK)L;4T2d|3^&AmVup8V`oAMPW4vMbnLP*e8o4J)DL$gJEmhCGtd`a(`EneEp{%zDP>+ep;CIQ^tY0NYGy6rnK>j zU*cy!+4@{A|Efj*DsXeRf1nzzYA>x0-{C6|8*=nhzPB7uSCt}o2&1t#Om6Cb|M2De zdiolA7`%n0LMSmfdNSyr03xLzl>LgbPx}ML$>#%D$n@C|Lc`gH8YZOOl=6!qU{r8n zE=E6E<)xqo+q9;q` zZP9r8c*6LZk%ow{=W)@kWx-wn(Wt~8mE}TL{jFAHayLSiEQCaPd^H#m+NB;W zQ2FdKh#_L-oyJ_1>&<8+dm0v*OidOtuSHV$VkMD3@dM9_UJjO@h`uMXe3n0vR!roB z6m6cwsU`fwZg#;BHcoX?=A@EN5$@`C;wRFY4v)!ffa;%drU=m)Z zSBI|xXrT}M^)G4p0}js3-%u#t7Q6HCj{BXUyWoj~a}C-LRdm3Zs&BlZOu^AzV~GCw zTfsVALa*T_I)8$#Q&&rAje)qwsoTcP_e}Z`8pI-Xud(H#8$6|Un+oN2teC`KaYAUf zXtikHD+x87XQ=#}`8nw+KNSTEKEho7$%h%9_V3N{lH6kwy-9EKm&#;nizLI;6?qg~ z8J3)ClrI|Qnx_s7L&>Pw>0F~Y;-}=K+FRa$K6^BV_f7dSb2G`*FW;+w#%B}JO&3Cd zWAs;sbUsEXm>(qC;{?8X4!H_?_&0$3!;?f`D%DwZjW9Mi?&}M9@p#`p?}0H#Z1B-O zb%b=ny{_(;Yv|srm6}ag8DlPk@ZxbYh^YFTFoc2g9mG5b zk`45K^OsJGkN9&4opJZ+e#i$yZDj57&0z9dQp|}fwT$=f&)9uW6oKIjes4LDx>!5|N$FbPry2b*#eF_9zs z1xe_QeS(I!9%R4;jDn2ycxFp9%_jrtQNP%Pgz}*c<;U9zZqe>p>NJlwYCANmPd6hj z;}_LtNO?2@lFy5GO2iHNJ7O-=cIA1_>nA#E$Jc_Sod>YO`QrqHY_K+S?K4S)+DBFD z&cX$0pkc;ccFAg2Wz{VgP_&zbLbAXvf;#5ci45LQruhp|5;K&nDHPaU7ZZY^Z}%bq z$AS@0rGA8F%S5h7WhX1j1w1nT(r~ch>BOo1OpDjS5lBz06N-XQ*|A0gy#XXznP-aO zbi}rCEnprhxK3_on~){?YcvRYmzKh+T0ea5CJAqaW)Cqq8Aol3y0@1QJmanxQNE@v z;jRkHbYy{91F_NGK_=5rvT0S-hkwU)!>^355sDGFv^ zw6{C+w%eYdm6zAx%`xZ$4i%;19coe9BVfvEek>cmk(N+Tq&iw>EhM8$Jw%bQn?J8F zn)4IP7LI(A3>5Iueb*HSbU?boVn?5xreB&ZvWroFvCTqGT*YNJKAN$k)RGr&0%(uS z(`n(ne)V>igary&)&Pb;JTPLo4TuiZWR((_cHpQ>sj$P9x&m`I!s9w1^Q^la7Rx`=XC!YQ3 zeklvypU?suqm>tKftVKS+1r933K_rao6G1keyhRPZ{xG1P=S2V^}(Fn^3XhOre%Xf zdi`Nam{1N{pZp?sgOaSwHI%XC1qm!$&jJyBE~|6w`E&m#>*m)}$?sw4immy2bDu~5 z)aBjZTD>16BJCC1-%_A)!f%np8o`jOXeqU*?bI4dj!om>^eE)8Y6&h5IgEhvynf7v z%uikpmei1tXk;a^<+d$aW;GZ5F56w4l3pC6=Jy#pY}J{EqxII7xhS@fM?^i|cqgAA z!5(~4WIdzn5DQ&xutFnSw(< zZN;QO*0>JJ;Vt4J6S8p?y)W;nY7z4kinv7+9G9fG5x7hcqN+9Yq!W{UhczQCak!Lu zT;Fg;Q1wZm0wvvGyQ8B~lYy1`GaL1e%dT5eSrOi%sT(*7|z8QFs{0Cr_D zmoSy6BiTcd`Cmkf?84FiKnF9C=)kj?-c;OR)W4{{`@++8H{V?zceGFo>YcUS31|6e6@E$ zegWvxysd}44QLwdU9C5iaQBvswU~wwxqORd#5dnQb_Au~jk&QxtP3oDMz~@D zN4>-+!RYsL4kmB}u_PE){*_+{ZwngbucVo zIlYcvB{J1sS-}WGCn#|UGEzm|r4>pO8zZ|dMJ65jkDSrLp&;@iXF=Pphx=G>%a_07 z1`Ki3c4c`Q67=f{h&V2h-M8TOJs?@$6!E=jWq_zx)h{=#KMHb_oC(U>$OZ&jmukc& zoro#IE^n)k!~FDIPyR)aUOM{O-)^XRkN}62pJPv`z%vPaV`^~p!cE`t%rBP(QT<3C zDCVF}OQ}NG5l-sV`-tTW;q^xHBBi7$l97TUPCuNst z>0XYG{la}MeIrz!l}rz}iVm3u2^`Xjst(mvsn7UPx)PGgNt-n z2k^$dkK5m+(W2f%1sa}m#$j|SJ!8Ir6j2{-V;_>Cgx#gy5 z=SGMbn(Lhy3HEpoa(le0d{T;CqN*V<%)zFd49j2mNt@VyrblbXimxVt%4{lt`BvCv zvI6XDx}nO*N1p!NBq>xt#j$lT&OuZsmPR2e<N&4 zewQ9z#%6gyUAc|1Nq3VEoBEWg5ocWI`kc1$BV6_LGhm2am4Bn^1LNms0Q6+{S}X6u zVgATQvGTR=X3U%;@+m{84^GOg;qa%i>5St|)5fak;GdR|!u=1}35xA*ir_+KqneGf zgFu=xH>O1TEtRdpVc5bPW%p(wHin%J^*Pu45Ufgm!FnG9L5*k^Y2DID5y+Hb>cz9Z zm!;oz73U6P5XIsgl%oM*`q6Pj!(6m@{=z>0B#)ouc*ql?$3JfNx$P3lgO|SV;_m4` z*ZBUfV(&)gp+SrO5>r{o@QbHI00t}mG&k+ygNkTQ5408Gr%M`|T9+Z@tpiJSoyeRe zE;O-KpA8(Q2C=8ItJqHvHYwvYKO8{+;&o6$)D=@3?STsUNz3{y6v*BQhTU&-kO-!X_pt_Gc zAr39gr%iwQvZ5%x|Im8(?->6Sz102gxwJwndSV)SowP|0@RZ(z1GLEwCL}wWv(z#= z=SPXu(u%VQhYl;Z(_v=O0|}2BW6C1?VM}gE$j9au4Kd{n8QM2fm=?L$4ULp(Z?eb~ z-SsdqP7OiU_Yi)w>?8#(7ozvPqKw&P1rbFT)GURHH-$5*!pKb3Zrnic^a!yw0B-S) zK9gv!B70SCNh@=b_)dYyG&}dPW92ZrvGJMT&K0y&g+8LHV$#D1gfiaUu$m?Bbry|A zkOLjUjXhIV3hi4Ao}tp{eL@-a3qPz&YfT8&S2%%q9B$1FQV3QU1u(-UB@VT7-P*RM zN$*muuyD6Dx4!@8Zb~ug7JWmirSqF>W8w z9)4REJ`}7TisX0gU?*3sis%{;wyjw{`I6YGNY;~<%3Z_GWA~g+L_wKI|1Px*B05Cp znNi8jgwTfcUfacR5UcSw%7EIXQrKPhzx%cjL5e~l;=*BC7NiO?N@8Q|~?t$S<6^UUlAV zzfEp8>u{*ya|>RVTh)tsLd0(a(A)wwrs^Rcl0u4$mg;;4L#1xsP1#c_l9`K)dN|m@ zgj?mCxO(-<`^%8-Fk8ig07u5@n^_dnrcCg^10vb>P>dHq=!#>;Jk**!Rvq#Jzh-I_ z_)bjLST)+3F~pdHP!JHXXUk>Nm!8;wfrJ3LX?M#ee;LS zA!CRRhw?Z`M0rYhwkt8Mgof;-*7(S6o@?5#a*wDs;iw)+dw=%kF@B-QojH%<37K3z zrEhH3iy4t2T8AmEqYTU^up7rBftPuv zceD+hq2kaoMSAt~WZ0@tf-RoH)1-(!v(VKcSU%CU@}G#>@#%nc>5Q@dS5;rPgC}j& z1OwwDWMsPkj((}x{mR&U`T#IRJbmW3b&JWM6vgcTOl`=@=Z!vpbcCMoVLwp)^OU$O znfYK)g}DzEAVZcvgp~F}jcCZiJ%IOL*984Q-yN+&Xqogz%48cC@!~zO>vuaD!_iAz zBIJT3by%|6GLd*kZLM-?VTO8Q95TeriT zR_}Y7M-6p+y|1p!(RZY3Qz%i+Yqcp$UiedTnu*#%A!XrSkrV z-=1lImF1^Q9K+Z*A-fr~E3r*t%~=lE0l)iVh!LMf(>Zm&QRHyLu30U_)Fx7$n^Jf&=p_s8sa#)%38Nxo8IpP`pOYqIsM8^3+3O zC;fAhEL;yP-WRd@=*nxAfz!=a$eM8RMC^_^ZR7^c0_>+aO`fJcls`xhBH*PPcBqy@$qVI6`3s$HM>{z35!{x-@nAP_hYPvs~M0zKzCnV zN}Z?62|>RTX-0W*=RCOZ=_dH(to7%>fO1`r7e%4V-^mkmh&5X<6=ch%lNdN@>QAPn ze3`)lMYr8EGUw`1)a$l)BzMNff-_unDy!dmwUf>^b$7R>EU#W5pwfjtc(3H&^%m*u z)WYKiAs?8EBxr;CZlA4Rj*?z{g5rE1q#ZRO`PyIAfr;6FU*>-!xFO~1G*qYDS3ce~$!FF! zw|1*U;>_NXWE4a?>_loA^pkEu~}e3BTr|`$}9Xi;>G)mqQ0Aj}LJ|PXTU0 z%iPK`nM<7&GffYAmb>tw-Pr8phn%X;x?(#W3cSf|sV36#+kM^F zU{dAxu>`6s7VrK;BbhY!e!$}>gvqsuQymJ4AR#IT29fesn!b5P7rp4WmF z&mpB8y&4weg2pVe)rqa83TK~ktr?Y%2CZ@CF?KVrj8BB-@=s6KFv*EMk_MA;52tJ1 z3q&a1*}Ll4JSUXX$4j2~;r?p-yM3H6D=t3K>n76n@g);@aN2dWXiBi2P&*ghx=jQw z(r6evn+6TDZ5FUNz@U48y5D+BVM=lrg+TpO!yZvOoF+H!RcW~nJPO;l{Pl69#ltRN z;>7OI;~x>WV%vbJ^T=Q#EehgXcv$AJ<; zk!8a8pM*TAfs3<*P}=BcTGriCNz&P~nx_n@ttLqhu@j3{6KZC>kVKTzUdS@(;bT@^ z42>m%37AO4&gRoovB+xK#N^A|tkg(~v2#{Y85`-R|DON-6qll<;5&0T&vL!{8JFoa zaq^FObqY*Z=xyFeJEPmi={UX;j2aVFRMg&1^N<^ypM0k$_pxA@uV52lx_a0kz#fX5 zTwY%>sVI+PQA)I0C8qhFW-h>p&~7fm{FGzG>_u^<*&}=K%Q5vcA50u-R)&q`>QjNC zSTf?&tX$1j@U8t}frqU>lKmy#g(nMoWMW-9e>yB%s0E3GBuLg*o<2qTND!G?M~26l zIrda#O`(@|CFK2YGg2?*l4n(q+9N-y!b`=6OKjO*Qndtq<7Mrr;nWnfh7e^Qv_3JF zJlO?OxjrBtQjF&IdP;TdE~ApA8{}&0MJ^h6-__P3fHOm8PVtq40QNBsfWxt&prnbb zcrQ*=bvpy({$PQvwUw+tOkk7i^W952J%pENdA6X%bjkae{8)^LBFzVNt=a~tm*M(m zzQ0DUbxgh<(Zd*eXB9({yyCOc&%w&~pf_S5cRdTQsZwFq5gSpSGPg6f6jBqC$=qFcYED|g37oHAd|7`|l5woJwPX^Eh~prfW#_(swDnvGc> z`K#Z?@|XTn9Pe+E%nVN2+P_r1dQg%|(jXER*l@gGdA-OqYWXm3&KuNr=)<8ZLyu(^ z@9HcWmjY;_5~dQBNK-gK6xDBpYv|{eN_-_jD)hnjBsgWHLI{q{STk2jEjlMb_)8W9 zNOj`co}yOH)~1_j3+{oH(qDKJmsikfI$JXezebBiS8GVJ2+ZZ%k$>qMYca?A{w#4M zex8{i7=DMPC@sqhUKG2(92#wKnO}Y#!%9N%63XA6nC-^hR8qViFXiiyfR&MIc&t)X zi0iEQv7%MOiaaGW?3Z1sIgRX@sdHR8)D`4k|3agER6gci)XH$(5#;c}4HO_g%+pg3 zcePnMJS8I)D?&ImYM)*FWM&P{FF6r~DRG?kNH9z?~b;Li={3! zZV&XTT)2e=2Am%{$zP$BTsyhQrRQj~X;pi8Ot>-1=n0B3O_*|8}TY$L*Y8!2mNLrRFX5;5XQ7xDNujOL4d)tGd^rWbXZ3 zjLQ+)cG&HI<7vAj1v$-+uC`Go+S+33H~OL@OAkO4s{{sPz^_hgZfYJodf4UqQnoXh;&JXlACjWp2*r<;KS~FoG#jf>44>}iFG8Y zZxioCd_9`=*D}wg3MtR5Km`zegwEUo5U7tatf=CB47pfo2)C}rL#7sJ_Pg5X4ynC- z2(lLULl^^mM=h(!^6K%AWpEv(X;Bj`W!^4;E()@{cujx(0dX(Jtoqv&S^tY>uqaL@K}~> z*YB3ZHG?Er7dx`gt?RW4g-Y$osshMleHB`3V z#{}PYX)kqEB^tZq0bV}&_zB9 zF*)YJhu=!?tvM^&%#ZfOMqr|0U1T>amk1;tv>ArU_q@l@8z_&krzRg1h%+R)B@CZv zmK$i~*p^Sbej2z?j+A61w7}(58cwKr*!3%C@{}RBU>Pfg*e!?bHxmH-7j`NLi6966 zqP+R}8r~M&gHW$m9)5GNwwy1s7%2m0puY1X&7)WgMnxj%qbq}Cdo7feVU@peZ`1bP zk2EKbf^##uuAaHaHQoChI@XM6vG%*Ou9cq@3U-@57FoNko zdK8>u(*D_7^Q|eiXbNnd+kKi3?8d(9^lg;p3eQX}&7)p&??v1jMSXrHN-x%Vm!0sH zObL&}!k`a^cjATdOt3kt#lu0}@md-qlU#Ra2CykQx8xa=tkh1&nGgSq4mqM>q6Sdb zM?4ZF2BxZ;R0H{0|7OO~Rpj^Y zcNu%T2`k?(pXzU|W?Xs4T-`334~*q}drKo1c9KF1y!G?5EAv42vDDa@d6_&l3C)XA z-(oOBr8wPdat+xW00XrcXzGI=m|>JsBTD`yN$5O^ZA?%YX)-!5YI1-OS)d>4tRj)? zur*#$2%#VKdM?2uf8T?=geGXw5D_fwqftbKqh#H}_Yr;XD(*-6vQ|@ExN~W>-$=`6 zc%u3zg}j<45~77`{1*5~uyDVJdulRxF9hU$>sal+^&+KJ^?2Z8h|L)~-}DxjI{f$j`uEyfu)kx_AaTfZ)>%0{lm?vZ5Qf>S1(r#xGsvCrr7Uj|wQdp6ACU?XB zta;?#2$6fg#z~48IS-Su{u;nU^%1sOO_IeS^@kc^5m5FanQoGiCRU1}!`sPV*t(I5 z)nwpzZxpYCln932xg#AZ6Yi|niSbxnUCSO&zPub+_4Ru(zR)tax7dDhkwi>EaUSmQ zH-b$_Szyujxue7SDoAJg{rg8ffY|!_KzYDtHyUv97YIR-i9Bj3`w+cdxJYSb%ZEBe zGCAfCc_k*g3c{9GNg(vjN@T?=(0Z%g_zC$wnAhB5H=%{7gn9BJb(0L~#Le z@FFC|OlFxMPZ!?UXtg(#nC9z~%Rr|OLZWOujDGVt99LHro-^ld)3}v4`Wgz{r^R5% zHQ{kW(t!TiyzkP67LZa(B4k@;LbaMp!TcD25IbcXAE+Qmx(jP3Kv&Mtl{6brA#VZl_R5&EWZrHV=%L{hhg zshc&#>*F>IB94uqR^_+)@Oh3u;>8K4m;UJ`)wZ=!JKxswcP)ru>nC~)N~W-qNYzx1 z=RlYc@5F?=xXqrg0+q!i=q`HI+nN}VYlm0yj(5k~6@ldmMRh}eE_ zqo#xCRcQ$w7A=X|o(L$IL3fSrwNn$R&f3a|SeWli+HGBsUT_8bIJ4fdx^n8b%msSb zy8_w}y6ORMu%N7#IWbLnj~#G8A$#vtW1}5A!2)83 zxC(Jsvg4emf|shRXJ!%P@L<|2H11M|yZ*;xg| z_%I@$bde4z?|E#G8MbH8k;Z?PM&ec}Bmo+pn1LXB#5dr-(Csv3Xi(fE9!S6bN|}4U5Kz=jy5wAq;6D4ZFjDZ`L6RsvvC0J*% zF)?RHr$IOEFnVn=R^CL8moGuzcRzZQmkgMR8y^eMl5rbu`K?bFHyRN|&=;+KxYUwA zWSok9HpBYeA6=-^B5j~*zSXGAJ;Xi#LWp>*lLd>O0BQ}J0Pg!*Fqny|M{c608R#*S zL&Rt*iyF!Wn`U{x8D;iKwI@m>f3=aXXct~7FWd}tZ@ZC(m=4#``ep*Sw`b6QQ9zW6 z!S&x)RxI2pKGzd_a$Q*~@yJ5OAIB~>T;EKlNlvikq9G248_4hZv`B=4eDU`?jKVnl zMjjadx$#~b5-W7JnMBQAy<-8@Im+O~5hJBYJV;M;oW9p`kcQ83jHh&QqMZ!D^Ak$mt z&^vA;RsPdGpG>UBp%b8E$>Zplzmg_`{Cd zI5h+(Y4nH|?g)jb{VC>~hA35Gv}kT@kk_Ktqm(wkHRnZy!g-w$4rw4E#jecuT77GZ zH$M2)nisnhj1coe6_1hqxKPF-3zpm{HNc_ca1JNx$4`}_YuF`fx7s@Yrf_0@#Gvk< zfo@E+CJ=hH*@u)%dh3W`H?bxyh<>7g|K(v#hj-!}E=Q^=(Kwhu^MH(%h_DP`Dled4 z!_(HoxI5+FZ!CKWc;1)6EDgCV`DfmVXA@KwAQPY#Wc1ai7Bm!#=X|Z)%g>QwC^XjH z$5r95t;?a;>zNpBKmy5A|^n3(W01KH!JU%#tEN~%gfqNPKgD9f~^MQx~V zxMEt~*5p4ce{^@yx}NT0Ek8`cr`xWvAiPCR05NlvnC!vJb~!^=$d8X-pa?&0 z$XK@c+BX=kca#@GE~fD(6k|tSUPFRCddmZ48K)WKATqX8pfnJ3!2C)msqCReN}fre z^~H`^sH4q(WdJp12tCcQz{l{jdn|z%wKcy}h-cHj-;Jcd5tqD^V#r$)z@l4yid@$) zLZnuY*Cx2eYNpqPkL=J=91KMan=oG??{z{UOs$O6OYa^2W7l*Smpq5{tbO^wnp;$t zOnw=AW4l7x4T5(}mVFJya0E%vk=zzl7xg7CiMzd6bx`qeR8F^2DU;gBOx7H)u0fSz zCf9wXICXYD(8^P@e$G++TGWI*(FZBj`vH)w!CHE&!m^KhP0L+sD|pS?Xk|2^`LWv* z4g4nNxIz`W^_~9RtlCw#osxlcb}Nk#{et**M8+t_iw;GJe5_S=8CM9`*)7T6Z&}SB zQ{n%nYS=(zK+E7Ib&pe+1*jFtg{kms!v0p z!g!wRH6=m!yq41{LI$1WfB&|ZKh!TZlHcvPC6;|`-w~x7}XU?_SpMj~&g*HH7db zGov1pX*mZwSckw|n3tcWZn%fzaBuN-9cCoij0&NIqDqx&^dIfTy8_u-nsBns51uBoMg zMw2p=hOw(u`!}%i?)?sMjoYRPjWejc>b&#%7)LqC->sgdA}DQj?d4R?^tu#0779QCf2{T)wmLn;d!OXy;f`_

sQ)%9$CmX%Iv^|}(sv5@zk3u*|if1Hz5Tre#BaA!#0P9zBWSSyBF@aQ| zf0oB6`COic2C~+Ocib$FNWhWE#WOuAuxX-etAxb<5EauQ6*ME%nJmal!4-eMDz1F* z-GbaC`t%$A?DUG#{G|Z(gCM;>1J97ox>K!Gu!hd0=?*BNvGk&PbE>MjwBQfcc25Yb z9aT%cZZi)F7KF#Ck}_rA!}LVi3H~2RR~gsj_qW*?-QC?t3=j#4fuMlI=#nn!ZiLYS zf;0n((Iwp=UD8O3G>9;eZmIu%&+~Ghy}QqS&bhu<-O_lvglt-)<_4rd-lVJFo)l1O zy!Z7L;@nU0zD`TO*5&Iej`-Njrb8<1bs&P0+a|LXt-GuGbju69{?0iY#K}3$noQbfe zQj(&s?DhYdoVJRV#2=)zNMCkk&2oVXFgVG-&)kvx?uB4=27UfRP`mGu+=^)nq50)6 zLK|d!`nVy=*y$wn?H?HUwDz~m>v{}Ej-{*5TntF1)N<%%M>5-7xE=(%;&JOuw7jJJ zw{877o(+`x#+~ewxy>I^C^s;5P>u!R_9;P$12Up!g=bOw^Z5b`LY@GkY0FU^K=W~P z(`CJs()b6-AK8|ZHc3uim>V{ymnyxgRN8Q_&N$-@isUIJg7zqM@Vo`r9qRMank)IU znVAW<73-_~>M8t&p>jRkXBZHrjLu2NEd*W)Cmq4Y$4pfcPHZJC-NeiI;9T8Gx3&hr zI#!I`;2cCp;D5}>RS%`1%gRPq4kgJT$=1HsV>3XR>pU1Bi!!(kmEYA4vEfzH?4T6& z7aKEjDk>OrobaY{jOt+6Ze=&9Q}c^J?v zoxBfJg8)Ur>30DRwD?ukb!Zi4nq>HeBZz$jE;&S#BaZ^HXUKhoXa|v?DqM|!K znEkr=_Gem7^L9PK%2L-qfu0O?Zr6#zN0%0O06&rFp0(#GRs}S4q2=VOq@Jn-;MO@A z__t?S`8xwnUUVL473ec?HCia^5_$?PwjZoEDtS&kBIR6^%i&>Pk1bm~RVf%RJ=|BD z3%@rH0}F5CXSjX(#;{FM=GA%*LGHv}Ha&1el31j!>B~gzmPZ9FQS{@*2@7&_TlKVK z`bV?NL6{fRLlLOsgFvaJ)xC#~8#gaSZoR<(mHqJ)Y-1c0Q!6~$^az-lITJlp}NEoR?oTdhAYwL z+(2{CO~eIk%as}mP;@WQFo+GDK;F&u;bw6X!86ZY|2ui;B{|8xA^RVVhS_@*kZ8}q zHms>0Znm!zVtNKt1~o0??P05Z(J%JQ=CK({+1wLjaSp(cd|(173n}SS&Fa3D4+uE++--v!JNAVno4`wtClyfZVG^7=TK0YT;p9VYP{4O zd28BU~#e?}+I`DNs!yyK!17QIhh5Q{{m?162ddE|mXyu0RCx3l$r*S+?*wY(^Q62W82? z&`DY3ljDw92CAr_RcsjmOpRr5sAsxxj~DT0rI9j4PPklO(FR#K?y|g}v{6q78OYV2ixQ5C@nQv0 zBZM&!dlF?jdoTGJvKAR{v{88Tu%&HB;GLL*pVN=-CqFG#V399-{zcz!1rS*ukF`t~ z4-NxaC{O0ZM{0O2dcbR2gw?1#*v1n)_B zRXPC;QOry>a2tHc^C(?O_#<)lJ7dp^4^6)hJdf3THwN$+N~)+1l(gaxEY9{LZy1JX zKT3Wnb?B%@8=2NSdA|z8IvQj)e+Ns#lv``{i3cQKG{{O8>GQK%>)mqH5{;lw<6mt_T6ibhR(s|L{l4;E-e zuBt!{2_6N=Ex8ygf>>Q~9uskf4b>UcPkJ||f^!>HRxqOv z7fMgfw5KH>%_SqMoHY^FU)eAOjbn#v1aD29QJyHchtgSPjDZGwQ&3)JLQJZu} z0TxkLWAy9~VtpQ;bs)uu<&cl3%5R?1=lAsNg#LTk0NivaWUhA6Yllug3gRZ@@xE7r zoXX=={H~M+V+DZ@I4`&BOB4@|W3 zTVaIU(mbexh-gU|d`e#rKwbUscR1iTo~pQTVI0+;nfwXLu<}O2*IN)bPzh=bOc{U( zW2Q@P(y!>d!8XRY0(f+-$-eUja>~5sODgz@)d1x*yJggl z>zB2fHuVs~0u-PVD-+S(Ww{hPGAZ`WjQNAjaTxoO`_It5+PJA+Ag#P9R(aA@xp*u`ni)LOX_q35Zj*&h`GwrQBF;F5?fSw$3t^V z*Jk;yHy9~|!=2e{?XK3yPtsTJs*r`Ds*^Ko7G}>F#?ys;KVe?*97X|$Vwa%Nqdic= zt#xxn;g~vNeR}a$^?;PsOAhaieS|_i7AKo|S?}*(Z)Q!xW;ZaFX)mE$B4>}z2W}$v7tE!3QZT~ zjz~e%sYN5FP*LS~xcTz^tXY>Kny8yOD)hfc^!xYkbvh}oCwjS8cxj9%0*d7wB{<1Z zt2d&0#PM|$hAKL-YGa3ZXiSw5$CsYeXDGzTj0gU4^nFEW32XLGrANFn7N^k9cLru2 zsYBaPt)3$Hw^|u_>S3P8k!>iu!k>6-E@SQ_DWezi^CQ643W&0rNKS|#X_W6*60ipv zyC}95^va|Wg{c?&c8F}5mFUrxu$ZcqUgf!&Jo#Zdb)@0Ww(yZbe?8DEth3Eg_$|R4 zum+AC20g*qg{r>$=H5u;bERc{JkzQ#r~lq6WkQNq-mYUfZq5}V_ewr_n z;BfIU6lDWqe`7;=QIvsrw$fWOylA7vYb?={+CQ>v6-H0N9-0pY-zeyU6c&A3 zj^938LNy@uoqrM3lI#0S%-qQd%$vr;SC6s^h{-)GNp^2GW7PeVWHq{F9y@7cQ>3G= z66i|s3LLNW>WeEtTQ{TkxA8paKp;8>u#K%6wBgO58kE?vl`haFi$k~;A5meoL|oTQ z5|pL?pJ52~q{vd=U#*7p29yOg5qGnVgE$ThgB}hr1OTXQH)te&;X{wPUfuH@Em1nh z-&cVB*cH7-5j)~aTDm4uOA?Y9Ic!d4a3!bvU_@hzj;>zeg+TEvZh8trw)41pw|m%H z=v38_{nvI@PerM@hwZ5}&`xB**V&+bzdc?AK33D27x#_siGI&Dzdc4x@}CfuJ%dXk zmNbHSJRc@{7L=*(1C#NO8UBntlwlbfphAi=#}SsY-{6EH6#*p~AoxG^LWUuGwne@Z zA2iNi9%f-YA48Rk&Q%;)dT>y%`isTjt-U2e3J-qsX@*vXodB-t5@TCiBaI*VF9iv< zw|REqm{?O|fR(F8_byC(dPW+X%aL2r$pCG6Ew&|eCjZXUVv+wo5IhV+RVX0G3@it!hPKMUhFEG7DK2KfLGCzaWW!wrLWjU3Hoy4;M%iHwLib??K+b zmz_$;4#Vp$!l1+=H~F&aOFGz;G!u$j(}AN!m8Tw^m7)l){eX^@impUpT#rkcJ)+Qx zAUh&Ofz74RR2(Q~(OfY9zjhO z{yX3PCE1{O?bC7`ctDo^;(R#e&r=5_w=_2rj;(%Q9IBHu{C3E!ptPf0_%kju2>$a` zGd5@Q9eOBFK6bB8U-Ju`@7#82{`jgFP9SL;frU)q=>AWS@enf3bmoH(!E>(gmS|Fd$B``i2}yp(RZ`W>!v zhoePr8{f|mR{>0R55)y_l&YHq{%Xbl%+}z8Xgr1@Zt@owc06iiXpzHc6a}_aL>xil z_=%feL=;sS!bL2>IXE9Ku)Wg=BLdO43vl zwRVUN77(=cULxNf*=eU@5LLiYnX+*ghx7Q`jTS#?jRr|rs{R<}d)W+&&=f0(yOMNA zoYq~7CuGoNJi3b_j-|eVT3wdbhQ^o_`nP+X=-+{uZ1%qlSDBpD+lA7)ZH51WKHJV- zT8iX-n;EiMsW3F8G|E-eixU#X9_z&zYX2V=H6>L0lji>wEo>oE0RT$-H2bRA{q~!% zU^ytNG#%La#KHWfC!#tg6i4ySE8hxN0ep((bZNeC{0p-frNK)rKdY%U7sf1*iGSmP zU$RZq$KohBnQW}b{Be%z!JT^~hv9y?<<)TU-Rz)q{ql>I)-Pf<&RD68&w6RZF{Q{- ziwIXghd$zo6~6q1+0fkFkMyC?*7iD7 z=+nE>q``5rXK85)N0navqLx2=Rwsfi49Ni^3!HwzO7V{cE=2>skt7qO;Q~{z!pnqf zK&TrfhMw#hO^tAmjuv_-HdoEo>kf0)K@=y|pfPtLuMrwUWcm!uWB&~W^`nMT>``>D zR+n<+5I+skbT!s`Gk;>^L8`X6=DExA6#+(V7~9SZXmZ0@zbjkzFQM=Gug)L@X3dP4 z059{T;Gzv$7OPVR zrQm2W@_%9RcI9_!Bl-Lgzd)u~_m+xzUYcrN6vE*;7TT4@Fx~BQD~9e!6;g|Iusuxs z-1jC%02L9n?Y_*(yfph@w|n14$SKXs0G}3e2j@HDa)FCk;4{RkI+<_GNB1hjr2tgmG%HDa`sL%~yAu-yH?4Qc z1gTD_-F~3vg-OdS5tcL;6OBXMVTS|qasku~HohoAvvXhEX|tC7&FC1}B9l`!KmYj; zUzlX~dt zoJ#&J&QWTMyW5>RpV6s5WBrsLB|)_RE+9t%JYfLtARfo-Z99#~;@vGKPUn~P?|+>! zuqlc?Og3pR)^xXCHsP_%JNiV8^Sa>B(C1K{rHq`vder%$A+K4|eV(00Axsd`ZF7*H z_{wgbmQ`kQHJ~7#A9+KzZYXgb{54!zV-(2A%d{)pGr*9tBe(qB>m(}^M}`u-6KeJj zaKm-xU6S+_t$_Z&x3x#XI2#H8Npb>HTw|=jw|Zi|R|gk-+8Qur1~@0TVW>UutG^Ya znZBiqa>kIzO7g_HO8l31VY59~Ir!@6zr?ou6@|G?{A5Y1gWiL>3XcbBN!wa|9(wG!T?GD6u!5M@sqH|zSj>fgL?k-sg79{EBN_p;UljJuxp zPG~qLlEN)L*uK0t>dakf-4c8~K=Guwelpih4SuDl3&%#7y$|Kh>)l#sWz1*hv`KonY$7QAk?M=D=Cq#$`0?-g%(YgM3E=Fg z0r5w!qiNMLYGHnRzv<5J*;($QVHl;yciXIeuqFLXU)?0d{#Mt=6i?`9+g>u93>qeY zvFY40LvaJQ$Hf}2_-?;x|-qcu6?Ei@4{Hc3HEz%fTqX24919CRUvuE(^rGL zNU0M8W?bFzM0p4X-$BHK)2s;v^onKP1Y#(sa=6QVWyqx3dAF9gSk?A;`;|)3_g?Q; zm#1rhoq(~ln0QN&%jzHLCr_>5iFJGp-hW?u=4L|ZXy4QXmfCSN^aR>s`pt0GkzAys z2Y5x|*6+Df=Kj#!WG{|)_F;dD-)+38IX!>mnbB#dpH=AQ;c{gq;-+=&j( zSE1_3ZjwM$i(JaUZxE*5JPVYnJ~Mu*qv4GYSz@oYs%PW3nkAfmyi^YvLSOXxxI7;w zXC;{(^c>4>{R^}0F7T%p8wKkNL2rPHEwekfkOgr`6Kgc&pH6J)!ekc{o}6Q|IZPkT z91tmLovWLh)$M6Adb)(FMv1P~VrilpszIT8CU#s1NrWwP_K8f)!QqcCyb@eHJF0lfiv#hMMYPLPLel!zpO~4}t9Pl=N`~m8^Us+cCciL6b$vKKznBnP zj}Jm#Ab+out)8?sJuEVH8nj!P%|yiR7bRbQ36?2u{Kv$Zv2cHI6Q)9)W`f(B$w8bL zRmavw9K~tL7gP9f?P16S}9$AZ9@Rac{5HC|M;r5vtbaXJ5FeZ^(|Eva3?4f`G` zSC1y|qLx|>&?dp{eco6!c}397363{00B|vWIx+$CL=t&`&4NNmBWmn*P(l%VPtn_L z9Pp`K`ioa*T@7Zj#=Et*0xL_CKq^cjjcjNAT)yx)4ZA-0yd!nu9u2)CcR6H6P8p}3 zFMcrmc(f9OFw{ED^v;@){qONcs2LupfzYtB5XG1lC4N5Cx)LBSmJw13cWjvo# z5uvT|r#E5w=PvSJ7B0_yx=X08zQ@MDH@~a#m2|%uv23v05by->nI9O(0M)gmHw(sL zSs#Z*&SgoeUkY}Sot2_ZF)4}3z-bnIp4V7-1nzeGr8hr(@wI;jO4I9Yox!En596(X z)nV*_IcxA=@1)8@F@d}kfV7A@c7T-%pY*d#^sc?!e^aOmTyexdh3SOnjUog#Ufi#F zVEZiMnU^zjzs=M}zdmH}v|UDyjFjfxHQi4(jVDye;;QpKH62-AMiq&^nDH-gTB)Bz zwKiZ0VsaYUjcy*O7NA*Dqc}53ZCiaYQUJ6aya`3@U4D^ndQmswT)^ob!IK_en<&J_z8@3c^JMN7SiO zZRhIh(7!PExe`PtjeUEX+MRcdD3Cr6va?xVFhS%}<~h5GyMsB5dIo0vvAM3K$c*hq ziT^v{N4Q}dNU1_J;HClwF<*{8K`6&Op)a)^2C=A`<&>12xz^^Kh`HpYIQczQY_#KH z$?1iQa%`^SsnhB{X?B56E~#7cf%3~7xh-6jc^4^vWg$4#@MMLBa*17q` zzbfi!#py+W93Cl_(Ju@ns#DTFVTz}=9fx-(fXJwwp~TgAa-nRm3z}7P`A+CrE;A(3 z<()-a9=+BAtl$m$)mof3<8NJ5Hgu;wF4+yYcM9)rYm(}8Sv@58KDr?8GT=ZwWIp5~ z%zM6+aj@Ix^g*+`{ls)*G3f7T4*SiEH+QZHp{JLFTWQDlCeZCMVpR~g7j!#r)Pf$s z+d}@@CJh1&5JE>uIH@7PA-I2Pe*Ps_fORB#g&;6Co#y@4eZ5-1)OR3q%S7X&OrW{W zQH?Emi*C%=>4SX|2LG68PuzW>@@c04VTm7ztj6JXggk<5%}MV?!d0fzbB*2NhWnc1 zY(9{-0Di)R)v+yYdW1-hj+YuuF)=){nx{p+=p03iF@sVXlHHF-DeLUk&audfAy=!J zG1Vg=PO2F{N^p#w(YNL14p3=AB~j_@_!q1?0Z9&_hL)&gQtM2=llZkx`b`D2dW__J ziD{qwQS!gu5MoP+#91~+m%gtQW%=-D5A0_mMM|TW4Lu{YT7z8so1@VUEJ0%`V;^w# zSI}yt6G9WlZuc5f@5&(#Yt=9O9!}}S&job3MoGPJ%8uH&|8*?qaRhlEpU>gLc17Yo zFS~uWgFKA>sf&B@824r;+4WzlVAu%-M`IdEg7KxHf+NJa6-)!H;Qc8Rgc68Q6%1Y9 z=s8mhR7PnKLzJnk2o+-JAa)rmb*2_}|L$&hh}2M)|8thfu*U`ww=h+r$5~9Y)F1gA zSea|vWR9^`EVfG7on}gD>1f%GfQOFia&ykrCFuF#qU#6Y zz~z*hf?)P`t(qoO(Ek4D3)Ab)8IO7|{gb}S4a25S$E++q$s0zX%$GB6e=FQjR*}0C z62z|rg}%fy2N~r-KXAcUT24~LD7KajHn)xpkoOH5Zx!)tPO6?-g-@biAz^DJY7~8q zj|x+UyfEAyp|~w7wS9?fe59qBMulK|>v*$h^l9?LRmne}P9K&zSx&P+K!DL}-p_I2 zJU`ILOx^0ItP9JtQ+n7JGouD}`=@Wk8|1+8>|mR87WgQOcn$D11-3W~{63C>0>THg zh;ORh#_R+`ef9*xMvAwvP$ts0f68uG%G@A6Lk9v*E|h(lBiU}5asvA$q5(Fw80RfKd=1|JPKnk ztN)6;8qJJr|7Wo$c60o6*Yvq$H|A*8FlD~*V&X0Yzm?s*9+-$JWrl8s?S zwJ5i)0^lM;#9mLmA;#dfT0$f5MgC18f!1IkVGXz);%zDKg)n5G}u5C2S>?*GjX#8PB76SFYzX6c8OEK1h%Dvn z7@_EVwCeYR$Q(~p{c?lQ#hIxCBv^8c| z8CLJ3i(B@$uYKO_s8hm8T7^>W zCwgiV>L$p#nyAv)eYn5cJw+BQU#N84ov+CD)t#SRerCIVcWZuz_1x#iZ_Db_F}n~h zKm@ZQfwK?R9%%%j_^TY{si6ZIxnRVaJ2(bG?me>qrl0YTNxgq4uLlBjF;hlI#Syp| z2#;6^gchEK0_jjT=#z9E5UPn#i!-EPvYG+x4u3Z z@h(lUff)=hId85NI?MEo-k8#JYWN8z-INFoQfggd>8n$AjR*_;w`7Xy?hY&b?3o35 zXn9#I1;md6xtRG6!K%OWaxHz&7>&#xsTffo?jY4*oNRt(T8r_*WVfcI_4HIVV}raN z`CGz}R2>j+N_Xl6CGA~cWg{js>!iz?TUw1=>U++C`C0nJMgx6Knul}ifan3R$oBXhVQKDw+(fwp z^^Z0TOo3ILem$qi=5r2l8u+l+?JJu_92-Y=yTtZK%>miA(A{!=oABPZHiYsMd7xPbho**K}Ah0*1dGeW6al$qmy@{i5& z?#Tp=j9-|kl`aykz$-T**%O>JtgvEa{a#NIgaTmjJbsYNB9ykyorE-|?D~DfC zZI7p)I`w4}E~r+EhK;up$hx-Ek7F6y&CjudohuPX^UwV*H+AR(L0q)5)qs6e?!)}( zpZuPqhX$zZCSHON&~$soPOWY1P`*K@XNsOuIm!$Ih_-zt=OjVkN+5>75DHernT0rj zS0K7dmH+~|MFgH@g_7BOXppdWM!SD%hXn~b@7Lc5$v zGIOIetQzSe^|v{?=$v(v)&!jB>YX^8q@8-pA)2LW#xr5ckQm38IyroUJ@O4}_HGBW zZA`Jo2elJFgKY;>)haEHJIN`>o7YGEFu8dEdL6;+D-m9lSFqpYuQ@+{^&=DxDZOef zQr!)AXrG3e$JU`aoL2!)OiS8c?~7WKE-nCyqN-N_RJlVZyWjbUBr9Kfl)+GC6x#)? zPcaCuhj7E-g20RSmBF3|A%Eo*-l&YOb3v&=MG&{_+Pts;R;KunC|qlPYp(Si4ijhwn!VmVTB>4;W$jM9#d2sN2$g=6{tq zo;Ox}8+>`wIIJOw8Lr0=f$qB7;|wB44so3`Uk|XJmN?q zO7QD3#7Gq_YJ|zJ-h-Z2xkQLe@7`ab{ooI16wQ?s1z&GOhNCaP2qCGp#vo_l?Qy39 zMjvZLKTcHM=$Qba9~|AaURU&X*OK%A`C(Ywn-8@gaDXn=RlkQLJuu(6o4f!(MgrZmMF6X=>UMdE!3~=DCD8EKwb%= zD9+#Js-vW^J_FRv4NT41mp1K5GuJdKg~LgqE{(@o^0}n1dimVEr_Wm;bi{S!bwq3sf#hPmhHc2@>2Um$ z?uuo{;nC`etXB9^REtMD4u_{(G3)?ashwG!G;|FUgvss&u6w^TlACgL9yzyNC#jBA zOWWll>(s6Nanha1F0Hn&8wf zk=V;9l&j)j=W^a~=k>R!ZcBgLmF5lFeL_y=9o?fwcL~A$DYj1cP*Y-K11V*m z8YWRQ9d55OQJIYDGYhwK%lij^am+&iFG_}DMAuHupiqIRnHhcp*PAiBpUE+gyG&sU z)`brs>LuYu+PK>MFkTWEZ&Y2IBAB-U4XAx?p`ayzZ*!p0_#0N|(5!TM zWM7%}j3OKSC8SJI!Mh@9clWKu-c!EJY7p<3AD=uRRYN4~dwq0~C*<~p zPYJJVICRERAuy9l5#D}K{%mhB~rF83SeSI3g zML+m5Hc&Qqd>bJkb5-(v@G>V?MsB6#*k}1}ezh-e5W_vr883 zA=5|^@xTc zjQgH$Z4_1ErU0G?DC2REe4S?&0CuQNFNXH2)GRGl{8vRtiR>~U=j}u>*47=^Gr#rb%ob+;+$zYa5UuGYZNTK-ZS7=Si z<$wHQg)s-=&rw(U8!HwcD;Yl>eoI)^iYAU7BUVG53p%&WgMIH08gPAktc^)otgD9_ zO#~{qfhsvc#eq!FdXcw$onm|>TvK(aoB8|FD#yiP!(Q3%pB;@*LZ_J2*{8nGnF*|U zy#LnfyuM1At+SwtxY3r{TTADdQW1-CAzm#j-^)HJ=~Sr3akXYL1cjQP#q?!qly_IA@iCmmm@620?o5048Rp?|@9OJNPS^jaVmR}YfmrElXlsQEgK;t6B5KAaOXyF&w;X{0hx;mqnP94&M`dI-EY)^2Dn`padaeCY+# z@%>lx58)Ggw6$Z0l&GGFako zE6K=E9u3q1gDHL;?>Ii}L(41ki5(@sht2mwleciO4QsU6Dp_e7!g9xC3v-E@GX@!Cv?FR%OR*2wivy(W+4h({5T?gfS*SEarfX(9d%YePre zUGsga%viCs((@o56!txP1^&^YTjW^aWeROp-DSxpR_z_60M~x^2`rGoE}i4&`FX0q z<%P>>EG~hOvXvEptNOe(!*mplhyZ?{;Q}*hA*0~_A)-dAI>aj0i@dUP|2%N$^ONzZ zy`hS~7$e!W|d6J_^ykLa(pyC(cs^iTOv6v?BM<9>OKn`&5 zP!>|<2G0Zncvw5B-n=E&=~2vN^3q5mU!0bR)UKm4GuHP_xM|){SVw)s!K1J>ZM~E7 zdG`f+y?=Z7F+rvY*=iZp$dMLOyuGAr_SANAqdKM;GoJy5HT)+`Hq73@gT+%VbrO#q zyp#T_7L=m2iyR%|YRu?6C>i}${Z#~s@;`H-Y=>_9jBo+jcvxZ+!wd7XgP+(~N5ee- z$#FQUPQ7<2+!-5Q*M+TA+}|~sU*3x21Z5nDyymu;ZVF2tVaW^=%OG8u@2SBEFHi@K zpC`U$)=n_aetS}a?kD)CKWA!N)7|Y%neyhVq(vLbICsCm74LBbq`RvS=%eI}Kj;H;cdelbk zl=Z9cw9p!vY#ekdR=;*YS<+#g`RNjfo=SY-XKRX^FaYJLE4ulu-52v)ytC#0(ha+! z-1;b{;<{uZLtfznPJ{@SBI@2(9!y<(79o^=VJd+Cb{wsaPF;X<{^sa1;_~Cp|t={J;VRKW*!Krs)t%p8z|80mmzA}?rH^xt^ zwG=Ex0BtdZHGp)4F>wx2P4JJMjIU90wSVSAv@&7-B`tZL5pU0cH-Vg}%NXM}?S^;E zN+*U(@areDJ`{Ib)b2Tt_rRx$mm*99@QS@br{n>2Iv8HiMud_qvdIR(L zlZy|jyI4b^;3f+h{IU&XCksUrxGEpSx=)PKBjlgOS*Y*;E6T8kb}4%Hz6N2djD?dZ zOMsuV;92QowsQr@h)_t`LES;p9m=PZzjOKOPM6)GR@%I-G0S(W@3*&~nFa88Jw8n{ zIJtfYa#lb!-;ouco8sjA4_!g$;He8^SZFvhVYnF&$3^*suzs8eTDPXX!N1VJS5Gj z1XF!Il%fVJ0nvw(rRNEwAC4(aszDlK=mi4uBQ_cAFze7{fwDp`=2|86uhJl6vAp(iC%6%{@0$DcB&e>*mx5caeOyAjNLM49 zmX{YOi(_Fe_494})9m>Bj}T5*3JQ|IB4=tq(HM(p9F~!88qqGFqdsC_Ol#;ItCdy3kOYI^ zs%q-K$~X%LMWKjP^4@_kdFl*f6v`?O5Y*Q>*2FIyHs5onHl>$CDHYxr{Jp<0Az*iz>8tc0I;7vUEd{k~N}VrW^5j9OTEhtHM- z>QkLYSGEi&i|&KdAfV~0%45u%Y1$uNr97hLHM1(1{CK{Wft1+tk?|}v>HWd8njbuH zm$BzKg~sinaqhcFdlUmu>51{#d;_`VH;6e}z5nGaG1Ly^#^FMTdW=`XJakOEZWP!- zb0_=K$H}ihzUMFN0`s-C zK*CJ|+Nho0pAYV1-K&&|2Ew0mO&1ufYm~xbiqE$cB{gK+DpM2K;NJ?ZNfcga>`tHz zT=I|rArOj=N{i1h?je7tmA^XYtz^PL-zhe__r73zB$E_nK+m01!I7o=NbEvZ@)cEa`1}ta~9=Du4F-T@L~T zeH+)VfYzp4=|S3z_!>O0 zB;;O}a2yNV^gc~L3N`&vO;fWrY9*5=GMaPrr(*-p2|b=+;BOR(hb zV!DhqQkxdVBA&`DQ9v}iU8Pe982Le*%a^4^Cc2h?PwIZtBG|Lr(YR^w_+VNa189S* zv$+S`<_^Xrf#MmtA8?)dP=lVFwKT8-6QFAd(pJH<(6Ru08k>mzk=aI^m`u?BLz*CF zjChdbIiNH~TBuU?q9C%8W%|jbks@`po;uq$s%LDb_AwGm5!mLi}rsP9!~xY z^Y}ge>0fJ!WB%9k`oy&Ooz=HB)3PDFiSB@#hA=K7RC7*D>xWer-w?*fH!U)mBD<4z zr%x<*pIr2Dk^$TpT9lCTL=XJ3F{X(A`&IAh&pylgXgl5SIkNq@v}j)0injr|G1R%y z)e`_VQ_?ws!t}I+uR}vb)uN1lG6snl#kPgZLlkwWgdmZrQKrk!H;Yt9ZriE*ps*Gful2 zjjrz#er-`Oua8PmLMksl&v{1{X758A=;k~v^)2bNPqdg40(ngWvdgy`RRr&_!EwjT z`NHf~l_f3~zj=Iss!7v#tj8gxB&fe`%$=KWY1tK^*mVpF4Apyy3< z5<<7gb^B9t&#!@`yX?SH{494;L0qulq$o&L}2 zdtOQ1J+I5Bw{0R9f~eJO!*KbZMT`t$fLUajhOBqwp$>%jd3hL*?wMfjDB)Loxwb#T z+xnzg5Gb93byfk^*IFJ|-pRARWOy7?eR@xec0ZZA(w-taLl3P%H6IAx;Bm(}%D)^l z(H2I{198QNGVhm(Jk%cdvZA=$7s(^4?R5x4%XMHlfJk#%O|9b9f{%y5A67`IwUKyr z*Panwuwm~ArYdiC02yk!K7MG4IxKi8?sRiq|{?O<8 z+&{QQHqSScll96sgBVud%H7m`iH~?MMIT~E`RVvwAN}0sUBi-}J0Uk&b}j}KH~TAq z!!ibK?q8fXb6KeD-EZw*Xd=dO7Cs`tiaKn7E@*Qvw1`gIK8=6`B|#XNJe3yca);cQ)+T+`ko3 z3<>guH+;5KQNkjf^W@3phzQC|TZH4Qi(N5S|` z2^=Yv+sU2YCSBrrBTxf2lt2+&Rf65ei)*2x40wASKQbA)(MV676mi6p+rfnbjy337 z3%LgNx4u?~_8xi@SOt|*c`vM8j@(y6FS6i{!W41_ZJghFbx?HAzAToBwkqe1CW(S2 z<0qxbXfzKs!@Vqzx8_bVq2=^LEbt*#cvSjc9J8b#H0=u<`7wdRP98HmS2TyK*P4K9 zv8Sl7ytIcsEK&rV?-D|JJ&}kX{iX)o|6OiGCYZp zoSWf2y+dc>(UU~d(H%cxj$ZXc3kSZw*V)m^-MrL*H;GfeMm0%f_2n6+9bOS-2dAl!W>|X zS8H-jqRtcOxbeG?CcTbrIGI1Y9f$kP(MU;vMkpCNxv$3EIxQZmp2G0{l-oXxW-s>- zN-OKf=S@Uhme$K-^YFq>{r$g+vx3uN*@P9{K|@kr8m1_orqfOe{`N>&ywsIMopDw> zWWLryqiF9%=d!#<1=g(O=OakK|46#-c&h*ZYwx|29rq&RQn<#I(8nH`*<{5fd*197 zspQ(OYtM`l;+oe;_RPv2Sy>s^2)}pV-(Pq<_`~aUU(a!#=XuVPl=}kE6=EmP=hc0J zk%7d{ly!Ibd~}{}zwjHAD&KA~B4TVRK{2pJRlB&^-AM%SP#Ww*Op1TxC(%eU8xyik z^!IoC@~NU3P*f$>q3D`%H-wFFrfg5qW+JDNy{?v+ajPgxmQ~^cX9N+6Q;pp;WVhH)dL?g6wjO$&wu1`QyvQ{P>fvbufJ*;D{kdYi0OYh}4ig4w?G{K21Gchn@Wh;V1@1 z{FN)~u5x;$p!-l+0!B^FPYcAzDhtU74H8bO|K7KI@0rDdZHzq{WAx$4s z)D1h3Am0F>*e63^P-*?0gk&LdbVE!Xcd(}+EB54<&nHS@a8lOi+rKeoYaTc1^Jy>Z zNo{@!EMD;4-2P#zs!e+l0g%mQ6ysx8k)q2#@>?df@DkQr9cZa6>Zy}yMZz~sDV95~ z`2*LwW(#WA!^ewpH0?0jOY!-^044gGsG}FNRBGI+LZ05#`hc1XjiXxaEQ^{LmfW$U z4=)APQV}jWj^Jw`gL1ZWQ{CJ7XvVPvXgqa%KBe(fq0nF+*`)`p~FoN^hIV1bcY zEeXLckvq>=Rb&7AjppZ+w25dd3^;S{N*aITPUdBM13LJac`PQl^lhRzhpvm%0{%(b zIkB7Id8WRkPVAOj>+~wK-?=v&nVX3$Y%VP4rViU%m24%5tBL zrOElO@-|Ua@^l4~BIV)BK4U>YQgtz`dc;7ib<*X<+(Ej*JB#nHUN+j{L z#P0ATL-vE~nhMgTD?YC!tFsm+8~NYDd5g+QRZZ4JDyDnm1pW47id(&PtSzhuv5&RB zs+qG1~-p*k27rXOuzvl=sLVY2`&P<>Lp1`+cVnAIpJKEltA=;OZ zXJ;y0Ed*@DJ>p5NuRD%NjbqH)?zT+4KjkqDyB4IIvZio^>jlX(26A);uG<(>`uwEi z4Svk~i6H)xFT=!&EZ>nB(xl&5n&?KR-WO>{Ue_izg?5@4hSt@TJ#E5SdFmM%!yY!+ zW^HM&%;5L4^winm6aS6gMJ2QXGo{+ z`c=56GZfd6NB}UfiR}L*&}$1os@YbI5P+%Dt z0A-4f-8vk+by}%P;M~1RyFKP?Ev}CO#b5-A_i4IP+_Uqy)@x}s&lLSI`*nwNS6ugG za&8fk27kt+AZ}gsuoMy7U|<+V>=y@NNE&?{z5UiYX&<3a6H$zMpA_77#v|o>?3m$o zXj0h7yx^*L(;~gOzj=H+)G`pwT;zsVtlW>s)O7IWK%k?gbzt`X3MYPrjl2u(&BDvq zbDxRCLc(=53Cr%${)$*ie)|d`MyzY?o^5yhR#p1tV0f~DCl3=rqtvi z!Ivtf#{XeN_Mx7b?x`7`q2?dE=b>VWL&Fhybt1lcca}%y8I6UIh)#0(Z%?e7)ZWkO zH^eWi&Vsr0>OVK6GiO=gK5yy5mW1WMra#rx!S%HA5iv$FTI)wLroMjwiTU=$41b8` z@(JiOtLKGkJVon_$giyb_&1f6T)r{?gbds_E&N0YVquf@Wqd7rQL z0ts`wqT;UD&5uvR$0J5>0}ck(-EnP6HJeG}`2C*xb=2b(aAt_}0sD8@XEGYa?{)Z0 z#TU{oF=Dn|PU(105|+Ls;T#$Bo(I$)RWgvih}_yIbfX5O7K=rWQ6D_l7undNJqu>KK)nRI zOzE*kQ`i=uY7gKgs3@j$l4j~W$s!=TKN6Qzpum=!PFVP$59UaLONrf&=PK6tMlSD| zD%h;LA786eXEFjlcZ*-Qz%yEUeG!)4U~6nMRu$YZB<6liyO4ec)JhD%&ez1ide&jV zaXr92{n7+4VD-LJEkqkBIVQEigq`{OoGlL6fqICKeVE@uMKb55lN$HDvIEH1L?~5f zA3&;5*NPZPqPu{H?2(!!29gsY#Oj8J<~j za_5x&7w(ZU=SW@R%06Dji!ezhR;rU000iFg07$2mCuR4vbyQv~zYjR2_ z)?Ckutyv&6{OS>l&4CU$TF`o3R4QClY=LwwwlkS?F=?HovP1L0!p^g$OCS%>UcDYz-0(j>UcbS$X8db(O{VvEWq0`C1JyboF>^v>I#ZiX9$*4g5j4c=%QWLB_%}yboLG z&ikVDKF<`P`!aus1L>E0l7xha^jqirX+r&EWv=ys3Jf| z0{$hUouTV@R-!NZV|BSR0PvuEceUS9^6&$Nu3Ja2;MZD< z^;2U<&EO@AmOp#q6A=bM0hw<28{ePog~)TiGUZKNC;CPtnDskx@e?PDWkoEd9^)tR zKL$Rtq>i~Qmro!5Zb8!QR&RFJxqsBAag|YZGow*kif8m(4VS|$ioBj{qf-EshE}?B zL*@t;-S6_c!XKn?-jDpNQVa=Paisy@RWd8ZY0_@abd=xUcfkz_K^pUW*a5BpgMKiM zsp%8jDj>&5Ny&==qFg>A>)pN()4~M5x8Ce53$Dltk_!NFkbwav#|3zu?AidOom6^pQ0;3SV@n!W zdMs)}W?J-T|=kWNmo@4VMhFa_9Y%u0cm;b?+!6RIo`sHan z_~O%vIVI6c*@_5YZqqLC*gn!QnLv=bh)HQock@JgaREoc8;Cq&rvbD=sh@e_ z{rbHhefh=j6V95%^3s;;%>KqTY$tnCFMHU8ohAXRf#uJe(45KF>L}#Tse!1Jqkl}vz?-5Hw5LG;QI)(Dy#JTyz z7D^dNIxfl*k>pkr{JuI!VXrhJ*WhZ6#*(M*sh&F@%zwG;X4P%CEPCr$ksu2R$Tn?V z6-)`+25?>7Iu^D=r_UQB#QMtW0P&yt=mxFRx@C z90cvD2;0juDgA&F8f!%I>uVW`3f&){r3BH7$ne!&JtD!HC-V7+&qUun2=)Sz8qLO2 zG;hOb1S1)j7Qag=RYio7m0g+wiZe(Ut{d<4KcaIPX_p1VB?Ie}4YNfDd?uX|=LS}l z7VU$2mX1miU%ej}bmnQKND3cY4os=7uim|?c|%FSGRu@qptc^ojb=kVb4&Fy;f_LR zF-i(^C%nYDxzD0a!R-tmPCjz4#wm)I%PWr}vdMJaxM7h16;L=j?=4O1nAF9v&L&(Z zeimX`DMjpD2=8Bm>kLYfL*9NAS*wmW-in`%|1{onwMwr(vXd1E4sn8*{R?>1;J9st zQrlY5JMAZrMO58vNIx!$JU)C{Y+T2pZK|jlE|KkT7Nz0#h^g*EA<-|t2sPK0%QCrm z#dillov6m*zQQ|-d^DXPO%ww3lbqP^<##bJZ7ev{aaY=y1N|cT|L*GF*@W62|L}+m zSlE)zAgO$`EAQFWbGzMB4a0783@Uu0T#3ScClF*dAjxBC86&XHS+#QwE;E0rR!ora zSd&mN*tnfUUt5c&vtt9nd#W$b5t>AmZ!ub>166alMv=Q*TLKxx)Tuc*o+w)3!rV9!dTiTX9>P1-8=8cnznL%)tr z?0gMnv2m>Y(X+CcY@@yZs9OFNCU+}z!9(TOFC+RrUz_oN0+#kIN#e`IA@ri#T3cDx zb{lKZdl{(&id$J*IhQJ{Uu>TxSB?(EjWxwU8uGq->Hwx%O%+VBDVb-ZjMaV3wRg@I z*gfzkJkW3kTkm|lPAU+37fhd2cf_Nu>IlI!f^et?PSwkC`Yj#V%a8TmdemGjTC{bc5894Fd5)0}@FGX(IB({Lkzou#yaoNlDq3`yjOGDy3e?;%wu?~msyi`vi zTr*nnPyWwYq2;?dvDHEc@xEj|CM`1e;6_>JnzWy*LTmmUTYAzvQq8LQ0O=(oi2nS$~2}CFCM@@ta{H|x>n@zO3 z_uwwxMhEQaD_P$F1#L5Pl8}MK$;IKj$eD)!oVngdAMFV!Sli80RM$ja3>@=@YAAM# z4aUh1T|mBwy>BWN0{oWHwRT1%0J%RNr~#WdNAKPhtXI41cwFIpGsxKUj0^7P)%aC) zDSZm_Cuu8o0gfLZGxq2kax~;|jf>=?!2T%nsq756rqXoEcae(eN(Wz^@~y0Kuh?*D z|4?a6iRFHlH-#;Sk4_JyDKSc@2tyQ`Zt7yiD60G?#hMlaYLCQ94>fHeYWr>2Dk;8 zc}0|mRxx8!UxTeN2cOkt#pLtY>-OZdWqrdwG@`6tM#0>Ru8<4>DY+8j#_ce}iyz`{Ef_y_ERbr_Ul{A+21slYdYR?vS; zpQdA%j*h33_(-gvmBsVY1%J>ci}dKtF>i?|cLDn`Z!L(mv)|j!;BmtW`j?@bc2vY5 zZ0sl2WrD!`OQ{HxVyL%h$CfIw9N=X$+(>fx;ES&$XEzRgGMRWwbyxDSGzi_X_g2?tSR@k$imRO zPavMBh9s1e?IF)m!`RTv@0SmJmkMEAr-41PR5SjBAWlrXl4ZYG{iTfJpDdrhH(~bE zDb?cE&h10acB}jLNuICdD_?!2<}~D*zmAO3&?GHuCM)eOyxaW%UQOt_oAdlx7-MhI zfRG}fx1uDP96nx(yA+)fsGIQA6VCJ9hL!FEFM~RTyU)q~Csk>66IcTOTcFR)g3^q? zD3Q0nK-b4VW?In`v*3F?WlfR2q9{wfm!fg&%t1*rAAo_AuNK- zPi`cV)=6b_ed$4vRBCEQwa|v42 zm9^vJL26fa?mD(=II!8P9Xp@ z|F8Gxgq4RIj3Zc}<>5R2UeWc4tfsbM$+n^JRnMI)L^AUEy(ksW)&26nN{O$nL`U0O z@PE~OkGmMCT9`Q5Q6Li?c#PwpLK{42L?rY)yQ4F}!;W*%+=J;UjRmwZG3*LqoTlfB z1}lDT>KFz^h*Xnf`3-|(;mXh_rIOPR`FR;*1sqlIFeOjT65zu3W9j}a01HV8EL)A| z`RNdqmU9ZE(eTQXks@Kx(`xv5@i7Mmq-Hx_a?GEh@898jbM5fVyO_l3_bgc`oEEz* zrN;AoS{iy4Du*c902pEP&xF_}4Gu>%JlCJ%tM32A5=@Tju;Mkp*Am#szw*#IGz;5) zR0Sxe6omuyamEY|m!4=AHpl+3Cg-Pwk9!8DZ0|9!a=KHdZ-*55)a zuumzS#)}NF$#nHe;rN$}8o_s=KupZGt#(kmm z=F(LTw%lxic|*DL-qOyFroLsyC(Og+us&xroi3RmN$DnPvGAbZqHe!#3vC78GP<$E z<+lovfLiolRcUl+cplmy=l;N@$l0$CgX= zKpHUGUOFIXs#tg>r%)P=9X^149w2h!8OfO%&_{nS0%f{Jgpg@ecR1c0i34Z{ue?gaNM4q2I6{@Bg7u9dQ&- z{-fciY{NDz<@f~M2b-;0^!bIJQUMf7^@I6ods|=0=5y&w?`t6ECYr$gi_h>W7JzTM z>~@lsx!OIGVU;B;<8nT{@}ASv z;s?eG1D#}C&h6CD57sDlz|r{W^v~$QT8*t|ZDS#}9zb5&ResHHLN;c@q)ZjhS}jCv z#b^N+R+=^Y4`TIXk5u6(`6=IQ&l@}utE|crBI1{WaOaZt_B8e zD`2HhKGStrT4lYO8shnbmx`T@<5}LaE&FH?6HDt@2l}S{t-jo)AZ!1yNW}6GDK;DJ zZueN7h+ivQjM!PYRsJ5fupUl*I@0hFf#eGeJHyEoXv}Z=LPkc2_2m`|$<{W75K)zn#$k zYP{cDSmTv%p_9ME>Nm0TX_otFVQ-$I*UhENCsb@7Qb<%*qY(v@i&W@2L|+Aj06*q4 zPOm4cT}&Dm`>i-VY$2%bjr_08x|-|rAj6b4z0SVXEF#op9`nC^kY?}chVj&y1?J>U z@P6^|_?`r~GDN3Z^eGpn-cQHqjv*q&pZ9=#ntx@I6XMq;CA6*3TdhG`GW3!(e3ZZ zK2#@yMNpO~nVg+vVOwCkjnXkPmDY8t4}4p=)@N!Jig4fWDbLF}~5ekmoY6BiBPY?>hV$5(o|EXLk${~1U9bNhFcp1XI zSJ9VWt7Fy>A-Gulqm`_bd?Jcyy zC`>?XI4Womw$XV~VVPl`OwD-w#n#seL{wy3soinK9JMc$Al+Xw$>r{3%k-A4 zmg4Q_zItz@joDao_vBD#HkcpN&L)>j+lrN6iz4H(I%l0oQk>o;DmCZ3?R z;hi0@)2H0dDRbf1xhr+i0rxx@undgD@24uS#!u+K{Xy#AOf5JzQaO*L&h!0&V9>8X z?EDuzLX2Ab$;yy@7SJ++V;HcGm`!`@ZL#f|Arv(?(ouuLo+;@a(~+@*nVHV z(3SQ{41IY{P*Q^=YjRpL0NgeWZJ}L#cu-BHB5dhM^2aXQkXMS|2A?J77_mvbX$UQJ zMnh57-Oz!6#Me)tN6^15%c>)v#b!gGhQ4N-qAx`%Ura2NZHXGC_#jLCzM6dm1nkfM|`gO+| zI_2=bFUkGiRYW*}3a}FpKuNbmMjIY9f_8nm;*cM=6`F>9PYa)A)CJw=K6iou?<9&O zcf}t-{XT*An{Kqmict-K2gZN6CEwF&f2>1&Yf^|{U;Ujpu|5(!3Jyz>QJ9K6r}{N) zZ?lYHsfuGHoHO9Yb#m&f4Sjmwk285YwZ)g5b)4AlYHDmed~czLO1GL&GbZITjo+C| z#1Fz|fBc?he!O~3{FE;zXs2snS*p4Ohgn0@8Hi@vDsM6XMo{Rt#602I-u6l)I;YTQeW@dp zG}#1398Yj5Z}x>)on+R5A7X_B%pkntjQ=@0b$oY7%rs)7S4kZ$m?2;^OF|0@r4V$9(&o+}lP5u8_=sRemtf z)UdWc+O5r&nM{n$Y=VGfXhBRY9Yy2HzB*6n%&n;G$V!gtpL4?`CPte;x3MIFr#WLJ z8EhzyCJ;GT@m}p>8k2?G``nj)YBGn|U zjiFOR%3*&1p19mSj`r zw4y^N7)N1G{e@XVBf|?C2bP&S*FKBs;$p2c7Hzb4ifFiS!8s=lwjM1cM!3MgzzJ$GQ!0N=!ZTJ>O{=|+Rf@*2v*J|c67KhnX!vQ7q!5&<42 zBT4lqLK%-Y)f;s^EtmdnBTfigZ+uP6Ne+!V3t+ft8yWWmhj@lLP$GYJvDbFHd-8@` z=C#(E`R>-a)vZ;)&sSg9g1+oxYu*PBk-TiDka;A|prT3@sG&?jxZu)+lpba=yzPupSwrr-TrTL+jpogt!y6IUWQ?K2$iPT*3)qZ(B_-mc)^v=;ae%AiqLsla9*UX0M`uA@# zUhL#oH+&IU*aRL>+2&%)Y-?%3y|&(4SsW34fJFFskTlTK6k$qa6cZyT_15x_EJalh zwxeHj-nH#?Lh*|AMu?KO`sxgKs0w9@MaEBtT^EPZ$oa3eC+u(3y`8z3dNiZAW)@XG zpkKS2tQ~GEiFHV?_}VPA@nA4_q`Up6^Jc3J>3&$LwI#zeJD|d87$#@qPu=bJ0nj_B zzs3>}CHv};6k#aJqTUO?6$Lc)C7|R=W4mWk;S@8zWB?5ydSiNL`FxGT#Mi#162hXZ z*caYGjbK)q_0k(2M}Tna?XM`^S~L59qY$43r6sz(k_fWLGcsCh!t^86Kc~_xmi_ymsvHh`x?vkSplUpIVs+O`A+Y4bCkz zO929KeKpD<%rBmW2t8u_&)G>y$(x-@r-QBE08tlPQ*u`3>VaNCgX}FqF3 z0Xdk+i=W*|HR8BYDA!1~M9$wWXnz` zWqk7;NK=BC5>H7v7>IihGhgf}+&1ZzdP< zH1~!K3=svcBgu0rBnn{Y)4yR}$|*k3i=m;m;;ZmQ2F?s-)FFWc^eHbD%NGJacT=!aLA7vYtdFg<3&l#u!AOt4eh**0BB{hq3hXKJb#9mj`e@S}_% zr;+fTed#9&aBm|buhYUNh~of>#20>u29!R72!$&CM@912p#XB1s(x+ILXQAj!|Bj% z&FQZR?l)q;K2?QmmZERu7calzA;XjL3;{Dk8ch==$;W_-1()Yu=zo19cD2{&JeBuh z=H57G@izl226r2;_9Jh6U+!FdobDkiTQ|H~$$2!OvW*WYh5W{=Si9uAS+1iD|6_(= z?>ze*k%JqcSKPVE8>~Ud%uiH}tn$RRdYNGWQaLj)r6te>SYI39j~8sUq@^!0VDQX;sc-p_(1z33+ITuN- z(iV-S9IxF6Vq~dh>C9584IS-N|C38}C~sv-n>)@2nwGhsIIigrG4w_zFPZ+^dhW0H zrme;vN8elXYBq2^#dP#&qcc)BypE^c@2`3-(}r)4vwqLIFzAx)d3YlYhsb(qx$66qYgXfFbf|s| zzz8iklds@Tmrj>I+Nmw=jT6R4`7hY~?i!{aF_U{jnWyfgv=F`BYyAXoqpQoAXmY*~ zA%dG>*ROqqBFy!3w*3}i_33y|v+=@Acx$Ol%X6I2wWnUVu*Q5DuviPhIp`j|wfV}@ zyP(}0&VW?i^sFVGya-US-T&oE1E49FQ`H1DmmB&)52^80ejvP)wLFuskezc@0?vZ+@JAkVc;NQg?lPg*B`m;=ga z$YO0L*~iJnW5UV0&P&Dut=;U~mdc0|cAJGq`=XgEs@2<1TGZ20xG$xgb0-0w?71U5 zU3U;FJUBB(&M5to=+?NI2zUmS9sJAuqzKdTAl>QA6Wiow#dG)8OZ1kL?p@f#1O2qe zZ)V$kAn_e;BuE-f$+u3iA6V4ozLV{3NBZ6_7LY<-9LO(qqpzSkmIUc@%P?H}#b{9Z zCOjggMNBwPUB+&v`lIJ%6W=lu%?>pXI{qWrN ziB6soOdDX)o!W*G>VJG$c6AURpURxqO*9Bdr!Pztyoq_Yvi=?RbSmntJq-2YFJs;> zx604ndLgbRkK5;%lj>T@h7~e-p2si30A@4JA$v|f*yG{35qpg%n7X1e4n++R8y<#{ zHidscQN$mt?yQt4C_5{CQpdg)Ag){-DOp+Msfs8W+W$BD({Nm=xC7-9KWio*K{z%(HEbP*vk8hHsaFS>)*d`$#_2~}gD8)5zzbPJsqaoAn=P05`Z1Nk zBQ@m#(y+_DkY~KQ3&qmUZ{_d52=K0D6$_fw#$eH-#jx6FYX@X=-zCkK-VZtlCB zWSv4AUWm*RrVv6Ul#V`&fODq8S0%#nq_Xh@mL~xlvQj638%y$)94VmCdj0x-b;%ij z-hUl%+9;l~VuOx&!UX9k9djgYP$#TdWGLc1MwV_Nh5A9o7v8#ul%QXZg5|Kp^0B(K z-0u}+$#)UOrx^~4iUQ!=nEB1{}Xt2NBQllKEM z+#Y$%?Q*{o(EV8~eR;TM+}6Bw6PuH+phj6V3AZC)iehSyjqA+lwNvdL9)f&gysBG8x1@1K zU_YL9js8G%c>J(Fv=jz8>&_#mR~HU<3ZIdB0;}8{faTSsL&oQ76TF1wZu}pm!Nf%3 zt`SiIr4-Oie1T<$QpoCj^F3ZS7v66V33XHEvPaGFiSK^lLFh;qRpr(G;dt%R7i(5; zMrWNyj-(v7(>7rz0s@|ndhBC~k`gW!@BX+<8Sl2e2>AvS(F|NR22oIG2%}Sx-1b8>&BSCTr33;QErLP|P~=C%Y zx+{)BbxnD(hCcee)s^c&-5|6B@V;j*5+)PZXg6!!sUg1*P?L((%v*XU>@qQe(){Apj8vh(}QFLla)yCcG_&Xus%Q!@@k=?q+NeEEBq2=oFJ@<}<*t(>P~Qsx z63o%za%a+Vf~DmEw?(Qxy-xORuN1K^;r(4+4?`9pJ4GfM&nC@(p!TAy<*neQEHQQJ zOevrO;2x64F698+qkC@ewfZQIVi>s9ev_xE4}0v)pFdrW_AG6Y zR5QOWp`fUDMT^j(K!qj(x?;+~?Hu%^A!Dw=!^+ShjmQWIAqc-Bbu8Ff$u^iJv2Qqn zoH&TiAi=XjWVyN=e`V5=EL5bp)C>6g%QW$5fl}u?GS|>20N&XC@X$R!6%IPz?5kM?UDr0?^){tfaVd^VeDtbH~*L1xdVZfp- zkODkeeZF`lcM{})((34^n0N{_dh{OKt7UQ~%iHpStAr=H*Dro_v>v?b8jh;^AhbDc z%qoDF8W1WYSMsXy^a(k~t8uEITyJ zM)~-0r6r!c$7pCU3Chhdn8p%GjdD=)6)2wO1n}$siE@`GmWs2XP)#7`Z3X;NXPr$6 z$OnM;65;;+?C#Q_?2(|puY8;+Ip>qZo3CN6!~zd8P>=g+OV$ZeP}mo_lSpT}+sd1B zpN=-L?5a^t!;Xbb)#1-uGA#dy3M$=79l&>zBnfw)q(B1 z6i!?>AfVK*hyqmCmsO8e>(vZ-UK|n7fGP9RqD`mN+G!=R&{O_y+B!En@z#&EEfaH(pSBiPYz>A<>4a9M(ldhZ!24%0KTQ z2|`oGwN`~%$7W~$)2yMeGIYncfM?Ab;NAQ9Q3sgjl&Q%j^s zG^AslPS<-mqeC@#PlJHUn%6KGAo}?N?*`zO1f^3$%BwYqBv?kf(^PNdJvuAx-~Uye z_L)i6Te{A_MI#=q*(T#p5rLeATB?=$#FCh{ejk){w{v@~3q|MN{Eb^>;%u<%UBVqM z;LZ!?-A?B(&^9?keB z(SHlPOIEmbHeET~Ni5zZwwWGMBbjW@%^`F@ob{( zU)-uV^Okm0!GfN$eMXg@{gQSBUV=-kK)sAYJl9D6P~B$Kv`Db%Q-v0Vq2Pt>AZ&cf z*T6mevAwEnTxE(qCT!Fq+4K3XP9Xh@*1kWl7%e>b3c%QoARx!^Qvb*AtfU|7rzzgT zZe$->HgOHrzQsWn<8tFiByPK}GyK;LU14qy%V4#fzQ8ftU#W&OU6C{co~B z+Xw=R1hMJ|8q?iTcVU(YP7rrn*!#06#)T0(O4&bU-pQ<8i69sV5~aGSiv6|rx;V_jK?HfRyTpL8!0q_UO$#< zInofCzbxo5;Lk^p?8tP%TmVspVjn#@sGoR!@7g!$jJG?>xwIdfr!sD`)b$P(+&zjg zpyV|hAo+ouL!9P3g|vJ&>4p;O8c|6X77`@@AhMn6Wxty`FfcndueCL?0!61gckn;0 zS1WJjJz}$`*IoR=n4b&O2h+D37{(qKE2;w*Vi5DrN;K)w{E9>CqG}JFWU+Il40Qst zzL7N>wPt;w0N)r7)#-ggJcaVii8AlSvMgXL_k-uVL zP}XnmGm~$sHFP#Lpm%H~m)8YzN&(%z-+v*2y*e4eC+=Zm*BlK=ga?1im-eq7)e|G5 z8Tv^yPyXu5d#Nv_bKKDtQh^MmL7fo1QGL`NGy{<^H3^z&l;VH;O8J#9napKKe#82BPd~6rYi7EL; zknzJDZ*6yArV{I^!|*Ro8|;k%&LW{}R#ArHMJK;8pkATYo^&m&p#@n!2E>R8?w0bC z%5f$@Vw$1D{e-y1=IbaLQrbl!(evdsk(<%-JAib(Fkr`H1QEhtq~gCX=*Rk>o>c8k z4mJ)Q`m1#;ruJx}G;Ac`7Af_2X42kx_%WprV;)=4x}UNiy*_W;a7Sw{i+TT`UwG}; zsAwrb{{uys(4|43$)Yynr3VbcFs0Q+K$myoS?jGwv*Mt;cgGWRauX;xlS>@}C(0v2 zWkk8UL|DnFJkkCa2cCG!;a=itKZ}-lznV<}PEm7VWbzn@h{en{bFYtKI%3NGekg{LKK4P(9I>Y?!?PB8V4^|WqSLiGM6WTT|N_ZE?|!5u1UMH{;(L(4V6a|Li_Hp5F%Ss z(5P~9$gEIw;BlegjQ4%Yzoz?4w#h=Uf5{cLokgb!n(po>!u+dmI01--#I2q9UclmA zF-(5ffAEK)2(G`AcI+dr?i$MRlhlHl9i5JmBaf$l+;&XZ#G)^KtD&6DqIa+-w_gId zZM}_57Y|!^bq=ofsz2lRjPY-9{O9F8JkbOoPY-3)h7vmk6Cd`=ZlXTW@kG$^DDf!a zM<)@Hk#ks2W&fr=ldGiOlrP;e3GnkX_Vd$wPbYntpp_a7@_AeD|ZZp}=?Z}q&PPoIdQ_c}Dj=N^b;_Z62G z7&j&TOeIab2(pBW=ZmrO4S3s{d$F_^&AZ0%l28%MCwo(oculqz1c9?$^MzO`=$Sz0 z$#(}rjP?FwxAYpH_EV%!$r8OHY&8z!?q5?Cg>}YXqW+%hyf!Q#7m!i;^zJ!Wly@Zd zs0p}ULGF@tCS_IP`V+d+|Fm_gdXmDS7fqQS1p@2QhL5^kb_d+HQ3a=EX>oIt}(mY6MBMAzfnZ{==lDNGm{;+XG__ znRMzE%uxl-QhyDdOC5;e;cbWH7+B0a?a6;*NMFlW*|FjTD@ALdwet%QBT{!?2P33lL80yDUn0v-87!-rv-?>SEkq z>J%f#1LWxVJ>d1cLS@a1M(SreCwHnI zvqwQ-X1PsGi8%=sYL`ATHwT}rJQf0CG?X-GKx4N1a_YF@Dj@oG#Zm|pD zAvTSz6Pq*lzD8XK*xZ_y_VR_I%#)JckArB8I#=uuzx95hzcYuo#dGi*qa=I>FI;5B2Nq{=?&E2~Zo&t#zmb(JIrxn(BQuf(cPJ z8J8w|>3{3O8pJ^mKNI}uRAyx(siko(w@mUr_Z~>`*tOU?f$mMc`}sFbLu6esczIo2 z$GJu)rk*t!TiZmX%+jE;{>SNA@Njro>?86GujfmZ(2R!-Cx=}}f3M<4LmQZ$nva|j z{S=Dq>w5E&Tf_A^qzS&stEm7y73HfJ>+hy-AGj_#;=a0kv;PwjppC!U#4Mqm;G%&S zulaE14ZWv^x+Ga2{(1=s_c>mtUP#I?SKI2?$=N3U8qLzB%>KbIt~ckO?c)EBr7sVM z`g`BEW#0)QvW`8<@{%oU*s4OWV%t&JFjAbOsATgFilS-B>$3In(K1mbzbK@&;8u@{X9pw;^yURuGBJq3lf9>?g`v6O#OhS>Ql?g4E#HU zYcS@JQpe-%0+d1HC^r-33npjOyj3N|kL_Cq5W}3S@}R1-5}wbpk@txp-5k1`ytQXp zE<*RPq(9R?SSYt*QZLYh)gb9XLtmp=>dX5RP*IKMSv+joZJA(~#l8cpTVT}7+v)XC> z>+VXqrMyk6ZjpPEv^Z*lzv7pI_>hPPs6{zf>Z&UQ2Qrp z9qMnIGcZmd$ovW>Um6$B0`@Q88prUWGSj!mcfLQFVHJ`@;EDOhy}euxB@=yi`E%-T z`X+TXENE~)jnc}GmzuI2UH#W1|AlDyu2k7WO$Ji_6gX0SA#I{26tNwgT`76CD#(*s z`{DNL>4aBwUQ5poY8KbNos*z6iA~}I_nNPSrulo)kuM5-8Of5M0-`!i(&7PVAiiLk zpscm^$Pq(BxaYdGrgrYLmUCoq84d1pw z!PFW_-u2g0;bj+jeLN~(0@eK5)NjQCh)Z(%VdEp&_%gKM6Al8P*vdp(usnwB@WG(` z$$CdEFlCpP_MJvl>I@4=v|#tc1y2I4p-^3H_&*m%~u`nq=X;Df9eXdAu#U z`K2b^r+0i*KLo5%oYU#_gbxxA=yBYLsg#l83jNAY-}f>i86@do>xe&94g*sL%LbYO zT2wz+JHr1Om(tQr=j%8o*z-E> z&uKK-SE$LjAft6$e6(u<<;1m`zWQgse4qin9l@}eWUWPpYYN$hq?v24r_v-56|uBW z2@UrsC7$suO|)lyvWr#Op1o4%S+kDoS#*>Y>ggW-J9+-#E}URUDZREarv#E}k>pAc z>%D5L7m6pFck~XLb39jF3ybtl&-BBCv+9D!_u^z=D$Df^Sh?$`IlX-PL%mUcl(%;- zVfG8-!NvG~q4A`)aRd9>Q~07qsH`zb{f1?k1f5CcjeyFRWm0T$OO-EsQ<;>Ke-Bq% zUA-r%v*KK@bn_OsW7e}Dp$JhwJjNeUsr2{eR8|s$h&QygZ7;{oX2m$rYMt)I_;ADg z{$L=$w3(37zdsc_?Ztk)a|ysr`&Xy9vXejTs3 zI%*zOs**e$^+FK#1*>YKH`M*^O-- z6XG6oHLJmz+{<2}lus(a7@B-A!m})|^*S&?K`NE5b9V&~H_wQWa@ zFV(qIjl;bHYDehVFVDKAo5Zul!;Lk+wdaVp#F(&(`C}$!cI%_r(fju=2K*P>`XS<3 z6wS)0{=IGfpqw@86HFZr84;j!w9bL6wo|eGRHe&5Vas>Ce%+CH1RJ`U->IId=nv@leEJ{D)E0n-B3g;}O#-0z%bwv@0 zhia{-mi=ZbG8@FR4J7yvnx$ecUx$r^!;gcv_|dD|l`u=dOr&7lhC8Lszft*MO>*lT z>q(>uGtoqIHb@)dP59uhIpf-znX1jnLGEoO&!yt-q5!<$Wn)wcwL}B95HCwL5devvb@wpz)(td${I*kJ=(>R9BSqq_|pK>>M$_ zsUn`1nxuWd$%!A#q35p%;F9{@r6O(+PVx&H=Q<>Jl<15taN44&V4q@MozdKxne@Tt z3Vjyk(WK_-^mT{*MXHz&a^b1AW~SzWrsq98mEsBu6H7PwU}g|ZHXuAd>pVA-v&jI_ zX3eKj_@XKzPZka|O>mh1^DXv#cs>@W47RN|>Qc58A2%>UN3dwfbyHik%NIoB?3Z7f zW=_#b zig?AwFp11pFHo#K{gW5YHgZ(3{F@HFaBmP%ixlhjZ2F zvf}rzB5eaDbxDDw0}`4BdmSXHg+Sa{gc%$QIeO0ze5X7Z8qoi*L}766d%Q;5DZKAJ z)v(d(PG&@2icrd*vm!)!Ep3%bvN&wcC|`5W*nECT^UF7Nw%QtQ-`%0-i+$Ew9W$N@ zOha4CD-Rq?-icG=Y?72OPlnW2Qn3co+)wx`{ebP-+J7~>ifkNBqYc3olPmq0BpFNQ z$4Zvu8-W)#EM!OSEMst2mb6%bhMk#+%tct5J{fNz!XQf zynQl&(4EUPo3(qPSnQ9GA$_M}CE#whHW9b^HQ=BhL-mCpf1e_he)(;wUr@894%%XW z@J@vni_u-@|0+^`fl1HhL1V6_Y+TtJ$J7QI-564~xYnjVJ~JMUMu?G+csNqnGW#;n z#8U(Yg05{o3p_7QJVSW;uS=Ul>l3^Uu;j6#*qf#~1`gPV&glDv-)tzxt11l+L{|(} zfxSH(>#wd@2C+4=6K3c2Vy@s5?br#va>TZ}>Q@coEm#L92+O0-4yT_dtFcw4WYQB{ z3Zv_Zr}&;%_7Ra}hemL01oMsidhbKm+<^9`<|20Utt{^ZgFJpQ(6rD#`di*iy>Vq; zwBm0RCqXhVB@y<5_n00K2=k8q%ge?q4%hXt zjv{o4XWu4pyRTytLh>Z`%r$*?ZpM$w@BhOKJ?E2VxxCcRn$!++##n7Mo3D1cU~p)( zx97^1xE9y}bleFzill?am=OSNWFKXq=KuO5T(TwWt8sIRMGtGWv{*iRM4x^Au)aY} zvS|!WjA_cJj_Omp+}JA`eDHOmkI`{MCfoB0kM-k@sN0{s&pk6%NS&F!jDDg@yy>F5 zyJ(h*_liWldC2ETba)9|70z0l2?M%WeBX)DOWeB9__x35P|u2Q12$ebD(`I4KT3Pv zXfDPB8aFeGwDPv>WRb%O4uZHC&HFcwF*ZoG4vxrkS;`!;FKBs)UD9xsgsA=3gL> z@KFDSu`#>3Z7%Jn8+TID0tb6Hd_^!?Dp)(ToJ!l>fk|B7Bs1=(Q(Exs&`RmDt@hDa zr+sCkIOw+kcboG`ak60EMW&dt z*%6=PxLP>~7ou=fmI=n_MDk>iy9Eov73~E1_Y7I#pMB4d;$rLj&ZMN6i(<{nL^7M+ z(`HK15?~6dEPm1I`eT^!5%h9CoHY2}_R(PSsT=8E9kqJC&F@H**~OOEbIwdaO-pH6x)z`c_v)E-G**RFWJ_P41|P=hBi6$8raY zGFw@J>M6N_!5r$@J`*gjyE3TZ})U?4#P1dA{M@50t{r zrgnjkvBj4qE&E)=1N@yZ?`GrTGJ>=gz%SKhhy>ivA*=LfiA}~=Exth4)UNPl`PT6; zPH6B$`28DQ5uvuk*cTqkU1$0y8`F3jcLmtJLDJp3sj4@zEcfAe8cUx0*SAF)bQd(9 zTvkH}B+dg}SdQua>E8}|tS>6LnUTzJPgA|Y*-Z(2j_X~D1QOdVyTS@&&Zz8jt2Xkl zKUa+^h1YLjQDj^6Zm#jPb}D}MhjSy=Q^2f(?fu^SHTBW}Pa#aoq`vb5O5(4cKr4>i zGG6_bYSGjo0}Xw4V8@{0;)!Sr;~MQvFVD8+L7+@m#?l=RYHWz|H9}o#4Dwn+gcrVe zxaKvL*z3<64Y_^2q)=SNr%i5K^bauoOhaXDl(1nM9eF$ zQCC^na*AcgBx)MKPiGaFwE}n4{;C~IW3&XD?-P40;7fFE=k40j?2t`ZPNqEIbRCh7NPo6}P5f@c4hG6(y!5M{?5zRkldnEr) z`5>ak@>ua~79URqUpTUHa#K9oiWa;aGRs(&nU=*&GrQIEN43(zIiDEMy{%g*k#Es*>g@M_cC9O1 zhiB7XuAj2}u~gHbVIMuQQqfc;2={Jhvi0KRV3PtZTI&S608#uM+knx&YMdqjmu;sJ z{1vR#N~iUve_g=)W$)i8ZHmA7zVXeDQN!#*K`sgC^yl-TQD<*k8gIa|PtH9tpsrmoTLmlA@M;EIZO$F1BbwSs#PzfJSkNciDBXp#Jp|qVr}BO_rkr zufdQ=I2CNc1s7l9i)oWS7bzcAKdIa@eT$hR9%{Bet>>iAky|PXgR=LOprbC{wl@C?>sTV1ct*!nF$)O?n+62LXFF)Y6> zuk~wBG9|4~q5AZ796?@-Qc?q)i)1Mk>GI9M*J+pn>;a1XS4K^N>()r=gfKUWbxf!s zH!X=zlrt_@m#e#e{w1<^O?Ip0;lqa91Hn|)i)^Al7|)fzXHVpJt<9&u zz)oN$wiQ;lQGyV>w>3lr zV#<3AlIBuD9RrTFQD^^gi(2UL{WwB5i!J4Kx3Xz_uRJ^GHVr0lgKDT}qC~7&sOoUf zkBXZ-Nd@L@Q#KO_MNQ}H|Ln+ey!VdW6sP!QH)qU(khavSId>BvN+}K)6q{ZTuVMHI z7gt_H#~4oe;v0E;yQWI>dZa##u6&mgbwo)kFLhzk8kvF-Uuq{g?#JPO>j0MAQjY{%W%J}qWX(^K%WQVj@n0zeKWZWi{ z9S&kgaHcg$|DxAtN7&}8DabPEu@laGT|ZZ%DNJ@_I+vf%-aE9La&P~@X_@iS6}RiH z(jS6Z@hxgMJd}vBGdOlJ+dM)kpI$gFWe<>9s``uHPc4ckM0P!C=C?&N1D~T3Y$C4} zDKV+#G3Wn1aG2CbY?#ES?Ka?!Q*@k0%Y+k#0%ul!=YRAw(?D0P#6+aZX@(nP!sSd@ z;{>-DZdzse7a6sGY-B*_$kMYPSM%gLTX6@lW>Vz6t(mEtkiWIv;zrArfcvvKd0{7) z62Y4-gmUkGCoy%B<@~=&>77A{oPffCA+c-|-`n2=2)(%U%808U6vZ3(J-^(v&-S(N{QGuu)IA z{y2q-HEU7?%^m~Kk)mdkt$Uf)$G6@sY#TN@eqNbf$$E5{5K^x*yRd&HP*3MFKC>RN zqaT=P5k(;5;8sKyB{e%DB<@w|+L&U^DI+nMtK_SLiLL^svnyiuN8r^DvY|=SdY-jy zigoPdOAk?p);j=br_tR`_rB$pNG1{_FP&L0=Pl|JZ!x|!Iw=C{nI?=UeM1R>YuMi& zTzx?WlT$MJ5NaFdC7(8lkpECG*Mzv|9M*~}Wk`>n6-05CeAlX%X@eDL{>!LyTdkB& zhhFWB+CE*F@$@p|3tsp9bZ`NsTL0XU^Bu84oK_#ha8vWD-V2?l$62mfI@<#uh4kZt z_KP>Vp9dF`Mx{?mQ;MX(=N1ZD(iJ=Jg#sDw9p}>Oene84n4;G^OT1TX14b5k-kwFc zx_5_h(Ix!>m>fS`IQXPaO0Rxy@)tw4y%k09FOQ<}WGYO+T+F|&@tU#)Go1O!hC*Mu zaJqGY+jCeF|48n&z(jy;7}n+iewM!Tli!r^gWw`U^F&e{)Pu`KJYTyg^I|;r3Z9ug z4q{OoV{$HTAPRmTrT2hS>6C_@VyLcq$YbK-^%Q-1^d7mtnRlX>hM!o7nHoh+a1wRn6_rA3*Z^qw^la^Hfcfg_VXyNyw^$qp0^DRP^ zH5!-ejrlr$IV8T9P!e*ojt~6t>*f6&$JOEl+TuFPtw(-4_ALq8TfdLjb!7MN-eI_W z4J@(96s4v2obm7o?(+!K@Sf`9V3#x1tqqfLxPP0IRsESMHv&dFG%DXClwdvgLN(-k zh9QS?e2)T0ceMuVcD3s1WA*-c686*mhC=C>6C9`H;m zbkRNu;K@~W)_WYuqW7F5N{}h8*x~ZeGa~dT{-o?CSahRm z9z^Y4{p6W3q-J~mUz3(J0=?<7zHZPnTbMFl8Nb@$DZk#K> zB+Mzmc#Z|t+bMx(bkK|NvCwkCYyK^ttxtDkAHFbp8pMtlP~~lAUC8`yags(ucbzcX zMVs@cE!VGoZwk)|MuwGTta6yf!m;a*3mkV5tF1C)| zUrx1%!cAL7^l)?i%l{_&5NSMxJ(t3)SrPN0Z7qGn z))~fmH(>4ZU$Y)MU93-NYKwv1I0D#JY-9TjINe>Q^$czc5k+9*7pGoo6WBL57#m89 z*p2$X-!@YCUH;+FPo@bw;&2nMdN(fe=;?Dq$oQo0K^Fl~$zP6?&#Xov)2dor<;kGH z#7?I#LQ1KVNbm2p#sztVP~3++LL8T=`Mt7DpkMU$THSuz+9ck9rL|W5M!QrQEanU@ zA8m(kH;e5|buZ99`8SXc_(aSUt;DeyJ+tUVGSTJu{5V;@V##ov&-Y+F>9U#a=GWmB z*>$deXn88Bgwpm$er91T2URzXlL1&5J?4pwaa$9!rJj0$MDpyB>S;B54T(= z?VoD7{~rAa$H)j6bd)qEX)?uqb-JuLb_;LxS8m>I4bqwd?Z&x;m~fc^jd7EN)#@Lv zL84Df>gxY+PHc+BMVC_LvXT3AY@*k$v4u$+@g+J&y-LNm3|$ZrKQHj`9Q= zxctm=?JD-|ytF0%p8%@Ws?o5{mCu(0=_Sp~?{L7qo0DygF`4DtV0}H8{54nlCJZ^; zAf;?&@+5@`3xF&j%%DLvS|>BWfJjTvs#F)AUM$mX>X(zUPiHY~FA;g7%QI_xL8Q2a z_SM${MU_#i2@i<&BAzPIM-UETAY5(4dM#}6z^=&!MNMa6pQ^?D-+97qhg4p%ne_6y zlgON8^mRPv8Tiu0xC=d!n9^l!0X>%~J?m3sYt!c6&0mqY`rg#wPdW0`)CV zQiO7MrE={TMqk)=Q2LXf&%MLB1gEUSO+u+1P?4xim?d3pol@E5!fr(*1gY|d6ukG% zME;y{-OQ)UT7?)Sj7&CnTq#Yeuj+lNK~$hE5;OVljZhB=^*P+om;h=|rPq?{?J~sZ zkX0+z!h#jgJ(J;#YV>XZMY}gxQFh4ze}oE%Af*%Hxrm#1!3}r_DKq5e-5?#{!BM(W zp(`dIdn@Zy^WWbuf4wUTmqNpg|FK&>JEe{ zK6OId-uka*EWU42h=4u2bpcw-$HL*8We36N9}ti!c<|2C>4Q!&!5((m_WyYP`}2qX zT@~>U4+s&Za#5>~YK`M?xp@Pmz4u^?LaCFsXc|D}K5-*6(b)7eN9Z`$^}oBnitUb~0j^Vf%2o``)a4&} ze+djym77HBDFv-g2o0{HvOzQ&QzdC>sUxq$U?q+10y4>y%t#6i{-AkR#f= zN=h#dP=frho9s5G*ffrdEs2j1`}a&2PRBKC(w^8^P|!(L#J0E!<*B(qT_wG6#JDJuJ<4IE^4*p+yByp ze3LzE6cViZN^GOJ8pOv86s5-v6df6WTG(eZ&tVo=Z0Ecw3G2CCZ!#urnI~lD&0KR6 zP4&Zn2;T`o$~s5?n;I5l{ktISx4NtoaEesi?a(pT-LToqD>hiTG5?`vzuPW4(=Vou zEi6&rq`VVB$1NgziqX;Ab)}6!@EEXZEQ>B^n%xH|ZS`?+ zioBC`)V#~xFd<%Ng%Jd~&KvTtbt)u4Dncea<3#G@Z@JvlXG<@)D+u(_%Wm^L2+IfP zY{7#)0_v2y&?HWDEj`mpX0`<*6zVWOv(XUtHI4+02tbETr3On-frhUt@)mmg{1M7l zbn^p*pXDcr?_PU!K@$rAngpJf6Hdj4a}dVS#5Si$?C;VS0jikSlb6kI2#Kpq>f5?& z^G%-)^>?HsK9yQD>T>WNBc6{shksPH*F`d3?95XeD&`A^!_S!VquCJsy(A159nR>4 zzDsFl-#(Gw5>xJ3%%oR)&K37fh65S0Z45(QsAJRnRd}={6SMM_WV6Nx?(++yq+1cO zlu<-UVI9#F5eYiGm!_Jte2VNz@9q+?ett zYZ>HYEogm;m*^BX#|o%?e}FQ=BJh&p>}t*VCE$aWZsPV^{90&)_o>VOL};BQ*;_Mf z?5&PQR8yLKsR1H=>CPkxr&H^zp{Q~<#S$=j)g4y znLQpd?^0@jnBto+F*E1$39=I?i%Yf7HOG_K0vHHChrPRzIrrTjwk0CoBm7yCF2V;m zas0j*9E`ELkifOHpxpm3^5w8uT}?`p%N_T4=J!%4REdchQ2@OmpjZH*+q6oY9Zh**#sxmK^3}+|3{rxtXiDqdYO_9%D)!O?t|DB4^bou52o?C24|%9Bnxr zP)o$pneZTbyuH`TU~h<}pddQ(iu_Ml!|-t@W?kitWnVJGnmO7J8wNmz?R< zGV(rG!i@T;i%(;`d#yy(cG{e3yV6E!b*@o)bFwMK6um|$SWMX2zjnC&T5NUAIZ9_| z;(U05_O5F-mn9icH+e~!?2;%nC$jNl$OD}m>%wpxqCksnIs*bn+yE-x%#8;rMgy;D zR}6L~vsC8^Xqq5~_Qv7%%U~vp=E||jn>{qo<8NnEquhp57?KxCvi)xJICp*xt8Jsj z-mr;DjfAG&IBMQq$aR4vdUI54tNW{JdTm+j>HTFUiIjr3ksl2)nKYG7Z)}Hk_6=!Y67fSaK5Cj z#|hsd;8NAxR_L!jxg-}cBzB5@=r1L;9lP`?Jk_QC#zpEIKmD9|av#r!X7BBeYBmC{ zfr#ua;nQn3(Xvnt*g`8|^h_<*8tqQr#+wsGL{%z5^Pq?K_iG*h*o<<|gg1r9au0{+ z=rS5wg@*;a$qh9P2%!-s|Ls;&eWkm_I_v)>Ofu~p`%dQ5q26`}9|ivG`L`3E5VWtM zFx6JRL!2|tFKeZKy5D37IAFVgFP{J$C5L&p0OZ7p4=j;tv;t{zQ3Sr#1u+z86#({f zOAv7deZ8s8f5iAKLPXtB3BUhZrRhM-CbepmvGdQxw{CYOJ3u|Qxc4U8YZ?`c_Xx|! z;8-HU8z>@3;*jA_CsiQ?fA>@i+Uxn_)pSo(z^H2T{j1(jIQ{|pqh;6T)EKUJ)CKVx=lh@~xt)HT5JveZ>* zb0u3H;WozrBE-qw=LEhHfWg~Lv@pgOVK+6N&89UE{&A zczltIws6*FBd;MB40NWLiL78D%;i&p{VAC=c&hEcnD(qLRAZaE7Jy6Qjcs{l15D3F z`QV|Oi_#O5o!%hQk;8i9LHb8WSrN3c-#!-;Td98znK`!A0m6}al?D!R(+DqiwRvlJNA37@V<7DOav8vUU zLz~lh3=n2;%0Q%>e__ZW1N{z593ysWw1Et^T}sAGalk!4K%kh6BQXn;F%l7^JD{|u zCj{ro|144ym=@N<`wnH(qSDGVxx6o3Y>0s*25yNkqS7+G!WrHe@imVdR2iIuOHFB- z=GyKx=36*l+P#f_iO0Nip9x#fHefoKus&$!kF^eT;wLMgMKPL35jY67g76tud~f8W zbA6MZ_y>y_rZq1zS&TpjE)2|R375BM5CpBQlXrEZ z@1>+9qhlfCne-x1Bd{aa-D-_#5iCI1Lul(VYX{EAPMETxpDHy?Ai3`yk+CAJ%WUxh z%ew7j`ocAr4puWl-fzxgxN70A_cX(Hl`nuGJ3N*1S*cf`NHG`+K8_helcrkHUhd9l)?57i=ya~jJ|b}+Z6gWqiZDFbOP= zu({@C@)0uct%%o2HS$7hb8W8ym*U!LOksk-1&y>>o$Twt;-$NIw;e*|n1KeadnAP} zBf3D*rQ)=k+V5=VNS1XTpoe{adEGrSmGfc;kjs=v6z^VK;!8`R3^T@9@spFnCsZS! zixU}IH|Eyw*HoOgyF={`<5yW=A0N*ljex5qEWx35cM71?qF@Z9)r=Iy=D$Ak@+DMg@FilK@_FIMU~Y22ZEfgo=)x)3x~Ki)YUk;Z zKq16YZYnck|X7zMB>`QNNK!VKkXj@?MM7`-#fQasw4Wo#2KdX zX1g`Te@LzO5@r^$|AS@p?}dQvV$u{p7>N6mjLzAU?0hbwndMb7L6qklo4Fq)bhz7E z8pHvD?&tWf_SeU5Ki&jbv}E|NvcCx$aSCB^e%XlXZ+46nL%rMri5GBEU1tA_#ZYy=RzDMZ%q%LE9+t`&f}q% zS8T{N6MX)HkB^;jj_Arx?h{7MM@*=`7`5o-W+98K^#|Bd=WG#HleQjpn^d&MRj+GX zL`v!;{bP#sZov)#WyTD~kjA*x@g;tEes+Soze8FU+nX4x6mEp$GkP`@zvUn7t~eH^ z5JD_Zelu2S4kkmexDa8+6QkQ96ZfOhiJt9*o@roGtB)U$Uv2z{eOc=fMuWh#ap5r! zCTkmwL)#d@ClF*${6gy@Fw8sp{Y3u+vh&+2cI2#(0se4N-}n2`ZDERxkgc`4vV*37 zI+;}~`w>x1F?|DpKq$biSL19>_FO}o>xeNZ-21YG0XnS(S8kcqOM89&b&3Gu+G1I= zAiU%wqHy%4_bAnR{{qr3K7BCVb8AT z9a3zA2h^)b3~BSgEQUDAZQneR>(x$ual91cwXjD;(Ejz@fqd`~+Uv6RrHVn3Ex0A{ zDcWA)!JRpOMKp=PB88E&XK?vKXuLSk>#+XuMgk>B&dx~A>sIH-h}~-WW4T2SUXcQE zyF81JYk|wb&O2y!WFR6Tc!&rOtg!k9t{KjZ{@u;#33X*GZse9wTgi27&YS@-M#M}` zD}MrT9ReWU1p*sTY>k%?A?}9%fuB?hL#tW)$2Ud0A5L8YSKe5{ki730_s!`z(&{EN zy(FHD7ACmfmZ*u~I@{K)Vqwvqc1S`vA35E?ll}2V?C?XPv;eAclKXaI^LWzGY`wI* zv5+%5!YYrOU93ATcVkRSgpGeU)3wD?%)JugZB#un~a?iRE z5XA*3YeAq*?tzIoQr#$W=zhwCa&3VL4p#BCFRGGHq=}$Z~m|W|G8-!qVNvGjOTl#aCp~@ zNqymynTaqQqDuciGDdN4o*z*myH&I(yOo5>DF~$%KHPl1=Az>lp7C+Ck@_g-E7wt? zr^A0ohK0f_!M4BGMz>pdEa;eny<56mNI0p7&LcTP*O>{~Lz1`gWZu_}oM!L_ zADkRx)x$~5*JnUvFv9Cr&bv~5funy zU&f1kFhdN;B+iTf(YXT))J-N)C>2-+6xAmTs`P18&trlLugFaJ+^3DZUG$S0?8B${ zN78$XO)vZsH-uv%yI(hsN&G>_a5Zke0EC+>F+X|$%0Dl5C3O*mdsaME(R;i<66-nM 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 z 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 z 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 z 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 VdN2NRcdq~d002ovPDHLkV1h)x0apM3 diff --git a/docs/src/_static/Iris7_1_trim_full.png b/docs/src/_static/Iris7_1_trim_full.png deleted file mode 100644 index c381aa3a89539b1431608a8bf623ca683c984bb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2383177 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

KVxx!1rFGZ?{0Pj6f)Zz2J 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

eR^|$m6jXzgwQuix`x0`73<1eUbxB~%H`pa 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 z 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

CX;6 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�d0;9aa*8yeC6=~ynf8j@>G*6axX@^tZ< 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≫r%%i^npy*5;kyB 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

B86}AnBc_?IAYJXrgKB+q1yA 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�)gAl4lvd1Nd9SQFUN% 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

%J(K$T&~ z63ucdKotwCCu6QsIEnEzJwO z20HZ`@rF;x?U!Xn9e-WhnZ;vDh4vLV~)aFcO zW9J8g6Py5ePF9F39w&t9(d_mY`TWNn>qT6g4DFjdk(ps24|a>-J$KOj%9ouX_1!+M z09CO?hOEU_8K(-_QlaX2TpTE;94;R2WNHG{k0~UbTi+5Jc)9L7#(v139UiAJY~361 zar}?vmYevIM%;AmbGjgSxskWuA}bdT81f{#Xcv&Uy*7(&b$My_jNxJ6|E$nZO>Qhz zNy#P2PrX$bV2dZx7U-k~>Ve{{2oShiQooSgR(G(tKc$H`yAT6)S957(!Qj1R!y58? zgwC$|<&>t0c3R(!C~JE6fAf*Z44-Akffj??&lq)4PjFe6fpX-H8NV9Dv0RS5Jzvp< zM#2}0Cz_#wh@btd@aM_wV%IcqiAs^+>1yFHh!fY(0o4DuApswB`0*MEHD!^<@Tv)w zjUYr>;9InP)^V?oOU6>3pZzY|*puOr^Coe%;dtvpKpHM#;^P1AKt*X>EV1a`~O7P4m_Tgb= z1An!Tg?dXrv&n@I*Zu#gq5&_bW4&<=Y}i}k1F}H!$@Icr0wxr=coI5XeOZq*rdFRM z={`I#Q0$|PW(NL0#+1|f z_Cgr{C~ur#3(OHV~(s zaTA05S2Y;nk-e;KhOeD<2oX*>#i}y?K^58vqyIJk86)vztz#>Pv+UfSd#9ch4Z9?y zy+p$|UMh21NUk44EArbL?acV?kv zYt5nI(7kOt*?0T)1wC2+dl3$_`%`&EVn9>~60arOM+9V6Z z;&x#gH~#RHma*AA)^$|}hKv+%e|Lp@Lwmve;5R)lGqgigORU-l@)LauTnenj}O z3a_mkU^3V~h`-ZAd+PB3szWtp=DlO^hPMD4Ef>2kH$->B%!Fo~ikJ(iUG-7P?dZ`6 z<;}n@gbk}S2OuzXGj$t$>7tCfXFOIk`)MPxkVPa86%fYCqwKUn&fDYmaR)?@ojjYB zQUZEHFy36|@J=tO)>Nqm=vZpdWjS7j7{*wK)<-w(orRLVv1+I*v3>SR@d8Bz%D!w2tPO3xPG$yk{r*) zc81@b7O2JNNAk{yv43AYaIr#A-PfP|nM9j=Alt92yaXJq${D7h7#$|^UBO0B@CBeC zCzZ)kjS7;#6~$J0d_Y33Vg3;$v~_JN;0Dd@%vNqT@hP}G{mjtZb&!_G-2p^rQ2YkH z3aVX~qyIH6cv>O(RkpwYgWQS(Lp3b{JF4-YTtP`sa!jX|wNt7b`0ZuJs9<$Bc)p%_ z+@Of+F_e+<9>Af7{E#NCCCu6ftrc)b?eDE{^ap(%V1opOq_BWXL>+z{W_7tVvewif z$puN*GLG5lr_$INRNRupH0gNpflC3enT~uh>Q)?dD(*;8X`X4htZ7CLs8)vR$finzEUxsYd*g{gKq$TKxWwEK zSr{FFjB5F^FJGfmTs9ZgBZha}|5;gd;Tv1>)uG%(rOpbZ#BYJek9K1=v?>PG(F<;Z zT5W)C8~XfUXj)j={|oT&o|p9p+@+@P_tRF26F0EF)#}5YxP0VW!4I+6EOdX`7brkj z2!1mmP_9njcqB)7z#|fP3`>~-*EtDMU!~0ngOfsSi9ZQc>xg4LDKD0x>2W0@rXImc z)_{*#%E@$hm{GkKgZz>acJx{lVDxvV$ajy8+Vt?oQ2+2Q)gq8DSV&*TflU`?|ZgB?fI#ZxlNb+`p~UwLJndd-#@O!hPbE9}?5XbIF^+7kC zJs0nt5!DNWRIfJ@zOrh>C(Ccf{*?bN@G*hQj z7lO$nC*_hqaUpLno)F(m*bB^d6lScL{zXWXm3Xr3)KZ3*GoBRh6Lg;L6BX>A^!P8! zUZrYtBjd^gTWaNdO4dCA4jp{UY+&{{o(8{U{o|BnbTIUlIK`C#_l0P5P-eeXJIETy z52}36c4?E8JC^4Z#fex2O~1|@Qu-|oLrC%8Aw)B{oc|R%3iuFsK+%HRz`MAx^3>yM z)~zEee%bG7*Y$n9?n-%>?R4M~Q)gl_ttZwny@LCQt6@4t$*U3>$${CJBCjpD>d3Lb zo4##)S_#aYP6cr={hvXWh?3OJ5msh&$$|of0$^22`Ic0vtb%Zl0kE24B^HJ6XQVbN z6$%D;!jW!$cV#ujgtR3_T&j>G7xEYH!92~%gJD#T+fqQ>$fOSf)IpU{29c{?2&zjf zfm+uIP=l=9k^dR3g?SaiQhzYZ4m9op7UK>(urYZ33_Hr_%788%L=Fl6pu{yy?D4BJ z8QVKOrIC<6%6YjC{rA=-uidr!5=3~5#>era*nVQ2dhTZX+OHHil?w4?b*d#4Zw@1G@r?#cFPeFl{2|HvZeOe7I%?e6p~VpVxt zbO6v*{m$jF!SxTPy|q&Oo8u!uO?5S9U0i6>aTm|`Bn2@44CS*kEAB?;$M_^acu~wy z`$^DGt6If81Qs}iRN&fL0<9B0AdM5k^tk8D%?ZJJaSfI(txLV09>MM@Bf^8jfGqSm z%25G%bv? zoB#KpAGtw3{femoVLdhWCOuV+)B!Q~s}0L#YmWzo*9ztrV2nPt@W%Aw_9MmVd3)(K zh@l?(R%@+`XPsWGw>=({5ekGAO#M&?w+sh|8k3Kj{Wm7bw$r|J^o%T-e>#*TS9aIU z9Vwlh5TWY7mftlH-^2GUMY;r+&SETZ(d_=YeBir(chw?0-d*vO>r}4B<5@@RU>E;c z2J`;u)ceLw*F&Eh_W6rj`ICD=vpJ5FqVKQz$4-~oGU_Q#FSlDXE@CT*%x+iCGJ0=y zeA#zeV#X3orjnUr%vvHm>nq?p+gl0f<_Wa((n9x{#0CP>Dn;eGY~OU6N2l)NydC=G zHQP6!L0*la%~qZ{JcGkG?4!W#@!(>@fU?PsrbsI7{w$xODUG_!J_Dolh5)cJFrZF` z+`X^P#Q`$2##{cdn67Mn`Cf{j2}@_A1ezpICi-zfalp3_yKJf#f7%(KCevz(IuVv! zGkhud9g}21)@qOe>9~8JH6ie(1iU**0m`v-F4|Ad6`O7OpK4$l;l zn_klUL<=#p4H6C=?3rzkazJPFvXmmo%z$J>{zLYqIBRhMh}C60ZP?p+Ccq#Fo{@N% zeV$T2;`2h3AS)A>u@}spmEUtDgV+mp;?4+nHv^PGCLi=b@rmx{|H~UIV=Wt`c&`o` zY13p@T9O>5PjBa?ooX#E*?W|}@!ErJ$FOwri&~_d{Ahb`=H|nK*V_~x=gAozixh^d zq>R7PaE*ffPe7iqMb0^c7pe;YsYq(--$L5Y+TryQBrHPO8=5cB=M`-uSQ@i`y;qU9 z-%#0pIy#ulf{i&J#Z^9qbR4R>A#DN!DcCQR`fr=)$9H(az@$=Q6Qx%D_8?Y-V)c7W zcLrS-%1o9rD>ob!5-|Jk*Nxjah#;Z0*~G(*gxF5S$0xv9B|9x;`_I&EB$)8^**uAT z#}yHxebb_x63U`g8o!fDM!*}5n~Mu6#U+9%DZgcqw48lE(1(bbKL4>OYY9|hxSMgG ziHcpGDy*bCYv~MZ1<-2l8F(wZXnjF#QA<<6)dogu8jcPlZ(jF)cxq#S3*#mD+ z8eJPVnt%-_eiHGrQFJhDw{Go}`-D?eVj}Sc+nC<;O(0piX=(^(;fnjeY6;n@+1Uhi z>=c^!NhBYsg7lzMYqo@64X`@B<7hYnPUxey-MsG&2f>A=k~jJSDp-ho6Cv*M?QB|t zD`R#M+xmYr5?5u{Gy#%4oRkK2(hT&*?7rzL^ybu`I9&eY5^Ab$TJA||I&L~;ME`F( zR`-`#!s*2jql8J_f+8L36^mmVFb<J}HDww=#f)4?MHub43A@N#)K74{n z@Xs@LsuNpPoklpCLmcAFiexSs2sG#M;X=lp2F-_XkBhw0?x&eP{6FHicww%yA^R)- z&bza=&vjaOW}GfP%r(=`%{u9y`8~j8-{;ECW-(YKr%6wM^NK0r#XMw1NzrJoENt(r zXL~}FI!+=vfo`&#_=l~;xLz&cb#D(_k8w`+ZFw}_rL{Q>!BP+6ZYDwrUAfN|F7&>eQ z8oiY0s;#mJA*G3#-(t-C;ADHxUYriA@-#a50d==RL6>Xx4T?;w*oFsKEs0*?Ps$Pc z8s*qTdycar9zXo*D^iu#aIf7c181H`g5UC;Aon%P(=f;-?`g=h67$9wfg5(x{YXBs z5p$35Pt;{gjeuMr={?^TdIa${>KJ-5<;1J-quiAhG}6kqC9qfKl)kJR)n>#LRUXN3 zOHXZ_Gvaf#jLX4|zPsT66nh}3ln1;QzT|fyRr8yF=H7m9!keSI$oks2c3E`t;$)}l zptM8Vb*}!wUt{vHao5OBxoQW7UNKEjlz~y&zmIbzA)(wW28AzuZFrr2LZUL?iL9C# z{_5}tkqOgCqCr#oiBaZ02)&^XCSX(JekXEAv}<%QID1KZ-G?C&NQiO7Q4AjqJV!@fd`&p-M1dUR8v*HYwDgj+3N_K_s zs_W&2*<(M(PYiW|Mv;8wziNT3d^xiCfxIX2K`n7Kdi>6a1~r#F%9+*Ao>Mta_TXIJ ze7t3Yk|{}NGxa&f%Ig;VmlO6Ee+Jw>S86AVf}X^BCiU}LekrTvM&^#E!xwHVVMQ{d zNEBZP5Az+enRxYUFC?rsHQ^v277$`U1j@h8NB_wf>%vCh6V;0`vP;{#_9 zhJ`#+H3GcvUxmi+p6s3$(~P_w;3ilC_9JYVv|p!6y%S=@Bz^G{QVcK=Z$KlZ1nCEg zf4w6X2=xSdWzRB8Z0{Y#c6iVoR{^OH>|L9bxkbp$pd*!+Yg^J6haPfZ)cyhJ7{_B@q$FrFc}!bk&tSC?qP(AZ`qdh z(P_B^9m6PR&Iz_DQoQhl2bf$(IWay&d%V^F-wPVio>}Pb(f`H7{ zyzBq}tjIrNAp>g?$DZBkSXlq(mRStPY8o}P6xANjenEV#$c&6o!>#vqtntGw(Xnn zgkHXw1;}L%F|;KNglQ2SDyyA;_;=8!g`ijB6>!DS z{K1I<0mWh4w9oY-0gr<-A{Y_qq~3|2*F_!mzeV4lW;$?LGGd>D&f*csmBj5 z%4xX>h_pa++u5}~y=SF3&A%F}jyRE*$ib@+tq&Kfg#zKZSQg+x4{G;s?tT_>!9~}b zMqw{90(}w8zjW%sJl3J@L-^>X!+HKdCqaL9CK(RlhH4EAOKDCAx{`Vu!H>u!huqyu zIw`U#=n)V-xmTk$3Q65?05M(4`Ij~0s)q4qlF9YN< z9HJ1j3DYk||797dyeZb&!8Pd{_mt&8Q{M)a4RCTZAdf{;7xZ#NER{S*kRJ%H7?Me4No0;tfC6}v9TEP zgGCHDgt;Yhy|WGVML~$}HQ>z1BcfHZ-V$W@5yM&cC_MN%8!7Fl!pTvUQ2 zZ7|zBCMa|LuiWf^!Xuh-rcpF#yvsRWxK|$@Em)G@A}zUaxGQJW(7xQ+iRCxSUmHCT z=AjN54{WKAtdRvAHwm7 zQOH1aT*r&yx#j*P6h?c+YmF^UMIkS zB&N}NE60YNNfWl^7P=^+74Rm>WSYiid)FdW!Y0{m$l@OM&0g@mHV2g80*eAtF*u_U zxsJy*8o1UOZ6umh6|m-i*V0cxr&KjGWOg}->jEM!bz}?6ObUEVf+>{_^bK0 z?%^Afn$=bnA;c<@eG-t>Kb0|qG~F*vwSRSY9CS++HZZVb6;ENo-lhD1B{C;G+-4`} zOP?Cxd1A%3t`Jvbn3?O&+CAZ~z3^l5y?&OnI7cxrV{j%2)p4W9+T87}K0sqvHRCfn z=Y0T7IP7boP}BQw>_^HmLEbWV0~C({52r)LUWulT7V}+JUjXi9zHDzd*@)Her=GTL zpuPW&eY`3c%dCK}{@zj?HBJ!`i$rY~7Oi6rA;Yv*olKQ`J{o7}tav9D);7<5f+#)o zx4M6Ydh%L_urA={=}dg4(f;bb_-nld+SL!-^L!d zG;H2J#2LHIilxTcmHxAy*K(9}XFaOQIv0AHC9Qa?E@^=iDpSe^ddJ?-Ns%WlC!ieM zy`9wlmSRKS=G)lf>3C_&goxhrt>cU((NLe@C76N>T+v5!U+II8l;$xe^j6nyv}D+) z(XYy4pgbW#Pw5?vvHO;LaD5kcjnWjv5c1XQucKn*_Cj>VhvV--z!_&;(6;_zJ*3}^ z9GgTzVQH$ESRxTP%_m8~P{e+-jhsGR9{u6lfbBBlEdV&@-VAK>#yoX)*fdSi?=~a0 zZz0B?KV_HeA^VeS&v%09BiiU zUdSiDve6^lJp;JVLaPMcknP5Q;(>Ew*PCE)T3I)F55#x1G0AkSC_nqnK&SQKbT3AL zS7#ZLzQe1R^aah1x_nQFJrFVd`A)*sWXLz=I&Gv28&i!ac@{USe}D?0FiV~dKXgfr z86ceTdUC<^i3irp_i9=~iA$_cZI>kJ|4EUkKb^lc+M7)E_KEH{Z zlJ#}c1KPYD^9RdjLQ_83kL-CJ@hg9TH^fazQ5hf6JMz65EcDreIhN#9i9q&JPVLQs!pUD|n{?a{7i1F2H2+ass@%jYOmcpic-2 z*CbT|UysVbKiOY!Qo5ZrZmLi=d2|Pf$cv7rvQ62C_w^@&f)SF!KNy<_I3N51tGA~F zJ|>Jp$b<_Ri8)lwIi*V4joGTLe&_RLox}}ujdIVS*0e7Y9{bt8QURxk*NhSeofjpQc0@QdsTABK zGYkF(4=w0Mr6k8RqX_j3gYajCS>aM*TDq*A05^#+-DW1D8DKCbpEgG}djzFWMMdcW zFP3C#kV`+LB8CM)AylBiIxK7>WhzBrlma<_hv>L?)_^y?l(-$;&j2kOG1&@s1>Vbw zm1cVAQPiI{>uI_DtMB)+(<|V#VH-ATlmN*}O*37Aj-?d=l&aVkG*z2gP%+@` zz?u%=0|rM2VjiUB6?S#~2(lksBc%q}Ew~Sv7`E>oFT$JzTmOWXJtT^{U4>`<_wcC; zTzjVIzL-jTK?!o)Pk&3h1A;N8s?{{OkIw=)_ga(#!N)5@K_JmQolW#useI*em^vRb zM?ejd9v8np?UNjwMgktjlps9%4bUs=7@V1jr#H|CWo77ko*Gc8-`X$*H#gnaFPg$Q zCU=1AgcZPtOd0W)Df$j>paj1I&d=g?8H!72=%N&MHEu>j8Q0B+(jT2Z%Rne4n(04*FF z$e*$9sDAlK=FQc)ke?`R;c(gJk%M2TM#>DA`XmGGm|LVkbq&XgTS95|#qnf4@AqxD zuah3#f0DFL@vdPxF?RisUims-%#3GzEk*>#I)2IykV)ODu`$1~_)h08*6^d)g&19* z6Gv&cNFqm~Iy9+Mim4hEm_MJ(l8jrX-4@3i4RV8X4W{(V$EpK=wW6+Bj4FaU zgDd&~Fu%}*F?J;wtp693mWYR}3X7|M+R9EoRms~%@*skayynL&uH>-@tWz^|Fnly#_wuH`7|w_o2$s8|HNCy$K&>-r0Mc< z=&RGaKeH4XPN5$|bN1G~vi3^8?LFOSuGt!&T}Qph;|fzv{~ZWmad?pV1cfMBRY?8& zCs7=Rrk_gr6|sSD$#k@hoJN>X$z>YvRRvQ->BCXQaEOLd0db#AcLJOd0Djcujy&t7 zlb&Fc)6~8PAIRqY?*>m^P|dI$xrN422d~P8C6FykqC1S?u!`dd+JHAMx2tZ2rEe9b zlvovB_*oM3)ysV5Ft$YdfuH}#@G@=QDo*1)RV5%vZEAaLXo_;4#v|;97_@)U&}4fd z8e)Ff=Q!n-L#8>3krIL(kEJo&Hjz6MJp0J?AJ6R0d;6%$_aOs2TK|F<^clp-3Gu4S zMj{Cx8rI7vl{%>ruo>agEIf%HZdc*?M)}6ciKi9AJ20QZVK3x!<$~ssbxx1?DrRX{ zUiE?YM%UMJljE8cH@+ZNqezR=C)MYa;YKO5LGa{Ntr>=Z$rLec+Q$3juj^IO7S`Qs z7F)F%@N4c=sRI>cRLPwTBMx+a;H;qF!r@TX=aUW%ur$YR^SuiVn#={EuP{q!eQvpz zfwz$=2dh~{!r>$Z=_Xi2O+BF31Ja0#Ig26WH1>UR_;+6!m0v;BuIa9gFYTT1AOo*T z?6|k6w~+@X)MVmG!>sTwj~2QhL<`N+*hi?!XRz(8T&rk$)Y`>&(+Sx#Tb+hQpJa4 z{|kZ_O5MgWihG6=vT<{H;^5!^eahC>RvzBz^jT(HoUdHY9G0cvXi~V)J{+1n&mlBw z{(jfKyH1*6>JKH+vZtZQ>aPWRLaVUv5q^_R5_TCqo+ zH0#b%F7z!ZztN}HupVf%#KiX5SAiZ*lpQFu^pwv9cYh3{GtW|Z%=LKmyf8t0nC8i= zFOt#U>vqO(uRV9yZeQB<9k!Id^8K=i%@uQ%Cz*%MyKvxwvKh~6@+Lc8FO?lR{!L+8 z{jQOweq|!L<8Y_u`Qo-6U+4n`8yF>9Nomt!T`Vaz07qJE%0&=Sz4YhM2*PJ;Jz&(h~Bp6?{j2 z!4-Uw?x9%<$FI_wUJ<9YXoL~TdCfkrHo@N-GDbl9lTB79rc>N4n34?CTlX56of0ag zq^Om*f7}H1SN*8Hk|?&rFxq}-jMDD`KBL1n9dDDXqVFiG0Y(_=W0**KC-{r`r`{%> z8n$L9D!k7d{w}XqJ+&H7I|-`IcjIwJ$r%?l2;Q%ZVKMEU;U527iIJ^mN6hr|^+?pS(JPF8h_8mlv{N?%xoM~6HKFR8ZXD(Q1XKA>VyLvo<=Ex_$Jkd#HTC~-e-&j& z43O?F=@3Rsx0C@$kCKucFiIFOkP!j`(hVwzlyrADI2xufLb~(0{?2pG^Y8P|&dwq3 zJ$JjG`+2|e3r=&b)Q6Ks!5;YX2jaQ{q(mg~?HcijZ@6^wYOH--Z#7k4UDWTtg`mZm z*dC{Yb^^)gy`dvAPSD(WzM<4a9yy27pq62W&Gy(3zux+x^VHaEbe#KXIy90CpIx^_y@!mFdZj|JbY%DZRqykp>}BJ#ZsCV5#xC9gFM73yAzxd4 zzlm~wbKU;M-|J4%0(2i4kA!L&X9Sv1^)iZe^=P?#tgRJXdmXDhyYONv9YXU`q$pZ& z$X$|vMVdP_p{i7H?I7h4LMEJ2Q|8k>%l~0fcfkz4CTf{$EDJ90KG~{2k`Vabu zxH3a!`AdSFp~0BH2R>X!lj0W*qk*zUwCiUcY#Pg;aViIYH4u6stS}}J0vJyzI+UDP zWZWW<4_IWXe*naR{)l2yb)@ww3uTO|`f9FK7xlLSmF9gT4jJ`=eIfiBT($fnZrgS= z1NF-Z>;DLdKLSL-1VUGmAeoUpuQZ&5(dHFsWS1G>7#6w7mOc#{_a#s&!gJ>HlEM}D z{`3h}iX8sL;gA)&)T)%DZf3+-5{)8kY-Xo1nPGtQ4+}e# z5ggKkanI0)LlP=;q`%G>Gi2F!LKg&E0vzX_6Y|b*)R(-O9yD7 zSM8M!_Fu1h`R|Wn>sur$J5N$pbs29&9usTwORIkg5tUrH5z%g|l}ztk(#2F9Z~1CV zWkpA3c4L3ou{jDXMSt*&V|7>}eF5$R<4@bWqTU{-z6Z{i2t{NPGh0;Jzv-;~V^AOw6#usE#`U95u?>Fk5uh*!BOvenkWSyw`p!n5hyc^L46bbS$&) zs#Vy8gHhz{u&*c~+_$8pi z7kw{|P2Zs41QK}dxr$2yA6Hic`@p$Vy?^*1lQ=20CF-*a=Kf3YYO@AWG-)u5w`q>x)~)OyB^as+7PG^hTelFGGdN9S)KcF!`UY0N2e zuW`pMYVak=##>qcHGQU?W(hpH@8S8_1@0l4w6HJ+GGSjGag!kn?y!i%$(g81igwXi zR(7pHFrQSD0<15gQt2x1e!-qb=ThU1$}8miowkJgy}qeGkDFXJeH@~-K17esuZBkA z^!w^RgK_hims~+kxiQgM2Wkwp*UxN00`J@}HDo_-TJ4qD83lf4E83%uY(K(`I}bc~ zF`5yPGsTw_jM&i&`OaIcqn4a05xli3V%>QkU2wcgR_!j4C^|m0--5v>gZ@ z>0@?yfQ2nG>mHh=?F{5Oddb#cQFP=Mb{cs(FAz0(Ryh3Tso%hwj_f2s?X{jxAWn*W zq1LvrA+L{4s`->K>~Jp)MSrHNu&8=FA16iF9ydj&iaF`Q-F0rZZ*nIy7D(Gc*+`VN3qH}TC{It*beB9t^gcg6(R z7+gd|$Jn9w9Sd|+9JUD4j1GTSR9E8Eya8wusEihv0s!KZk`X3}MONpB)TQ-pe|Wbh z*K%7D=%*fBG_p!#$=wT+8JfayV|GQUB;hPJnAb2p0>0k0K}Er#CQ2vH%29t(?%vdB z)!tCUd|Oa6_65PL50~^lb&sibBh|DxQ^ZaVf`%T524T8SyUhyR$?~=!Lyz;*O2jPG zCc7NFf`8foBZW1E}R7Khk28{!zNJ{sZLL6eMsE zFg22j(H2&ChT*GQb|S3UFX>{DbU4P~>GmsNYU&Gnq_a}yXq-?Lxkc{Ib>r0cWMAd% ziSAaQ=2mzOAmVq&)Nva^5)}MR;_S_1m_OLU+LEeNq2YZhNch-6MkagYZi& zHH(QD6HBFrRjQi&vk>ulNSZIT{x#bi@*SC}mh`m!CwWSPbbALF! z?yNf8p>tm6hO;&D2Dz@nI}V*zL@lI6yMu}>m(b+Sb#nM>jNsgw401*zk2;mU$5aMh zz#wT}wJ)^+tZwj_)5Um(x}-HO3$M=4#mzsqVbqis)G-HFlcYaklym%9$cXUnl8?3b zUsk@3GN8y6rO@>9r9&1>Uo1@vp@3yjD=6FBZPq6YE|h zGb5>z!Dpxv(euB4;bsT2Km(!OCNSo@43*3S!!f%#h}WQ|+T8*Ab#mc z5y}VA;7bz%;=rP!oUNFX+I^As!-P|vjNMams zj6JwDU@H?#1~9x_C$qyh(O_4!!ou!lEN~aUCXZ*h-^4Z~>!foMRBNC?hQHZx;Fry9d% zV4GLMb)>7)ubjt}pSGaK{V|2O`c)d4U18WUKZqGI6<3#sSOkx*i|ytElh|(D2D1>u zF^7hJGS`b;{D$F^0%aW@E7r=7>Ris03~*%=?-E6{Il1!qBRSenSNo4F@WDTS3*k@W zs*mShwi)&37)?>rN2e+CQ__>y43~mC*BlN>1>M!bQTKVIe*BIP>lyD?^Z+cR_TJQ= zfNG_}-(xc3IwG7D{A)%YIZmIZ+edcVIQT@uevff#Ongv5l2bBFsEC8q>_8EPxURZ% zT%?~FjDm7{xq%^A>E>9YBtc3?o0b>9qCHC;Xiidc9&>ACefmb>FDjV{SfGb<`yIiG z6ZWeZjma@9812N*Flg_|tGeSR@Q8dF7iS;WvWZ80MnO=nAAwPy(7ECFmhFMH*fGSe`aZgetBlbi%}jw$?k_|D&cazjY>ot-t$D^sc91fs1o{I}ps+-E7*H()7#TD?4g; zQX%Xet7P4MYCDonLU`SM*8h3kOx*U9=`~4c_>Ljo7LUN34Lx5wW^2ovcX+{j`imRynGEl5Ne=&e z6!a}_gwOi+Zp>YlP8uDc&pc6<0kOPW?i_i&^>Jol-r8chFo~J%ZwIl^GWjXrql+?3 zL$C1iXnmf@iPMvD4Da};q0=9lYP82aS)VMmy~XV7mfofLY}j&uyIJ-yRoa(H-<3lm z!A959^XTnzqE~fJDaGK+vbODF^L4i z8hwuvep#WV2}D%w`ARY)b?_P@qgd)4?{U)%i=19#M?&D+-GB{mi_6cKnEg&XzQ%+| zcVrO|bYL}(`-1sC1E$$t~?f zHDL2s(QQ|JDytFflFX;lT608F#RkF<>!{vtz96^w(3|EY{3}3|ws9BL)^fE9Ike;k zHpSXbt^$GMaY3VU$E{mBt!$IgLH7MzQ=D#P(HU!qR&Pp{8P_oAVv$}TJZ6KO2@v znNf_Ut6jzZ3X3dBuJJb#8N{63cg0V}ykm84q-#HQ+G6Lg%_o3VwB<5Ev9UP|%kNhd zx7N6J*&Qg24F4s4%b(~Z&K9empVtt|y#^z4A0YrUTr52ATxtdAC)7 zGTnbq3WO%cL0)ynePZqMBV}@AYb80Fx+tUuy`ukGb8q?e-}|u|Z*BMnQSZ?1f-Pn9 z-q%AG^us%^-6bfZrH_8~JZ8%3^JRhdo(Dh!Z!rkFbYP@3cUM=-FbvihOkhN{^qRnh zJ_btJ!9(Z7VAs<$o}aL(d}k6bO2iokZ5j3CbGva_BxD@fN!D ze3W`m&I0)(`+&Y=!c-L-jmy!gSp(lO4*pAR9hE(vFPgq9e8gcSj0xj`ruYrEoViXB z1zK=csbY>iLq`#*LUzFC_ahvs%mGZ-AWFub4?6(f97%;{P>w)4WG=Ei2cj`lEB8sF zaY-wlGy>Rj!LiT)38N~(szT=kf|5~;PwoYak#ETYvdSz@nPZ7-RWR)IH{Y+V=sasd z72Ba*;fYI;FSnIEd%cl@Ik2c_^&@;T>I2?1oIhHd2K;3V-jC6=o&@$#(gjppMHr`E zeGCcg|Ia*iFNWzS0ojX9I>hd#A6~W;@oFOePw`moFF8f+A%odd96&(n)61?>pdAVP z@{%hOCrR9TNFsKYj<@YB=?rj@IQiW_0=kdQ`oRJBz5Bpso63V+uPGVH0hhaz+@m*@ zIP+18uy`(4Ef}`jm?2O9i*8ci7IPRV?<==et}Sx>C*XacCf7=gFiKNK<@P_wRR;DL zQ;*`57FQ1_Pgr~MZX+LvQS+!EBiP84Uja&`crkZdFK&I9dVICEHdVFez%wUGh6Ii> zthl|m!|>TB?7n!V5<$Z3^d3vbkm&s;ZQ0AA_f74C5q9b^K{ZFoe9p&7*t>si2 z_mJEI$p2k!+eL8(I=5XqfkB%Moek8-T{MyC0n^HsyS4sm62tTH`v^Y^U?&G@Hiqp} z&^miD$*Wp_0HY*t1N*^JRR|tl!1!w6mF>^sVhBeo2(Fy4E_a#kJ9%X@$ei!nf}XAu zRd#lda@>Y$j94blLVSJ^u=$ye|zALMXioRl93uYO3?d%&)I7a~(h*@KGs7e{)8&MiBP$=?t?l0STfR55 zr&NB>tZ?((yme1H#2oVmbJ8SGdPjsg33`D&E60IrYm{spU+(y>o?HM&JG&0p9&G`j zRiRiqxn7E`v*=kUxp}YjC?>B5+O%=-I=?3s=ngJ=Gl7#IdlNJQWF6*sQjuu}Cu;Hv zv&wg7bJ^M94twD7A*I;wv{%PuH5*RUQ?ZCwUJ9YwNLU}IFGc$aYo7*fNA6#ds%L- zPHZ6D*4!a87IB@&m_*VRZ-F#^<7oVg#sf@7GhP-ykR&IFRv;i|xtJ#%`t7Qww+jyY z?W#15PS^kDCc8Ci!6qVGZCpvn@S0C`^eF)#=Lf6q4XO6D^YqDmjLjo}Rvf^^P8XSC zCv`7;c4Fqs{Z|BzY<(7TomzsAUJ_p}`cEu>OQAmMC-{8RZpwCHVNYuPR~0hjf6=*sKOH1Zg8 zAojp3HCte*@&2j8g|!;`IK7ux$g*9RsCe+ywXHUhl!-(Z|r=iyCM@Pc|z~;$#8Gx%148hTOZpXml6#Nu{l^t zVKle>$FT3pDJW&H=eo+p)ix|LK+UeLuO-^b`Tks297P_$jgs&~0fd5qYd?X3);$7g z09=u21$)?j5<@li*{>_M)2t{yTNcs;rMQoWT7`>#OsaOCD>u}X3}Gu{k{RDQIQqc` zCIG5n-;ef#1!ws+Tdd|L)5Dk8cO?wo%M<;+TlQw%{QaAal9{~0raMlWxLpljuA<^M zAToyZk4I@eI~#m7u8>J`krr>4^TeW^%Rnv{AIr`wBJIWr0!oWooGMFB%_#V1wp=u0~#-NcxxjpxdI zz;LJ%Zk>$^U3fe_-p6K^81;>LW!xIFzarR*sbzMT`&&gisx?z@@iabL>Czg%8cWp3 zG7@>UiLV+(n7q=4b$@JJsc0a_L1mk8s2_MuNXY#`ZRR$}$iD&yyqK>cqZ6;2o@f)| zw8poe<~%6pZ|!>a$!+BASS^VbrI> zIq!o;^Dmj1jrnSHMI_O9YK zb`$#jP|HzhJdpG2o-r|jS*0QY7?p`XB@y{s{?`kScui0ZyU3U=ZY#j@firl3ePt^Fmm4wjl zu9sW03pd6P##;~e@2#lw?y_uUotC4Mf|b{;g4ZP^;dlBvT=I2;8?EM$m1v8R*2C#N zg1gm=@`*e7&%-Q)66RovfB5$GaF5loJj3(sIJ}HN#we(+Ds(%OUBA-$I4c}m4U=`G z{HtC>`Rs)m{1 zO-k?G>;p-9iOvpGmNzibR6WqZF(?X}eRa<5I+>aO}j4-dM9nOS`qAAzk9sLW!`do_0nhLE~q!ZN<}cpyQN7g@E>4&FKD01wT1SM zbaX!JPJripWcFiwb`v!v3^TaulXc@to^>mr*+=x+*fr;3^d$>q-z5#??kA|frqz$1 zd<>ApN^qf`W-l#bPPlge1Ok{<2#8%ZvwBSe;Uk086P;X|@VbkyOs-`1Sw!tc>Ckv@ z4_4fF=I;S8o~8P|3X&nO;9+O&Q$Rw?B4=ccvr5S{B&QVcLl49I$3=ykG^3G1LAg8Q zFSl;e@KVT!Df4DGwlo3rDA#Y%BrTFm{r<&P>lNC3knBkBSv9NK=2ghG8(yqDe&-i1 zgj*Wq$cwpb_-eA@C)0@hUK~O8Nq!AYS<3bIT8BVRxAI23ie$Dd!&cSuzg|qavw>7! zGAm}|IoG8O@~_~&mhz{UgWv0p8puwU>B7$cChCj6;~3CcX8w)K8CH_ylGBY_yWa*F zoW79`pc%rBf6&?%m8y-Qrm?M#l&?&IY6U}cr|p`S>%-710;0#ww-RISp~aaTtUWm& z1MRNc$(g9DMpb*os2PZb=f$LOt$TI6GE}sP*Hg5HjE3urA2Vc2YSrq>@3L1yoLgq7 zqDKf-M#j~(rG@ZC!K%>PFK*7~1%jswyRrGiQj@<RU#5RYADf zNT>>OGH!cyC6m+0r$x5%?6fl{9I9u(Motku^4Oy18+VId@!D|Mt+jdl2dkr$lP%rU zPyu5a>eQ6b_6r`?&Q42CK@B`&1Gt_I7qpF!mDkwD7b~wc@eYoE`CN;%Lx~CK={7n} zOX91PKBgX}0cM|=_ztDVQpIF^G6n390Af*qW&B_0KFK=`Vt!~(-bIP8f@lXNqxGW( zyG^Sw0ehBkkZMY`TXI$W6g9Q2I5Yc0A+9d1P$%qRs4=^glmk0xz?t$tB$`*dbyd2zy{R#}*XW@g#HS889T^1CD{N znj*+I#8+902`$>_mP(~0vyRY`9%F{fxT2_Ms3%Y>E#g;?2q+nV$UN?pA8tTCV11ZM z9>4UD-z1;72VP;FkUK(BG{aCrz|llBj4>Dst&$?)?`>(`vD8o4d1h3Jv*;Qx{%G2; zm^Fzrs{I{@w4U_Qt4bwCN8PLssZT!|1-9UT7hEDR#145nWJ`u9=vt{Q$n(BSG~sUl zPhhvE(eQMYs&)}82PMs+!cC^oDd|Qumz;|OL4cS>ED*s~4PjlX-O2fT`i@hYn?72< z|0;#yH?<*bjKIUQ!Zn}j&^+3oY;l85Y^)MNWBMbMEDbmbw_qwjE(ug;NuXE#d^DcH z+^kL;drwjV1Z_a!*~}F@(hJufc&@=u$tBFyt}#^>#TEgF(ZMG=Y*UQ#a3FRD$UCwMgZ`N8pk> z7aJri+0zj13z;sGLyk2_>H?_vfPVt7rB};Ne2QpifF}j`s+_8!=vz)oW zm{D_~e&b3X_f-5TO*8r;g2=!#LtaM0H&93%)(zDR?Gc%Y@X+veQB1)t-+01tX@9if z6$~+2}bKS9epos=c--D(< zht*6|=?DI`-MzCd4DX$-H42t;nmb81rjwV=_TH&7l=Yi5&ujts)VhTxzax3sb99nb z+?``b!s}p(p#H^dl!*t%_Pm|0JmO}z{~hbWehORISX)BpOMrW7iP_1ya2*6obs_*3 zF9>qMPM`j+n|WjEfwF5h;*|GQ-sM6WcS<82#UYHv=Ja{k$SqDk_<~Q`Ik8e{MEpe` zI@SX{ug~cZ8u+vGOz5O)UoUvoq?*q>c7*rCnEhWFLbMniTkRjqq215wXbB=RunzfS z<@QZ-ax?X|uex>DD(t5VM7`r`i173MM`d4Jo42B*Enrk=Cg5ENomaN#Gta${)qx&javz2Y%9~x262gWp+?c zQ*c?g^~4_6al z?zab1alqU_&2c>d1j5+@m$a%AI1og&*w-JJcpeNxa%tqJ`dSQtp9F2PNsAS3ybV3l zrNeYoMI7HbDF>&hLau;jzhFSbsUO&0p-Z>gmRV%v{*%o5Rg7}t7czZGZ%##Rw1PeT zx-)UaPTLv{SDVI~!QZT%4BQQ4ro1AOG0Lgc~YuH1W~xBh1ZG^4NnO4r=};;I#m3-)0v5nvvMPQCO@j|F$16s(Rl`A-s%3?@a;^&{QZk}`R;qg87+R)42`T68$mLZW@(Yf zoe!y-ylG5I7edoi{Pnz7bO<-^+eHio978$PQA_JgE zw*ZI*AoBWpS3nT#G4;I|&v*Y@v+~qe>m@f1=*`oEv(m$8J?Z7MvXQQ$(O)d`B{&)4 zyj>jN&hf8I;2j`BM&dDVqiVM-11} zOm6_~9-d0J;R0rWa?r7~K8*I;NIv|ag23zh)>Pd-Hd~D$@MI&rJoffNpZ-SFdkbv3 zba;4fKlx4m8XXox94xnM(Rx0Q3T{5KJ#JkJkh~7I2$np)OUt_-4G#Va-~^Q<`=+12 zkIEBwF8S*l1+Y*YG#-sv_tS=rEb$B&?*l~u+J7YHlNR=GZ9oc3l-;kw*58qa{lF7W zcP0D8FaGE&r8D{L%XMevzkb_er4A3J1O+?VPQoLXvSO-l5`~X^r6{v>+Zvq4j~l_A z{k=Hu7ga6CSDtSP9ubhBmWRO0ENF$~3To~FBQGNvCZNBB*Iedmzs>|}Za8_QrFku5`-NxZSVP8RYSdahRHK1q@$PDY{0BJC#A z=-4ABmI|S9DT+}W-4P2Le%?U>h&c=>(FR`U`2%3+<1vv}{os5pfXdBK=_K6)vU(oa zApcvIIQ9=atG=srp;q))zHKF)q`JQPierE4J^Gn<_B8XRAnyO(^oSo__fC-P%??5y zGG7U)d6($rovG}aC8^KSx!SUE<+p%lp(ADMEUyO(Md$8Z$#exPIpcwsHa%Q2ffHnT zIA$q(GW52a?EOg?GOoq)vGC_5nry4rnR7 zym_E(+Sk9rRp79h@(iAVUMD24Lp<7jAIdLM@;(e~5cw>8>j>Y`_klcwo3#Sy9JtL? z2ao#W38d+@awi_tlhZeGfPD1&Ws832BmsI24v^@%_n)dP{FY&tK<83R+M27JtAc-u zXW)fj(!sqQIMvjf8xDc?b{5H|_)tXc?|^prQfpH$f2|>=@%qj0IxLlj) zBh$-q*Ji%mt2=@J=SpxHJwK%5*s5Ua^B~Gr=yU5|*dI^YyJH_iZTf zwMF5q-ef=VY2~_gxX4Df%6GXtW4`a#d^C;nIvrcvZ&Q7&M2fQdRCkshVyoU#q?P~r z@Pyzjnhz|J*l86ugh`;_W9LRItp2Z?JNGo93s)3@pp)%k5p$?pBsTdlc0V*K2n^sI zxwntIaTj|>S0gU@`xRVZ6#fb~z^aHLl9>P({^T}@ydx;XSsd$$aqj$0qRd21#Kq-zk26te~C z3c$bft4^+Yvkodb+_QhFcc>JSK*wh~fn%8PuVzm(neoZSq>D=7HGe1xhs=Ww7ZtrF zU>94XWeVUNv2m5KwR~e~{n)uDf8MG7?eKPJaNv*<;8o3jdL-m*C}-_s^XR~8Yh3$x zwq|?B5wv2*gKx%^AzVj-t>Q``r5z7Y{8YDGbM#^sKk%kKUgxf9 ze_qWs5GNp}O*vlSkV_5JgyVg#r=_rS`x!ayU}_Ym@U2S7JsAJz0$zT0zGyR2YM|!E z(`1%x_i1ZaB*8wlsuuC66xyf)H+`a;2iWw|kn!*cSf373N5z(<9#9&>>&~0_1!SC% z`NR#DM<+pj(v(rT-#Bv=%tJ`%2c8>!i0dh9i|geto*%@XdL+@b5e70@10t3# z#|Vh9^Gy%=UI<|(QJ2s~>0UyHP?r-3Z_I0lzZ*)Ar{o;L#SONv$wz z{EYW3MyZ=Izbm~{%<4WULEdcX%MSBS(*(d6SCUr(?$dMSucFewDiNR2KrSG2(hjqz zv@&m=9uUiz=h&(r`Xpd68z72HI7OtAp2JGW7~>kANRSURAe?P(-pRp%20HT@hxA}DO2u707nR4U4 z!FA)N>z-1l2GVjMOkPki? zIg=REgXi)(6{C{KmqK<{fb9ak*uriP)@Kk57FLKkNTL_(l@You+sK2lKvMVZJ};4Y zD&256e~{FtbIMA0WyF*X%Lrb(+|n4O`%Ufjz%wd@zX*%-H;X@gx(<<;Q7w&{wc~9z zk4mqm6nhSJxrMLR7y~)l8~cb^JD;}|=95m7Hw^p5tkO+}*r(Lxcdy?bz_ZW8nU2K& zfh?D%zJYdx$M!?pN5xs4+oC(+v+mC@)PudGpdR7gBVQZ(+6N2k#Am)8+mNpZGP{m4 zJy(+7-{j$B(iy>4j#O(XHuewJ=Sx)XQql%ToWH*cs0^H5BbyG=^F5OPV(9fxhxFQl zaMSB(dcQL+nBnFGVWE6CxtOiGdONExb{Cx^X$l!$jWDhZApze91kJ=b%Sv{B=f95> zs!s_z$C0GJVA8k`?L|o|Tm;hi%#mNi=Sh-U2>>*O&Bz8wM6^n8xCQcbS-;^}IBkaD z^(P)Gxh+w8>$q57bH4c8{Aij$Jnz*ie2T{u2|t?2X}^By(B$?oP0&0-Tq`-}=r9~x z`;&z2ryZN^9=T1ndgtL_#jik#jG2}|cYroF;qoeOCABxuE97rstLy|MsrlL8c%RGc zMsrZ9vAjJ~_OdX4pLW5kZ`A|WpI8I*9e^@Ea+|h(aLjp0k8e1->xY04e5CaO<)Xy~bRZP(QE8u2(F7NvG zK+Xmv+F1&~lxrzKUEb1CP_yhh{WkK8Ka%xq>_0xqNyOY-R7Hxs6QD4mWKu2y!DA-? zP{nTB0%keBx&RHzgdewj(c1eXb=(T9koRP3hfV=Ub}d80>;o09Mpi#12z*FGIp)An zUz~^TPoh0KPw5dbj=#!~B6})YoU?eWP1vVPiDm$hoUZrAJlJfGO$kwBJmQR11t1Z| z7XWtCb}@;c_TwW3X!@}k$=m=f9JWs)PwBGucq|R@1K5&oCow=8l_pDs9o9Ic3FAno z2$=q|Njt16*!WOG1|Bm2!ulHd;LS+@kl~5eQ-a~yjfdcE@theIthD(Hl7@b0V(PpS zX_B8*0h)IblB=+%HV8cO{<`DyDoK9q@@MK#b#IVn`wH zufOCk6WFB(PJHZ8q}iGcn9{JuC#ehw51?nV@v6svd&#q^VD$d673eU^P#w%C@neda zlvB&PwVC81hA>BTIoI>y=6h!f3E*1JE5|;z2E=%B+n10pXDX zIAN)LK=om(kncP-lS{XyJ={LnFHy?BGu;% z##f_>_G&~uv?$iBUTHJ3x?cw3*e(BwlzE)^ICs^GG~aARP(nQoEC>1yeG1L{@M3h^zHOvo~D^=riABL4b<=>kJ+?Mb3*QPhjSE%3aqPjooUf?&EJl+rN zS!Nw_og|PXTeKIvf08$5#MeOeab3|sP>N{8ZA&5C)l&=qF~4{!R?gq*Zvcu1@+;ca zv#GpkzbL#3Q0;?=n?k`bz0h~OUMn4hcD@g!faDlTlCZF6POJ>7k?_ zvefTaU~e2{2=D;qu*1>}Xcj!w!BTj3c~^uAxnc1H#JXtPKF6a%ml1ie5uoGi}^lUp{mcOtbTaPC857@6% zy1h7fx?z#kRKIeqt=*u9YI!r0vw7&U!Uz6?ABPvZ&ZrnkLQhzs&=3ApectwmlkiLK zp&~7>4JesX44K)a;U-l0@LuhOQp}_^B5Kz&641KVLe@L5Rb9Abv~281%wwm?!LZAl zydFQ7Qe(GP#kjNng*IQ8{kKEz_)eI3K>*Y3S6b2vGUCtKbKYE5zo#AguLVW!=oBGucn|@pC~xP(cSUS&ujrp zf|o&$e4CkWI^z7u)_u?Hkz?}dSDx5&K95#{ZJ$baCgP@8Qu9DC_T<539s3G~97^9> zib=01cFM$WH+Xi*?Qd;O zau(dg^WclJx5+=WtAnGS@#bVmQ!rS*YGiP^c)I@9<3*;EU}y1?uk7`#;4KTTZV&~J zO_-AfVWAVhAYms_!;C{NkTXJCdu3b?2PYul<+d*xo%m3}I~6i2vzMgrh}&TU6b$*Ve|PxwCv zD=EE(Yk4VlNPbNqSmXfu74-&gNx0gc#7LcTY#Bv-U%*8n(CZCZS}P3;ApuGK8&c&L zLrW3(%Js(cdg4yf0~6jCJ7swNi(w|7GkF(lh1eEHIaSx;_BxzIp$+ZFGZxVuOa4mk z$E(b4{n1CGZx>az#VHw;zYsLXvI#U6f+|K5u}^b>31JN}3!k`aNSv)m;O(jrY*;-` zl>6nze_u4Vh}qdi+;P-AIoS+CQW|Vn%nimO?yAtgcpzH41h-2xQ;#PZE`ipAe84E{b22LPJ ziA?N|HMziKsVa?**Y$BnRjuT=w?9g=C(`rI=W@$h~F{17T*svuXtI(^WJw z2inxdOlK66e*%>j$&6kR$e_u@aA4X&lJm#_55Xwy4>`SIHXJhS0|}Iy2_tVcfUJh+ zib!_j5n$kf8vCk0ONT5I65B}WqnAn5z=7f!FFoqt0X+GcAv+-1O0LqIzzObu5itH@cN9AWrZt-g4M!fD*=l&` ze>K$rQnrz;J6~5kum&({yVcvuQsCfwDw{VvXN)i3|7X5`^&7dRbkRMAy9G z4%ld!4s;ByTLLi=9f~i|3_!65g$77e$}0E`ngvz$FK~i5M$Js6*@^?mw|PA7X7iX< zg)^_B4{O;6{u&0z6V-tLDd}n;I#kMpv!LG@oYAdd>*3Ii2J+wHT_1tVE~Io=HDCMo zgMpz$Y?wOE z*oRFPoRULz5_HfCfB>YwTJ@|yO2RXN^905JRLBH2X3Ris{vXLZ9$?<-X0ib2V_wgL z!tuas>ZmtfmdEF39MZtWb+uB+dk7+1Jj)#f+v$ZOC{_PcdImMS7Yj^N3TjPw%mM`-)-LI)iliTlOvSgi|NVm z#Kgme=qmi}*j)FV*_npgtH~yX_O2nN4as4DeXPT;-7cbq7H&QM+r0_P!VKQlUD?Vn z_$@N|1WDoP-fg=r1jzngE^no8xLBjYk@rPg?Vp}JMednY`C(&;uvPXd zmnKb1T(|ph`Ac9R;%s8Hw`^XPl?$HPNW5G$imN)10y}61UgyjnwQrnv_lkwj9*nrl zO!>wb`ZD+g0#BwE3_m(#geo@xocB|TPEKKx!Dq~2xPDo}=&t{R$>4LYr2C!y?#)^)=5z^8(R_CKZT0MXE8$&=TI+|d=5jXXFdP=vUo>>C z)DUIdZYDrma2R^$Ozb)yB79W?h>b4+is1*AndW`fDAIDYnVYXUBTPfB$hvRc&!O4* zQ{SPUxIOW}pIueATQG}Uz^o03MJCdjI3XvatR$5!9B2_N!0LtI4Xkd=N78yijyfHy zxlJNs2LZNu*fi|jh*HPymsIbJD%mc}yBn5j*X{86^7?=W_OEhk=*uS^_|I-_k&O7o zJ8gx0BXWOZ7JVWt+v|lRmqZx636gUcj$`&-!0-yM0vN=gPb+YBHu#kfziXG)KaTCp zKJ5;-Uvy~x^?7E^?o@Tp=zTcFYoWDAz+>ZtCW+IPW!ybhqFX^ss)!XZ;`x01b&xuk zUDJ)`V)%g_5;``QBKl-cp1YRBfyWA|Qrds<4job@os``NrF=uf2s|y?t`n=DH&5em zF?#1L=RUoOEz_^uX>;d^)g4c*jb|7!x@NHf#qPOhjArjx_7HjpjH4JFe^ZO9UT~sU z7bbfk2R~?jnr*+o;JRde>q*`)JoqfX^sR%4(-SFjlv-<^4GPF|6Fmj@Gz@0CU0WGu z1kXhVajivts0kuKuO34wscF}+#X3Ju9N+NCp&1N)h!l6`)Y+XQpDpDs83orffS3Mc z*D?m0J5Jgc3p|%|jzsvRS^o_`RbLV!JSl$OrS`~=J3R@5Qv1f&W#VNC3_yN)26Kc{ z*^ozn?QQbxVYIk^iY>7_?1cTg$ux|me8hz*K$>SfYALa1UoUhg9#G)}E{a^JM;!=84 z9v~Q3NDDR{2(@DSaevYNOKyNf1LbLsGCr6z*{ZMakRoF|1hjfl>u39N5H2)ymri~oXGpgI!o>U z!`WX(Mg6|tALvIBh8z&28A9pq4(UcpN$HesB&8c9q>++PLAtvJkS+=792%ti+0`xx6gK-Tu=S)RarT>RnMVkwK7#^+r#kINYP7+xr zFv}{MvH#WgRd75?Aglf#bLUfn{RvRBc+>81Vddjbm|>G9u0V!!K^jx3r$$CQ!x*fs zW6&}Fjqzw4h}LrickCoGgG(lzjwEV+_S#e8jYbXGR~1>*|BKjG1f;`ZODneqfu6q*dh0*}66lpA#TlgIXG&r=e;4vOK>L zje)0at<+yO44<;6-m?Zd?VqU%U%S2d)kc2oFl`t!ESb2KEjP(Kf@WjH+-p}m&Q@!#@t;F;h|@XDf@yj+lk#h;O!%(e z*4K{CeN5Axx?~5(Yyajy6J!+Hqz9&`*FlK&hg>v^?g(4?TxSG z{2`%l8+G$yTksBwQyLn1Z0NCg1p3pkOW=Es`%iqI;4y{B@H&ZE1YDSa*<06@UF~rQ3&$4DPEI{J z$tb%Xo3o&-T_BGkrlWmn@|UbyyAlO>4rKfGvKXt7IQ|YFEx(`u(v@ihro!X?2N9|V=Dd2Z7y?R(iRBw8= zVjOS9U6{qDk*xJ(^Da8MC)0TGHpbD9)+Qx12UBBy;DLP0Cif|uLg(OJQ2CjzH(A3p z86CgOburqVzrgT^ZUX_upzw+GW1X0?Q^!qtq-(LM-dyjY@>^R%rTOQf0a2@J{&O$5 zvjm>MdKZm5OZvyaHZI6xTY!A3Xxi$v7~kDqHgD%IQWkcn8%pkU(dS|$8F8HQ4UXM~ zZfNFf7~Yv1U=+ep`)$IQu9P0G!o>+DE__Lh1a>ob+)k-vhy*G&gMybFyxkTJ0~?8D zg-SZ9YV@d zF*6fmX)w?1uz@ewc;*8GznhtEc?6 zY(6i0`iwn3FUY4XX`A!uMq{j0!|-#D0go86nf&r2F??X!lI6*Q>WiKZoNSE*9g6si zTq5Y|ETk;)ea9U=(H}i1t-2h%7DG(5IgC~a)u$<8fmCrbyZ2UKGVlXyx+G9lak^3pBeRuwm4=rb$nepZM zl~t7q){l|DvCDxXs9&o{-)h%Br!G3x8YpjKJx0|)k`!##ssV5%W zApO?#{jUe=%8q@lS9l<-@3bG3U39xi_h9$5_159L=XC~=&$ZSor;~wCdz2hBkeD4N z;Qr$j8NIz8PVsfzbu-NL;>09%fSU}@Vp1Pwhje5R8?#-mrq9ribk`=Akrh_)Ld0)r*H?QPLN1N} z=_kpvNL^Jw)g;f3D%8~ zR4kA0zX2#_8!`A!{tPU>#Iq0j%I z)l)y#5cs}Di)!^TvD^;O;Fo{=0kaTJUT{wi(c~&FfebN*qC0BJBn;T6&t#*egW}Nw zL*pZ?UVI6b8H=zIX6(;H8-Q}iTM_Igz*f~qIq$J5C2<#M=Ny60EQa2+qZ26#JE7Lv z&kXXxrguLr%_^CL{A`>qS7uH#xKM=j zE->2&hcd_XoQfb-oH4Rc&m3(Gg5tr;e6VUEjsjs{T?_UQ6ugBZ!dG4#rfZ;xJ+kS7 zMQFzQrW(=S7m{ttfnX3Sc8ZlQT#us?LTE;+%!(K3a}(|!NH;|sR0aLlS{ZalcF-Q! zZg|vzt_$wWAjZrN#@c6GX2_&FgpO^rltO^`G`t9uwJ0cG_%0h{WpVCrF_t9D@WsCRO zrb+)L_(}=_=6U*A;}vlb4A61?@2;*y;$W7zPlyjGsIKhw{%Xq)S84gmph1+CSNy3~ z0^*MsUD2M-d|qAmBa$t!t?3n3_5I_bmO&$66&>@-gzuCHgTw|MN-IrgdrF%&RYqYc z{HH>o=y)!3;>h%c*MtK{4Oi@tSdEUAi9IcDbBDI|Hd??|Xg=hRMd9K)GjGncqirm@ z3gf9SNs*K7=!bdIN&`=k*sp&jY8v<=(`eAEhP({o{%@^w6XHjUu>c=A5j{>o5!L*k z0MYiK!SE<_cipwRw(%`odbjBv)sHXxks14nZhu_JHom*EC>d$)ywzuFj-|Nf&yN$L zA@@e`WFB!S=f*i**Y|4os`&c&OgL%A@i|fyVfBmWOGActz+Wq+ytrzItP$oF8~`~m zUOpg&H15?e<{j4#;Lm+?xEk4UC97}V*x20dyUZwinJso>wU#}j392p$bXbIl&Rw(+@qD@J9Ii@oTlKb%2UzC@vGaiONY3u@Y~ z9Qda0Te)ZBZ;LrRFeLtY{*Cg>l6R2K^)I)W=D%NE~h*1#EHG zrhnBTh;m8{E^KMjr7U}pj20Pov{gu|EMHT^Z5=uO!Hdfx$ZnxOAOUaP)1LKCfbqu` z@!u5}EHC_qx|96_hw#P-Df%qc%_!w*YIf|LMwP0y&)2^HEw`ut@8YBu`V)`WK$cmsb;vyZs!@Fh zlRJ$Y?Qw0NDjhZ(fjUqX0s;FwL;`{SaNfh3l*_C``Qjbd>2J^T`gW~g-6(?%w-vVh2L1klNpz?EpJHnMlB_dW zSAQlujp+A0Ji4n4E}4*`6A_H(FuzKF^C=jIR5Pmx-POd5aa?{9gmlb)UxX+ZBSXZ4 zUyvc(t=I_2sC_CexZ<&Te(T3Y?y5Vz3-cRu4Wyuvf(#>6V^nUH&cyX67(;2FRTNA6 zkQEZ)M$Jj~<_wqfPvR#8eIr+dK6kQWe@Tud1J5^J*A~w&mg0`J=%9L{EJMebFv?9! zJYZ8P|i)tnlmVC@$#RIs>Ec{meRLY~Y5x)|*MH4~Be|+@M@4XyqNZxxdl(`5@ zY+gG~K#RIpqLoaluu2kr!ZT3_g!&E!E-TzVJn?j#akJ5MYC%rWKlOUy%@FT$g%oh5 zJ}cNjt#|E-!#lD1ml^ZRMg4Py9cpssL1asvRls8Gns|(~29^fXeZA-nS4z`S&zWmf0ui%r1SoHHr=- zYB4xs$mtH72pycCSc-)et>;t_M51_!TQhSi;*t^RX|ivi#Aui}n#3mB4GYHOE?9!h zbX!lOR<;AqGFc6m*P!%=DhBD%4kjaGWG(~`qLo3ws$*p+At-@}48b9j0kTf4szPf_ z{OlJ0$-mnDI(ISDo*>k2W{Oe-Op?zY{o<%AI0Ch^!P!n8Hbe8FCZ`E2tDZIAs_^LAote`pDd1e+ zc{Q7MsXLens%B)Flr-AasEMu3ye@F>4K0~2{26gIgL^dhjMv|OVY8v3g#E7%Z=bc|c`|ja3T8m7?L(#a2XzdHcl5!i{QW93@(#`v|voxo6 z)5Tx`17859IQ1Yq60|Sw{Rm5Go|^|QNH44a(F3$g{yxN7zSCd9SoI7;01}I?{i-Mr zmF`uG_{DVWGO<)JoE~LWb44O(ceTC*A^&))C3(KsO!#s6kLR=T-qxv&?TOV&w)+p( zCuI-D#=S*t{d=Bk8{w>g3u)VU=C$6|avjF+a~&n8@7<9`R=rMHL4$%QrCt|9oVdD37QS8ax(Wvoc;)J6&5qmyVzjDG{e%u+;XX=W-Ka z9MO$vWperKZ-QC7NEVG3ceS;vBu3hc(I{c&NjbnzO2t{IxqnH9501v_&5IM?hmVm?lp{`6_Zk-x7Zvxn4@c2;Jx9%1N9ij)m`NXtR3GP7}cmY~8;iB#LIOi+TM} zL=^^mvGFYOmDEK8$E%Ky*S{mQKZmB)#HW^vL=#1KW33>B#K?K*l<@N;uhz+gSnc3H zV%U}{)^nu8{>e|u)a|OI2FB_TBN8(L#joUv60+rvcoeZ2LQg4)lM+qPu#tj6+hkgN zG!W*;JU*abE6L+_1|^Z>j@g*J^iq+N!Opw6lyW6a3ICNAfk=q|#A+>%$V~sF!jKRv zZ~EnkonRkMo1-iYN>W!>Lykr{d7&&O@SG4_@^qOv@x)?n?$~JYwRl&SZ(&3E5_e|ssB~aS9zLba!)pp&u11cZXsL@ zdLG_F+#vgkOQ=|cbg^x0JZNrY#g}~XF#bl=`9XGm)22pN6Fwt(?X*RO+Y;ApSZ!X? zB2PfRC!*+m$ocv|hrx?}?7z^wU3TtBIqAVYe7Wqpoh3SE13oe5x#~8}2@tH5Y^^4X zS99pD7ASgU!TdU`Vgf<=BA_kDzcFh}oPz!INzVlq-m*9J;Q@cKGN^jDEOQ(mvHDX(%2zqG9rJ|F=WPS>v&WxZs!66FO-RjZxR)y% zZGF=*dAx0RWdK;#YyiQ3Bown9p3E%J`N}Q^U4HgMMS#byvknv@e1}4JcXqDXh(nto6u36P^t&G$8iKx%&x3%B>*?3 z11fU^SQiaasJP>#kr_DeFW=W+m>U8W9vc8Dx(YL#QhnWpSI^h;r>|+OYHV3{SmvTX zoVbH(jbfv?dgxRdELFf8m?}9f*#gr7TTt_MBX|5ZJ~pYDc0eat=0f3$y!lg+XNYol zSmUqvXAjqQOJ}=OH!AbcB{$Fa-NRk`lbR*w|{YxR}C zhVQI(`7@&dXc$iOGCSj-P@tgV1*P+a$)G47EQoQQh(@GjpcF2QFbVym+5 z{LCLyDuX0dRo8&M+vtpy0pN#R=n2MP;)vxFCQ0%m+nu&5~RNJ3fzc2K(YxG{Oi8v(m zIyi(Gv{^D7U3+^;lpU8o&Vd~u(GxU%GaoZ;34a2tXykERaach15M@OzUW)O`oaT^S zbBiA5hmYG_8j%ySOma;8?@2dU5$rZ-J}YkFR4mndqHyZ}tKtA%5at!;=LO>RA1ra$Kw+oyVgtdBAKnL&o<+Fy1J0Vn#&+3WfH1VyD&EzEYi4^7gJZDmI}V8J$a zK?|(CCo7%6O3QUwH+qc?E4$Ce4Dk* zNw`EjOZ7%g;ZZ6tKf;k(3v{lf47Q)ntO7lntg>x9F6?(!g6@mNu~j425jD$n)uCK~ z#g(%g$Cy zXv(oUg21pVEGZsThOt^dmztiUGs~C%qj_qO8u(55m6~TYcCBGi+X{%2e=L!0%U}=& z|CjE<(@8LbOhmxt*{dGHl^=200Tgr_xk}Do`;OXm0+`H)B}xGfmfPm3?v@#9x@C-H zOMjv&L)O|Kea5slqoURErw?Epw%Pp4!&#KKorH$q=fb-?QD-lqj0a-;KX4F$`hm)J zS$Eab^i2BTu&;P_v$;3*Qmz9^Qd%1Bs;)}JQBZj_t8D&1piZqiHcs|uJ%4^?2&*{< zigLc`7&RkGOhXcKhdJ|nrDRKPzN$wb{UllxyjQBULrucznnj9z={6#4*VniKBjl-4A0Ne&d<3uvbWx zU12@~UZ;rcY*F9C-uH=xXIAXo$3FWVrfLm_m@Q92SHl!s%dv5v+*5Ba56Jp_T1G<7 z_Ibg~$=*1E3n*2Q;WtCPDsQFsGh2(v7AzXX_54<;`@Y9wB7LsnPb$K*K-O?nr2~Cw zqjvG{B7j7nD{^HvNM|l8uE=;1gPVtxQVJ~G{;Yb>V;Fz9A_G%&El8Y(A==HEz@zU4 zv!LUdU!xmLpsbT$-O^PKhdWk5DiJ{q+Hvp`kly3H{3%&7h-eR#jH)+_ zpUO4&VbqN=@1R01#nW;AoF|z<&8m~jDL}uMfKMFgzRfH^&w1)bJ7>-aA4_#Q<^M93 zYYq3Dl+TC;H3%lFbmQJM6K1OItASwVqy0?6z@=TlRKiIZ%=n(_x7N2!`pTa5n$7O* z@b^Nv8KTxv6LDs|VTsspx6w!b;p_T+PPB|zaF@uvA-4$T44D_wVQ~6|}t>Q9|Aw zMFgK5bSZa`3p&y)PjB;L^O_w%INuJ-H3MkugS~RYPLWapfb6>`UDu#}q5Eyu>AKas z;!up~%^B4t;-shR<*A z?Ajco{2)1i@4{Dg21qR;ihe-aAi_rgV)Azyh)!c*!?#h><_%=LswELP3qSkhFwq)o z8-nc!pDb1E)B3XCJRx`JvMn?vVgktdSAU2G1$ofMnl@4Usa%IyHP@M;$V~;yuQfMu z34TONZ%8z<&Adt*xQqOlpx|E|Du!Ua8n}Tt8HjZ|8IA^Ifil}^lH0oaO z0$WXE82cKR)8F6rEKkz^tmg4P-TSzKuR^Fkne2jYZI zAQd+dxh^FbJi>TA(eMg}7jwo;G3PVfo=#cVAEl9g>dxRrTd<2AQXIijAkXJx$MFsW5UWo7ofIrgoW~JdJ2h9r4uuYf#O3n5Gn#db#$6MxtfUh^+ zVbg~?=*{1j3d_=w+uVJp1*2eyUp$W zzV_)%Fxahxy*!p`{!l_>c&S2RiI1cA?|6{J{MH)H3V$qP$yzCisj7}-uM32`)%m#M zfw1y&rfu}&>BF?kNY;bs_D;%!$ivRoo2?}u4*e{9q!e`kibXvze&E|n ze-Zer`rW9h@4%?*Lb;;jGZCZQmQ}iN&thqB6hSc*A5T0X&Fb&sgX&Ua#?ZB zZd#FV`NKUWvmiy#N=Bus!|)?GGH!IQ29-)e`YO^5#V30Mg@Am%9(;lq2m@#_3v3DTYZzSEvDVM(Jf5#B|Q@$&Ef;%q)bd|EdAXBe~MYYF6Jt6=y@HEJB$ zV3sIim552h81=H}<#g{c-rE0q+Gyu!(4A>JOv1qsKwMX;Q1Q<$WNL(iH6}W0TRV;Q z?l*$B%rnCyn?aPm`t(T7S-1Ns4<{`GpUo9ce3nw`nh_B#vg1r(ArDj>&r#ep51E|B z=(J0EbMoIsL*kBwFUSMA{ypbpeM8w3HY?#ypQ8S$lEFpQRGpx4NLltaRBljg48!}N zx-&u+c(JLD!`CW?XZGa}UJdfz2mWm+!jdA6k}hg|dz4Z0i;6_QW_ zYU7zqDNF`A_~%)vR%UDC|qeaoFM8Yw#Y`%TpfL!`*=_^z9*uzf+1(XJ?(K#u0l5r7Y37wkJ6xHoS7nC0-XB1W6Rh$Yn}1vu zf!#l4@{fP}rP^pL7gTyQQ*@&~F-fJj?jRla@{dr|4X-4)9;luyj5i;TVVlR@1T%~n z*C(;ll$(Zc(mGxaC>P|?hx>hP*a!G&H$mwlPtBP{*9TfG+2;J5ANr$vn&RvZ`7Qow zi7&{GKcOZQ!+khn517t)*`AHFr0Nc0M}VKT22h>)b@G$;V7MZQ-w1eL`%pejLZtS1}1*rRI6hVz$YpQI4irU@z z_oxn_>%{dXNt^;MF)SSp>PlD*0X0+7U6mN78>{@B81{@=m_O4pKI9?E6zqM1#eR&YdZTtdHemYn!hc?OUQP)d(4Tx>olOwO{TVzx>%1Qgr6H>4cR2%pdTBQYy;LQQ#nL*;tkqlMEwgz|392rW2|z zKVkzO>_h|2v?G2xf6Q3`C3PG?#|2w^Vp1hgDsnUPSQ5eE74NXdMzoAcdE_hUYUDcF zfvKSF*n|i2s)JDVcb?dlT_*>;OQt{rl)DSQw#!TiTwmyiY8hbS@OLAHtv_N?U~(JG z;O4SAe!>pOk5g06%(610fnLXMr15;NL^~+`q%Oi4f8*qWYNl@!tbnnipwa%bRIUdG z!7mP^*`uH?=lQpLcgxO18x|B&rC%(^yl!Z!bsHY&N|O&s3kx$TP74MzMe>0??NW>9 zT^3~8z;ZTswfPllnm>`UmaS~}FiomECxq0YU}d?>let+KS<-`9HW6P))un>j_<5c= z#Df?0@SzHjQm9&f}9dqcje=tj$o|rX~QvuV%LNXpY_Eb(}vb znV6z=4uB@CoOvfdEo&>HJ0+o6xjOL_A9+PTVeq(x&KJuUWg`etD#zc=Z0*0@>J zL-dH>AK|paZ`1-chB0B5n4(@Zfnqq1l5wCb9{=Gu2BBY9TgxA#ORd+TyQ?0*^nF%k zYG~-TP8U7s*mnPDeZ;VD?wj$v_&K-ZN?M=v&-v!9{$Z0?M8-vr16`*7?McML^^|`` zETMl!pln+-e4*@Bj(ON_@}3dkQS0f`qlpYlUHI~ug#q-%+CfTYjWJHCKy4S1jq-OMc3`i6fA)Tc2l-sN+4ZG&wPTysp|o>7p+Kqed$zRQyRp3qk)HnxqTE zKF_LqI-+7xfW9OhatYMoBM|c;m2l?G?a3=ZT}q15>D8#XSX90Kd;0A`54(m{bGOTb zSxDc1EauOLg^Jx_oI(Ed=%W}NtD0iew=UB!`E7uo^~IqEC<3{m()k&D3go3Z=Jcp; z*qW*5|~+h^>X^Q6w?Z@iRsD<@%t_yWdd42*vEAEISeSR5QB)Ra_$(wKW1D?S!AbVUXzQ@458+qaR#G zOjjwi*ZAnauIUZ2EZyF+!2%X-Q?1ClB$(^%Ue*AVY-x=@n?`6R4R#B3cKxyJJ<1#K zDsz^KEXG1vvY}d_IMsl%0Htp4bH@b`L?Px07}EX=)?A|%lz7jXOgXGCa9(6@2l&=) zUwj-7H^|Se7cDiZcSwK{WFLgC4|R>18gTxwnWd5jYns@>Jfymml@jd8f4c7Ft*Mr) z&+FdJMuKR$6L3PfjDYON9a`d;-8;e1a$sE%Dt|L$i96bTzC?ShT61`A&zMBu3Zd`=y&l&#eXa;fjaOx6ug8VT` zVVQ8~*UBIXOAl~fy=;>7CxBr-&furSR8>Maxf1TndjN$3n1h~dGJ8X(L{x2=bMGzO zKO5?s_Xn6mB4#eOf8U6L@QSO@|0#99q2_`UH&wDPetQt#dx(7y^Gr|w0AZ!r+33uI z$kY8=^sCt%ZFraxdv&q&+YPjZvD<@?2!z4kn$H@ZJ#GXe;z;8eE#MC;@`LBOKE(m8R>y@Czs%d^1t z%iS7>)dyvb@95m~h(YUv25YE)_DQYm!Vh^=$Ge6=ygo`gY^2liaw1;ai3W|~rfSS> z$eEjQF+3fkHutyw`#h7n0Nax!*k&bSS*%L3puw9(0)xh|+x(@D(?6Uyg=SonYr$jESBuRib0CDs?R0m;xR7%AFAoV|zXD z_Ad^1b_gnifTpgaw%lV=x#Bz2Sh?zAK)_+w@zh-X44b^f1*hv>92wEtW9&YLT+3%3 ziG*p{FUCe`42_gU2mss*ki|$p@ZJJ1d|T=qv7*K&)E!uLyz}qY=v51gZ@^Cmv4&H)Sgc=M;)E%2HqLdBU zhfIeyAbI%!7NUuFYb$(rtLX9YJiIsyy7FL_$HDpk-thrt*PpQLA|Fn4c;(V`bTMmu z>wC}anJK2fHe?#%Rx;X)9T=h&$sGBk@0(k`1xUzCS{!-KGgHps*Iy z&->t?jb*W|5zb%9&->rpdh|ay9~N;uT%7M(f$R_Nv>{Z>u_4m?2mpj?*rM0 zaRR3%moeWd^+}y_G{6Cfsw4m#038gSIg^Zaq{kq6J2AQQr4y+dgU73#L7f<5QkRO= ze^&aQ@Lg5|$?Icyln+JhXvAozTMHMaZ8a$);!0QX5RF+d;u%IqKo!^Mnb2q0Z)N?!-x`Fu zCwXadi(kV-io#4DMDFi&Cq^dGpMV>@p?iIvS(x=rXr9}mRB$XGCw~$+3yMfp{Z(bi ze<=0I+-*;cHn1b1k-n<<=c7f|u!IRh9LZS8PV;>I(4L|uDz^d0)67FqtB7cAw^GcK zHtS$4xJ?H8D2HFKr+u2_7k8Hw&W$ zmzTVQ(F@>rx+4ahKp)WWcm(SK0e#@(8&NBsaqC_JI5A;GO^5Kc}?{_EVlGk%o>Lr{h`CD+Kpn1W_DV3Cr7fuRLW_~N+ zjX>u?k?-$dG&YWaR;W9wPQnNrB7m!`8;zuV{WW)_CRhuBswl}o8c>z2Opp-7AXpv! z=IytfFE}u>S#)0()db1ibxlP_uxR>`K&FlbD!BjJO?+nyy5`)d3BHCG(>P=LBDbgD_5mOPM=d#Lus`L3%bg5*; zo~pVYY}%%?NBj}9US`(O@q%Pnbeo?4LCJaLqc>6uJ0Ib0Yn1zvlUMu$ zZ$gj+gaAMR5dQ_^C*|-G5*9DWC_slf3EsbWjL#o%srin0YwJ%2BGuR|#;oVWo9-)YJT)b?o|XQoyQKk9;m+`Z zZT@8?pCd0wtzA%g4wEzR4Za2G5DjNllOQ~M%k#OxFIG&wf4NhAX+gsQoixp_r_U!1BDBmJH zqdQ%=Cg+cs(OjA_TqdTjXQO#XhV(*O537jvafNHCn?X9?fT@|ceJFb@X04_y{Unn)b!$n^NA=`({v*)mc8;^WPc zH5!l0n_KH<(I%*;l01R~&LP#7JnaM*ymxHtyq=}L_B<~%%@G(5{HkXJGbFy?NFjf7 zk*0utP$qMjP4bg1_d3$$LEq-C=FLwQp_ZS2eplU1edlomwhl#TU2F8->5_}Z@Estsr0k{jG|G>vmn#6Dog=9~y3Tf?DX z-VVrhf3qF%HR_r;8D`%?5}d&X853kAgHXhFHj>KI^9!L7hq>Ui*pQ>x=|s1ZrsORZ znl!!-KHA4%_qrhaM)6x_cw1iEhXH0&MrU-Br40<=>PgK;D=V8n1D;uWE$?Ak)P{BN zCZFaU<5N_xw@*u`_)Qs_nKsGm7dm+#NE@$&Xhw^1!(P?m=k4y$SiNgTso8SCEnwmA zlI*#Ck2;=4ZsF2^LBpQ<*zoL#hD|;m#2^ad`z-%2z7H}bG;-Jt`hQIDew*7#_vq-2 z`pG}1g^`3Q^U17Ut_4&Z>?->oesvw%mDlY()*aQSX#9V*SoX&&=Ra9b`3__8Q}V|w zr))Kc-$zjnBiL<30>g0#NP};4o}nn3Im%;yT@4~(f-6>Zw4Ijg_CdwD{)F#A-7ML} zQ)sH<8~Ctn#L$4txdqTb0G=jqDc%&BStN-}w%i0WHZ_<*)U%f*lLMQ9Gn26HEvZQC zSVcnAh>n%j8!k_<^!mMA~J_j1F-T3d&Nx8 znP7t^s=MJBM2)$y7TiW)aW4EGj^&8((_j{vz$gA2ntKsQJ(*J?aPPl4qS2>%HkW;3au`hzK=`r>paH8 zE!Pbj8qxA+RIM|DN=e+7-w^GFkv!+|O3d3GIbG~e4wpVZ#TK)~S0 z1#H{KnNw8cZeX3{;3>TG_%x5E2Q!w+>qNB(D?vfDAroy-bw!aFNFX)9590i)IxtrY zNZOEiBvfTKz`m#u1BgKyA7%sQI48!u^ik3+VkL*TSq(WRe(0Usn?a^5mE~N55l8vK zK9gCT$Q(8=D~`-OfSVV+SImyNQV=w^0na$7uF`Nd$^;1mx8>feM3^gR*s?lGhOMuG z=4@aM|9`c*R9?BOO-5rF85423x(MXYrcP$LgenG640y9efHEI%F#sJmNGX>7TC_(= z=y$$4w)yBB9|WIN+|qUw7lhpetX#7jjt;qiEWQ2cI1tBx z(gFymny>$h*$yZ#ATTE;NTI;@PDo4#pjw(or-CsnO3*sPv#$M=JI9q-9NYTmaofE` zFF(*h6u>dFid9@=MEYZRAz-v~0W795F2etQsYV=;n*%CCT>hVb8pW;4?Ll$Qb?zFE z&rr^D#6onvB;Nza^{pcJf+viJ;81$z+=RKFoA`#Jd*Z?BnTiN$%wAR1sLhEkw{|V* zR3wzr^Zt1-E5At}H0)T#9MWrw5A7Fs8AO@_Rgr_tP=mnE3~ES>9`KE?(Xe?cKtY}# zZUJg}GP%@9Jq$>FNP1DSJ1eZM>}k=XV`Mn~gqXT$tS!!*cJ4Z%m&LrrbJ3`!7UuJ6 zS*~yZh_mWW8@#`}mJ^<$ZD=C*c8(6qZO_?smL+?4FPM=H{NKE3zJG&W_E150Dr_%o zs3;lt@s+`$9owYmNcKScuFDpDw`%uOZ&Glt=Pv)cQ)Wf1l^;x zz3)PLowUNCuD<{ufV{=)4AAY%uRvUMRZ;m>xg`}RXU46(mm9Z`~bvV%*J zvc%q^#HoH8*-(m|)LmX_vFzmANv65z>!`XMwPRXty~Zb{$z_stHB2}azT|M&Z;9g# zO|p;t37alK((+M4hLx%8pA2N9pi3(5M)mvYfi#R7S7?5nrlUJj1u!H-aj8Cg27!>be+FhOjL2!?awPqzZ2_!~jftJ`NX)?`lGzP~AB;yUx{hYpOmbzJUCjyV{w{sjei z8RdXRv-am6gg*YYn`bf=486`m(-?IId7ZxG4dzZA_dd&6Djkq7q+vnXqBZ@DOJ0ot zTgxY031j}CPDD8o2o%Ah8VRG`&M)K5(6B{lsdN8-3}tzo!mgWpP$@cA%Xm(LeSt2d zG1ho4VF0YJu)-%~BL?h;&HUb+*V2 ze(fJ!`(7qc!_$`<{^9#XiTqcUEt=6pS$I12KRbA;oNl5b63>&GiJ1ODsClCEnC%}= zrJ3iUELwk8`|fD@k8_@@h+gs{KFO*#=*7S?{)&G1)j%9tnLT`Ainexl%Qgp2*bv%l`X~@Ia;$k)s ziS3m8L%b!+e^@0wCjALPvzAfl_6;3-E|HkHE}6FfLxxo$L*OhN?1l5o2NQuo5&Dd`1W8EE>-21MjT#af3jJu^4>qf$1dXDrh>yMM+f@R)~ zD*?o^JgLmw(UtN}3^Gw+N(2=niolv)W52t6fw@rmA2mKxAA>*6W3yur@^by^_iKY!gpqnJCK*(z00b zcLc;E&FesZy<6krkT%=**09Mz?4(VwbSb!>&c*R^jOhA7YI&USRmGhFCw$K^yixHY z{2%@o0&-lrj+O8j8ru2-6g<;RvHlgIjGllMchf;VODCgK{*de1@)OdYBHPo#%(-vg z6Bb{ahAPAnZ>-7>NY|DGqk;s6LqMYJkkl?#Su14r-484Sm&|F;h0U6VlB2mKUVkX4 z4h3`E{7BKE5bwP89)<8p;FazYVYAyc&8UdkE8sAXjvXK_#i*zT}eGJWGw7N042S@+L1hv#j*63=AyZaz3$o=P^yv@$6VpYfg%ulfM zOgj9Y4b9ROYnP|YS^Fv3ZCD-_HT-)*^^{Mi^0_Y`v-n(qEpuy9D2#ESE6sAHIvFkh z>cYrNWcUCG`dw4Nzzt^*K{VXrJF)!zs4_x4r)|)xvKxxQUAN@zPmMleLFul=>Q%}l z5k>P;&GKGmGP4PJ3cW7U78@|zUb&w0iWl7O^H$N+o`Rk)t49tRa3DNV1;8YzJr263 zq1%t_RU5Sd63#Al29}V*O>plE%(5% z(Jom2I}cwC3Rcuf%5Ei~a((YVY~rey9ycknt?l7Pq#|sS;SK)QClfmdX*UpcPBX&jEt{oL&3+0C@4kb;TL@p{aFJ0a| zefdWjU~5U%NAOJ~)8v`d?8luoQ=2ixHBusZ8NllDMdKgs${>h>ZTWx>`=tGsDtg?c ztnwEdbS&acS5@r0sxKZpEdhgIc`&i|HkC2S%yW~h3)!PVkl|pArzz~g`uTWQfX-f zkp}6I?nXj-NCWB4p}VD}W002az3}_?-hceT;h`SFJnOmdbzSEv7#GJ;E{L&3$8Kom zZe*pB>Q~BO%4MTMmXxs@p24rXiVP??_TcvEH+;GJVd(r{^R{{MbtGkt4cX2^tMTc-}3=tBbzn;k=h{a!uImmB-HUEHu?|i z!{bRXk@1=IBW;q9p@Wx|Dhl{*0dIz$_^|1AM?G$nj38qdmivOSY#fPCO%W9ezS6$T zCIXK0(IVDz5ay(f0|9V4;ps8SL1)|v!6#4>sL4GC)<~i(w_#Lf9ZrQ(5U}Kd0HMqblI$7eZW8~g383c;kgpBi@wS+ zi$l2odq|##AVVWE2uQmOV&`+>tN=4$59Q-5|_^GY@&D|KkJfB1WyG(o+Piayo9{Z_c!v{r?Ht{Z45}W zejTWU!1sVyz%rr*yXJ2_fAHD41uV6lOU**W%O27R;gAQ+f+h9O?eM>kgLh~ZwC~%( zhoCJC!rgL!&qJ8Pz*dP|N`mTsQzt3z`cf|W?cdeB;CD%ix4;hFi@T-kq8d7v`VdeE zEV$oW%AZjds7Mu8G8d%_z6JQS-ST*Ih=}}aB1JIoZMaFg6s3F%3ehRzwneWedG_?q z-j1rdJC%T9q%Ds?nP5@VWyG9~N+O{44h)g~!7fWA6gy}^cHj(7_u>J7Zp+etEH`FB z&W&@>CEj?V+Jh6>Wa)KR9zb=}9S7ZUaQXD|5OGhh<}^ox694Vo;g^Q+qyU@}(2KRc zw<~GciD@uAeQaNBsO;IWrjGRtni?H9hdt`Os}3dsWizJ{b5?(>Qvgv@&shT@(qj)V z&s&4nx^DlyA%#jLJnp^|-8e7QD(4WMS9RMuER`qaxFvn@_-5(3&L;Iln&XDrL}Enp zrdhi7mDgpegtae>I;)Y&4?9cNWObKUNh~8^A#DmEa2~u)aV5@4?=_CgJ1!5HDRHv{ zkmi;WHrz-+@6pFk_d@x3n6zY+&G(z?e5TdOdb+NZ?@?!0PS-Sy*iqBGPJn_fXsxm9}Z&7+c4%WyQ{n#LFz(6c!!v;7UU>uldGFJ^<+b>!;+-{opAC>@f z_)Uz+p<9+)Qx+SN%U0niJ=4uwQ~2Oo06?olT;4o{Q>gW~hTqn?AKq zQ^`QTVTs(u)_%CF*U4F((B3t#OZVUuL9$(A_<12?OLP~&R{M9NCXrX>fuAy4`$fo8 zA+qy0sBI%o`|gymEVX|-UT)cS!s|SmA?f6*@2XDAxc9M*P61v#R@kR(fhz+2S`~cW zkc}=j5qvh0(t}o~bx=?PU^b}c&$ia62(EW358kDx_~&b>J1mVe(ece+$%}am-X1p` zr0Xl-v57Yp@6aws+LZ5JD(cKiBNVTM@or<|7$#6I_hg@C=jb9pc z6D-gQAsxTug8>a)5x8-zaY@BiVT!V)$kI()8a81{zvJKXWM#$=FP~>cM@Kl0SPxN6 z48HyJjVD6QxX&>$t?}VHBZq7!vm(L33>A?-1caU%VUsf zU|tNLb=Vbw@Emkl*74Q{4fWfCM_b;SS)#cg9gfEfv^}a>)oXBMhc9{o`*9#1d{48> zNkLjKK_YKR>RyrX!p5J;oa=Qj?lX8|kl6hIHlQT#OF|v`uT!MUeW^`Oowqac!Gs$W zieAq^6L(%Z_a4MTr34~SD>d@j0M@_aKDVw`W=SQ09wo($%^(EwY(8corD{b2Fp3xG zspf3@<&ukW-0gsvf#gx&t8xPzZ2qUHkcA357UxZl(!b5P`&$PK%BXHxvVFJ;)?npU znjdtJlDIKyW^}_#4}v7w{|PS4ezikiN&aHb@hC3!!OXH`U3$yikBxkx7#FH~Hxz*n zuDJv^PwSjJ4adYLuPy#}&@+d;Vl=E=GOGT(rL<4prU;PSjFd8kjAM*k?0g5if7AQq z@_6EPpw|{Iv5o;fJ4T;NmWXWgq>@YU%~|1F9RnbST=cA5aN0j0iE3c%G5mgv>&nKp za3-S&@n~OBi|N zBxsCzK=R!4NNNs@cE45Rlk-`fpFz~BB`h3N%~+5woH zSkNXu`zS6vLj>*?6U4f{(0)mf^3Gz6#ZnCAoSi^TWC+3fTDMX^drodC?DR00JpvKW z^Y%SA2V?e$YEd#zQHDEi(c|27oA2s4KzX0q~o!TVvx6*B`*gr=2XKv zgX$V^$`IHBU4&FhKyu6re7yIa@i3osw;$Hud3|YE4gvkX;B=(UbJXO7Ia1_?Ok>#R za#&!Kh9p|d0PqG-6Pm|jnkZM%#(uWYSP}UKzbr(7h zR1R3U0xtN=IC>H6^!VVkT6!u#<%wdI4i=J1xVFrQ2T@0HRN8{}#+bP1b{U-r>?u>w zzddOCkUO+M22QvraX*z)^fQ??esQtn3$KlWuM&%A^LgGx$LSBf7e7dzLszN}cTQ}4 zvj+eE_u$yG-L0f{QS0(nD8yj+(49++$1bd8xb#!z+~LL4S<9oYGAXKUoEe00^aW!3 z{C#!ZSEX+AM=b^)J$7fmKhHj#)Uzx8*taWXdl{em>$7c;NDdqjW#BAe~u%2Y&* z_eDd{dGDh6>YhsX(#CfJDO4q{<&A9&#tFMab(3h0j2)wuB(ep1A(JP=2#(Qx zH+yt+Tm4W?MXlV8Ms`rG%~8;uo?d5DnjZ^jR;kT{(1|jYA(Eg<_h2^gm;)#_>fsq^ z-#Z)G-L@(+;J?Bij_KTMScaYb5W5*_LY@_#z{{#DCKEtL`-8$v!Bo1gN9;P$?#Dlm zO*1@h-D#}rxgH)06~cS3&5Ppu{o4iA#-1cD&GCI;U=z2jJO;XBjv0jYxFfKkr1XeB z|3JX&6ctPJXTTs_xibiaIxzb=qB~LxMcxl?Mspx*{Ttrq^jm@p6GOIu(pB0F)F-!x zs?-@phZTai64jD67RZ#wBG0XvZ zoJGs=u>gFUydC^L2F_aIuEfqFm^-Fg@yaVTVTcs}#?T`}^^(zLTc zm}^xJ#KwZ>$c20gOs%SqjeJszcpU`09#8SC_*!%%XTd~^n(&)69QM`FFPwgUhB%^DM`b<`he$WSSo=zVT6bAQNTM!Q#o<&;6eGe z(ue**kniczgl(W6Yt6+Zgck5F-^fT4%tn>RBX%C)gg}a{hl$t3$dH>1fTe8j;{tz5 zxWxiCMge`cspGxhC%cKsoGvJY&-CI;1u#Bf-%my|75BO8r>3KBYh-lyF>Xx^ypuLl zAOO0@SF^I|V&Sn=1lY-Ahkb=0FY%_@Q@~C{UUGnOOk0vHg`@7~L%XW-HBbX@_f-oTSQE&+* z@DipcsS4?W4{@gcLc~Mc09A>=0qA{_cVcO|1z{C}^kGW4@ne0s6jnUukmhGn=#UDp zAW0X_obIuYtH)tQnSzh#tV_9jt*aFpekDO0M~FaX<$cr?k%Gjlmjd4l&Ee3*jwI4!U3eTW*b{Ll2*+o^2h z9(KfU}twU~mx_(j#*KpYZdXPAEXBdwKL$g&p!u)fuGR~YO9mPM~XdWsXEG6apx z;8)f=cH`hBXM^7X7)d7f0`{D+aNo&5js!sVbbdI+hV2DYT~1&mt_xpCycvXDAW_qW z^Xj+h&Pzt$_K23PPI91l5XE533N*diB_lRe=uFJjAZV)i@9h+uWJZvl6iJBV#TEYR zQV7Bcrrl47((V}0{H|AA(nb`Vs;A6_Y$R~ zz}1Tvd)?o!20%so*r}02zoh0GrSrxUqtR#4)oUd{ih zuvGzGUQN}p&7SXzQ52@l&B;4UN8s$kELjb@UrBN)Q*BRhDN%Lf{8*)o8ZXLf-*&UA z*70^8c*jDJQC5PHJyDT#Z72)6+=uoHo z`+F$^pJO8Fe9G|hi)LPO>gyh0rHP$NpIP4MN5F3B3F|BhvKKecuS#D;I|G2y?OtCHBq+vj9$l&wBzB*MW}4 z3QX9gJ^qcI=h)(>$%qYI)e^?6v+|r5jGzND{ZO=6?ftHM7;u(q89IY}3CdR03a7^$ zjlm5!RCE$v+JESTn1C&wWpVSg z6U#ImT(HHBNgk|qVtmn$40nF(J}WWN`&{a*b3FDV3=6V1zJEgAou3lFsug`cnJ|gf zY*UF1Bk8Iv2fFs?vQ!7YI|qS^!N-gDXDxLVak|G?TN?mX%neMN$#W@qriX@!F4w;2 zd4P=<)P0p>r^fP4rBB?x%ld(ZYwQ>hfd93wyoVqjcqdUg2JNVFiO`5Crv{WuRbkFp z#_vNxJqO%Q?s1Mu-rH)yrtQ=eRAO>kN?AZ8eSRAtBEY|J57PUAooiYmAOor|hc{n1 zV&mZ(e#j_-gV2pa;Wn(F~)_#!zN2&xpx{ z6zWGB=5oR7(zhwAG&?8g7zZ~6mE{4SAGBGImj|$z8~5R^n4K7yLE6}b4&~^Y<{NGkidk&^T6sMb=D0xG zaJis+|4^k9x`qK}Z?PjcL5(fy2K8=O#+2Ujac(egj@`?I9kIZj>>zXoK@fj`YBdGU zLSQmr;I$kOZJOpJWgygZQGNd>6kr39yq@=L2y$vAHAL{-3m}UJng=hMD_ijR?P}S{ zCcVy2f;zuM;P7dywtTu`0KnsZZ7)OM@tR&^7dno=Rs+8=_~K_DZu(d?jV*yz3z=MZ zgz>bO6HWnDMsO)R_NZ+q3op}bD8*4t&fobAa5u)3Qy~b77PoF7?puLDXGKCI{(Vqu z#Ao;G-Hh>IYF!j|jL$)N`|pxA-FXk_$Mu>0u9d@mLWebZ;lIO!FQ*=DtG)LdK&t8_ zmcX9aV_=bhjRlPQJEt@{f(tM5){adm<%WWR_Fh3jb*2m$jQl!|eWXq(L$Qnz5TllomSzji8U9Sl9|i~0@dvZW7o~hb@2%w)zF1)RIdw#cDT+GZ64JVT=1uC>G&*l4iNh#hm5Gko>AtB!fs$$jO- z9XP^7#R9OwWmng>n=#@B)Qu{l^)||sjG>!FhPhpyP@bj{ctCBzk@%Z>+sgH4g47=V zxOyemQ{We6yT(has4w4@KWh{Yx8^4%Ul~SW16ld;HpiXIwO!UV_sLzu-5cROZ)m`r zSQK$8rER0*Ur_nkumRLQ4Kt$!-VMq=kU?hZ6*n}zF<~n9ZYw+5BcrEIi3(P%c~WFA zHYju!URTKU^_dwY=u07p1$?&5{DH|nF#~nUWf?3HGpKzfhX>XA*@1kErfpBBfq_RV zMj5VG0$CbLzCl_5_5>#0qPojS>2d3U#}ZaC%5xvS(8=Ec4l9S8J56GQ+dpFv=@sO1 z^T|;@;H2mr`&A@r$K(IE275HeE1H-(Z-N#LBZ;a{&9g2&V}fHHLP94YK-58_djAm@ zm&(76Dl#agW{*gEVtmGH-6Ep@gR;bt9NSM9+e$4H6eWrU6j_Bh_4`)(NPjz=zNgt~ z5hLs8h>>X<>b0yl!t}40#Gdg})aBe)a_P}!KV%j9LD+^u^hCknM@P52;;7fH#Ias# zfIK9=?81XgSuFp>EjR^SfkKj~i3ZH5_Fn$XfHrX$A3=4O?O>7R(zOf`DMebN1{Y^U zqE~F@T4{3K;HHln3;0WEhUELle(k5X17|tgqeX2Orhj}LlH9hu&Y~>6PkFnT)6PwJ z-ZJ3ki6~m*D=dkiro7a|h-%+t)NdJ{s6$zLzd4h zlwR zC9e~f;E=rR+PBe^Mn;^V?#t|RCD%2Yy@+L2lM$m!03SfLFv~U3{h2*c9>Bms2Hwh| zO?kir7)#3jkJ9V@^kDRaXE`^KN;m_G;(=f%s7!*>t3uq?zBF$Z77wjW?ED0>{QssQ zu<(GMeYo|JXvw5^fEi)o zT8aiaa=DNC-*TO`u^xv?*vH8E_)=eqM^#t-?<3v6Ui$T|+fqt#6?zBUv4x9iT!2c9 znC9}+(m~-6kQ82h0n0$oxt%jkz-;kRUmRi zjN*;9GuVxRV4NbZx-;2RdPwRo<46Xoxl;YB8yQeUdBa%B-K)aT}Oo#@T)taA`aRJ_#~4*q*Qy7w+Q5Z71pu6GXlw7=cW z$mLn%UI<`}~m4<&nFPfFbKeL0iI3oCpykans5(GiO zpZEktNf6nAe3{4t>)Z`wU^=kjuWnpo6_ER(kMdj7`+s~9jsbd( zr2(J$>W9r}o1&el3V*EIIXm114Wq9jbC;(bEQpf~l)ENOHMlHSpYR$q5`4yXg-hI0 zCk}7UwtMQEvnEa(!RaA%5(CEMCNVfjZWbo%_C?2WX7Bu3Q18MYSloOmoH;AGxl6E4 zWmO^=F)JN%TW34E+_rSM4Kzi|h~f4>BG>Ob{z438MXuJqcPv`hW*piPDHFpxzd@Pw z+#arPLbb@Dm90xj6iPVmh^(chgib$!|Le~)#lp=`qSA(`p7%VO*!Ov^Xm4a0`H2CI zzkayYgB^(W7?s#!ke4#Ov98WEMddB>iVUe8R zL!juv*$H6~kvkNJS4g^9xGzl|g(Qz;b7ggl3^9u|>Ub^rFpJE)fswk+a+f$-5>LkX za9s&!MpMdZxapkft`u}^!qCv!d9{x;Gy5ryH2o+{F`G~IzXXK<=0{P_z(NEI@4jt= z$%~cK0U1Ocrnc+T2M}thQ3^M*VtMrO;V$wS7%K`-4-YA6Rm6Y#~VQdku#G zKWN1v?tw8lCso2w!fIysn4J3ZM{2}OVZ7xa4^?62i0*cT!Lg^4Ot3+vXl@lVIn?av(AWJvk_j7->Hd)kexsP@oT6(iM_1b>Ksyj)MOl5jZ~H~@=S zor$58CsLqU%*aUE?KHm-j+z_)u+SRN%(e1BC*ow!p|7c)b2OBJXLbsskTJ=T@o6`>Rdj& zw&dTG3_sC*@6KJ4LaqM26GU|Hu7$sNj^tPq{@C?hY7!C^9U-ELBujaDBJyi202*)< zUGteL%L%^f!A{?moN-K=J%Ces8KFK@3zzAp`dua`b2fAHq`KGe+G7lGe525WY^x_A2AQ^EQl)0#!DdP4a z(cO%aoE~%Q_!%KHwt*38KpBOLwgm#8u}Klx$5atUB-Cu@qKbG_u9FAewpxepZy1Ch zEN;3y{!XIW5Eku8be-Q4j%p!-`zkH^&uRhuVgeJHmKMH@CjX?Nos$IE9MHI&UE&F; zVg2`NJ$jrz<+fHGsU)*c046bN=TQzepY#%L;61EY!EFgpCy+_0pMH{qRLBspoG0EF z8N1+;j*WD}McgEZ`h6y#1kjZ&JwV1w1O0%B7xlC|dsHrUCTBF1j5m@j>Qbs{IXCct zmPa@;{$7cPpU;+etm+iY_8!0{UvCdjU3aQ27%n#@5tx)nE8u~@vWXms&CzWoc9ubw zX@9qfqnz*(`)4C5J^o6&s^2ufI$0bm(1@!f-W9Pv)7LYpAi>-FDlsf>vug>&#_I-wtdPMcp53C zTB5e{AMIV4AKp!tAb1PQ_9@^k4;VV2T~06vEWPQFkA3yh!7(qvj9=8)(Te{4BP`@v zuMYIQqmLXaafRQs)iKAYVCD+6=77`Aas!f|8ZJemu|WAZCZD0G4Ks%nMC-YPE)kL# zlmkC*wDL=$njQSKz>V}#@qXOEp`C1V2u}qNsw>1pk-8DMKHzPNS;(0xS!Q?3GM?bZ*eNguSv~2soDq+4?ZthJ+58+x|N2sOFj%Pr7%b}Vm0!Rm z`IJuR{^)~(v%O_!MBDzag9f&quA3_$1J)P2)07AB)sXiiUD%>|=YKkIBzbkab2OV} z)oD9D-yjB~W-9cDbWYU227rbxC%8&08Cmj*tWW_p`F9Y#dm2S$htUcNlBo*}YaJ{D9JW>ZKX4!)XwDv!-BT`xx>LpTK&e7d&b$}PYc7hN`}%7SkP0I? zxCQKIb@vuM#R##V=RG+(z`>c}rU4lQDM^rwwG)!q+>ua4*rJl|h+|;m*d;=X-bT(n zeOOBuA2#q^xYykIb~qCcI`=j_tvR@i7!9 zr}3ywnXkcDiUHlvimSpDZ*C9Hs3^MMRdqEH+&pW{%&n}=?4SSS%b4Q$-!cQC7e=iw zp5N5|6{r)@fOf$v%GH8&PiQ;DUSAmgEqv|P*&2yBsLzwp9sYfeLb^$x{dH3 zSCIvb5KvEx53t?V9GiE->w8Qk)uRtuCsfCCJ-EV#xH%UN_6P$y5qo#o(BBc@!aCMz zK`m>rFx}|w)%JY)pfEFd{M!4!J;068_qY8taC~r2cAr!>bI*R!_DJ`s{UrZCck!pm zMPt}8=fk;uDbt%MZMHizwv9}c&IwbXxAJ`kcU`0N>ZgjF=A#4vdfKBU-nDe9kT#z8 zDehjv@u!0?TTYJDToHnCyMCCxi_iLW8|^?(1@Cdi)`cT~PA`0&telHR1J}ajY8Qh} z5b!Ibe5R;Z_!RL#h$|dMNJR*!1V>^M+n#-I6GkO`jUa9dmiNv&W zLUYFPVO|>ni2|N}qX=yDq@B`^Z0*03|F3S4^W-;&knvY0i&0~75)=MHqlK^bRP~EN z3p~Zh{W0CHZ<&ItZ@~JfyKw>FF;+jm^BVGrjM_V56q+VCS`68-|0Az)>HTG7n;)@! z#a^~$#kfXA9V#mQ6$2_pNy9KK*$I{`U?&&8RiOq_jQHts0bdee{q1JF!E)8}o;`3l&@(NA`&YrjXK;-^ZD;3RT$}D_X-bY6Vn#aHwm(He${hNclMd=}y`ZxjK zlgZwQ$ul*L)7Et%*Rb%*is-oU+j8|YwI~)BXmbdEV!vw)@tyWXj;pOBLd?FDe&zoj{+wBDE+^nbj|3F{!_o_?WB?|w z9)XLg?x^|?E-0w0h}-*+PLQcL=Z)95Y;!Me-KhL4&8=`^@wB|xZ>pa_ZPby~ELwDm znx#vz&(R>S(6R9*eky83YLs5T1h))1?7wYeMG1bX7Xm_na2&Qm(mE>@k>nY@YWB0x z$-KCduWuu@&Q8n{mi|5leFSBR3N^bw#2r7vI+DKISaQ9Ic{X{1$Y)i?T1IrRRs=NK zCI2k#*Mji~CDOt$*79zTCF_evy@i*x5?|j>eh0d|mX#RB)fdlYjwPh^W<9vtwIis2 zArP3?Kg8u-Sv!Bl+Jq}c*O0#V7=6dKar|Uh>P#m>!qeET>*d)67PgXJ(9ApVOk|hn zxhj~>=IXvm${!&_G3|S_=cC~`GU9Y^2eiO%HOzhq0gsvhT)`CofJ7?#>sL6GzVQM7 z3gjNzz&W3|WWVKPrIixzPk!KFa_SBc^m_K-umf|HGi@J^@%rm9VYlm=&Aew!blki-Y3UjXPQ--q|1l&o&~r;LJ(a?LA{UzOG$gQ@o`W*ty^(%6 zAdpEucQ&*b)7^YF+f3whcBNUQ+wgi8gF?_sZ1`&DTb1UoB$scG7jC?<7Lv?A{z5H% z{5vOhk6d)&js220B?&_%8$m7dzdyw=`Sq*YGUd@s_m&>*+Yi5<-pJ3FD{s6T5>34` zTvh%le)Yn0sbMKJdk8Lx!6wRN|M*_L_^CgNFKMY&)f){V{@FeB(FR|QfR!mG=4%6H z8KJ^y+9k)_!I0>cRQfteuvHEktjd8H@L|S@81qtqMd%Y)w6= z^@>H$N&;|&uMH89{o9yBh@D-E{fYz1$FLvGZS<2<8w^9L8c!Urt36w? zLOE`ocx`=&tQ0Xz{2ksdGYi0sm;D?t@E*Qp41#EFA?QS>9|nT2I`rW@s~?h@lD~HY zYFm|#t^gZPrOLdI2w_bq3LYcvb_W}(jgJ2*>tybp|Jx@~(l6t3RlWrQJv?U-tqAAi zd`QM@TTIa_Z-?lglP)t9k<+zg<1qtWv)f#-Q&?bEf~n3QqCn}GJbQ%6X~SzGBJE8dMP`L_Ah_dO!1;VVan+P)$W zWvQd{9Zr~bJn#lLo|35tE z8yS;;R!O%5K(asa|IcbQxk4?kswlYymz^}xc>0e!lknq^c0Ad!6faJZ!hm9jg2&KU zwblLz=i!vq;c(DySk<&uTzv~5MZ&CkA%!Sqx3jPgo2=kH2J8{*1+0mF9iZfh-Z3%> zdo<)s^0}bWjdIIRhxs6@y=Q_%v_cEvI>}PWrX3mgotyKcFO{Y~2ExxX!2VL6Cf#S2 z(jx1mU^9FZ>Ol})hvC%RCXj4AV4?4}kgyOVtTImY;dxBQgh&FX^*UQ+kKw0T9z}rl zMm1xqoHpvk#MyTeh`^&@afRoHkXH_-m;`)$F8Qg5J|jr(eNJGAAP;G-`>C*3H4Npe z5WH*`y@gb8^uq#Xp|mq3Y_l<`O@L-&p5&mqOT)ux;dw@oj-3Jl7Q09hvSgqa;C-fR z2YEb(Bmt+;s`9wiDaQt}#pd?>1oqpmb__*rn-SibE^wd$N0D{qd@3w=NT<~{_a#vn ztZVb|b2=b&Vry;u46+!HsuG;kc(_zlSq1P!EBDh(3fg+&RrMmi!+ zi5Afu>)2pC&5&k?^V#srXN6IxCUEg`sA1wAY6gakKFPTE{f+sT211lA=<*>veeIf!Z_N}yEER^QbH z@6RK1jt&zqL|QsWh9_;YJu5%JXAcaS1Q5Si?Ka6kLh2_Y>9K9?Qq02mY{ICo48?bW17-bML_Caf7Hfhd7>%}k5%O*ryyP-4nb05^( zty=oXel{7U)A*K{e#0+$^?Sc+Xl9GG+9H!~Oe`2&qf_d+?GFMsuh$RKGEIv~t8`t8 zWLiiExbZ+HrKu11@59TCUR+c7SE2)uP#N>^O3xk)bd80s5TRudEtKrE&Ox6QF0UqT zVaR=9mkt3UvI&I&nU_`t_)rhhXC{5sYSRw6D31icnTJ@-AmpodRK-mWUQVSJ)r0o< z{XF)QNA05+{)}I)z`Tb*AXy}(>tG}-m-3u8;l#6?IPvlW;=|{vv|lvwR)t>rO@cQ=h$U*zGKTu?g4}D&!$N6kW7=~g*gnd z+hc|4I8SM{5ZfpgVXyPWuZm_T#aK1iC6a(Hh#TX5%um3&f8*1Z9CJtJx9Lmjr)L;~ zb@37dvjl0d{2T;QN0x}8WN#UzLue{?N^fj&R!$#hYs`3nee&C5MS*YaA+gZPgH|=_ zT9w^|7VJkP*3xt4AGnNm)undWT7TqLl&(o|SN{%c4J*7($7%m<7d*1quQmsJ6r|)p z#}H!2AtX2Imn(;Z0IYWD*Dx6@yj;)~)ZS^N3}&x+HZuK$%5yKOeY9Jg7d^Knpi7Vx zdArnJrtQu^!Grrs`k>&UvraSBBkZYmMhi+ShrEyMyoOTn-%rtp~bx+P^XhkVd`Y?^+&dk2^lYg>RP9 z*y3wh4!G$=@hrY^sg+ut`EGcczgfuSs}BC~?J zS;4E6J{Wu!?6I(Y#RaT}b)&xUOl8}?W1b~u@Ax!k9cY&x%Spp}QlC7a$rZAC zTp@Z~?^3u~D0E{eg1q}9Z@Qj&Jbw(oYas1t40L+lo-%ASv)rZi_Nxuvk7i!1ULhh* zqYEnDe!X)Lkbu>LAoY{K?E{^bNpC_&UVLQ|69enegT{m;s&D2HN|hNufmd@DSv6e? zLK(5HmN5T*lTj`U^oNCO%efsFMW6MHq(U0OkhB&KWP$=-iLo&;D`8T~4hRBTAGdPJ zn+Y9DAyXF7ex=URMkzWWR8op^NrOfpsFQG^m9YpK)I5(wg1OILkH9j z$T&{S-#RX#F`#3$IZfsq9wsi!AjC;sj#5~T5z|^I^_?^N0N+ogOFEmOumU3*|8+m^ zuh&mU_a7xeeP+x9W1$qdG2$`j0!Wef8)C;{X>s5F=lQi;A$`3U(pOR0Iiy@aV_CwfnLzrNhf~+?Fh@odY$Y??=fa?b1jRXyGk)7TZX-FaI&S#HJK}G@-Bp?e;8UR&8-{1I zM{pWj(3zYP8xazeeaAPgc`lLlBHyykjOfqfU1DP|fvTXE@JS<;CoMWIMt>gDNG1H` zv20vh664ctxUU3aCC6hkfp;KGJfbTo4HqhN2;8Ql`e4&LUqY^8rXwTNP*)o=9%j?|m@7p&-JI)o=NMScc7*XXWe|I`w;zDd9T z#wGhmGdNA|z_fMO*WqSykwC*mTY2-{ya9(@ZL`V%x81>^UE4aW=B(-DVkArQc78;3 zm5y3fuWMUio^xn>m#2hg{L$vwBU5kRt$oI$hNx{;$r?H~cmL>*M&8|HXGLOfv0*{d zQ^ouQu3MCOSw7c`@6vtaULemOXO&8x`CddAE)7qZmtSfI%~ffwUM;vmeGU(4p57iV z8>(C{@*4#CcLr^9NQU9}eqXYq6N-F;6lA^+)O@6SBWK-~hJha2wt8CsMPL_$-pDBkT>d5YA~9G7Q#I*$zFV9#qRz zzSH)U(ZsHYPSGU%>0N10EgNBx)gfWrKQ|M-wcOGr=NPjhyP}11w4M1L5LK^d|BmOL zV4X&7X=E?TXot7+ov&LHoF6sCQt+`N#NqwdN7~LsN-nX?Z1mMT*~T+|Ukyu1ve=j; zOV%SeeVm?^M>80od_IKzwM31gfs7r!_;l+7z#ODmiUWs*4LdJar$~jPcMD_Z^j&V< zebc@9{`1_AmO34E!!gVVsK!=Q6|?x92<4t?%Q6qyd82M}zEm!micbv#+#Ojrd{yXY z{vHFrm?OmzA8`ViAAPNrHx5iq`2OB!=;L=|5eph3$d9}7cWHr}WIx=)HlyosOhp3# zGuO~1cQ=!%0Y7lMm>H=N9~9$6is*jhqRQRg0G4yRK}jUd`8%}CBwK9P1*E2}W2q^w z^r)V8XNW`D-+^mJig+9U|9x9#=^!FG7Lt@9|@2Rrad}SCf0J0Sx&=C(+KeG8oD80V zsYA*15Jbg9YIfhc6umkhaWVR9?UgP{v}>W<`Dw?DPACv;$ztUP!n#W%-EcDA*6nbD zWoU@)f*C~FoQsRXhS9ALH!E0rg$PttSLlQ=M(OlNEZ5w4U$;r-mj`Hj#=+aKVxeSl zU2xY}Im6{=LI;=X;2m)=^mx?nKL?o<1^O^9M~0D@QixJLvfGMI!LD<7-LY8DU-ep=4hq*<>#bW zwnqD&Gg2B-uLYOPjbjtwBpP*ljryf@EgKc0=t%n|=2v_>y&o|Y_67_#GougI2ZVVqv_&yEE0v9Mjbg@m(nQWJDkF6o3=KlWEJ2lP z6U7cosZ>XL1G``hZ?TPY1_^%XDi*ugGzyvw9`yjiU!cf-a?3B|Eih?7o+mBQ(nGpm z%C@dY&Mg(0yzTT$N?Xf@*a*jtt2W6@)#0GK$1=tFqc{bxqbk~tfG{*Wmt19~QIh&R>1%nBxkX3rsR5HLX(A`b(L_qJ2IX`~CFMRxphWa$ zs5`ijqw=PTEUtM5Uz2$Fjw;g+U~)!nXRJ_%=AOf0=WDxxcDKr?#64ke{8GHkm;L%S zcLQ56iu>cJTreMgZ9Hr%wp42j8>%At-Pbqj`Gy(2STB4ujg-!0N|=6sxGOTZBF@`U z5NlW&0Y7@*VMd&xC_D6ZE5#8;L?DFCD_}Lv0UjHuv16r5E7=GE;|T6fC}lOt_I& z7Fdr+^nVWLo+D%(jEd$vbX%XYus!6q#r{N5k3JREW?)}vTPVDmwRQcoam{zuW~YmWGx|Rxi;Jb#4`=P_!frkHo~(@o z_@;ZDY(tzlnx?A``m}C-<(FMbMcNfXXn z=hZjUi%r)dWWqNw1hu!0R6oNlv26(@%~NbLc3&ML{Nkx?M^@jnDB$sBj!Br0p=hXC z9{g~9G6%R7#56yfs9yPQGtmj9UkRgL0T|r=0M4;N1EFZpm1@P(r&ua&HzQ$l{U8#` zN~95d8?|wm2?+hFk_jeihX>=RPqW|c8BB1@2Je}(64cv>7EotKb&{%=?#bY|%x8p3 z#sE!Df_c>Pu|8hH$mvH4O8qN!EIc+*q(dOP2yknNbRAGx$O&)5R@y&)o?R(0!nbiU z3#2>NmMnS{F&Q8&vY+KCO4YC*-F`hTA37sI`gCeo_U=u@Xp=LuAp2XtQ^mfKcZUj< zx|LY$q@RhtkC^00*zhxcAt{!*ao<*D%tAx|JnO$7bkO=YpiR;%)?N_X(0?DrkuUqO z@eQ)Rk_ddJ&Uk1*NdoXlr?v^u{5mtCV_G<;AlX-Cs}1lJdQW zxwvFqYcKxts8t_&aK58akUB5GIo9rb&#pj_(nKMe`T^}$*9f05Ac-31Fo$IfC8784 z!}huGIyNy*br1X^7awxLzILmLFP*#nBObRBIEF7Em~$FjXL7s>L%}A4>s-^0rl>B9 zM0>UW=;gv_7@3YJ_Ab_D zuJI({`4CIc@Q=p_X@l@~M-`GQ_kQ^ctE2scu3eC{O-bgDDi;;@S1Tr)6r}Kl1X=jZ2hZBmY_;8dG z)-6vG3XI#hUD@B=OErzbznl^~WvM>u)5cspsv5nnr{s92RpP0sA>i@qnEzUIi#+m+ zx5SnR-$uzTeEfjvS4K(m&4}#_k7(-GuBzwb9_{euaoYlfQ!+14ke$8?z zji3>kMD4#kp7z`e)o^Y482s-i(H?h>s?7Min1wj3!23p_e{gVQGsOQ7Rc{>?b@zpR z-wHV7&_fOgNH?N1NF&l9osuHmAR!FhC82Z(Do83FLzgfEDAL_6-Ous&JnLQS{by!i zEtpvIJ?HGbuj_NgDqBeRMM8P1G0>4=5cy?*+{hz-&DEn&m`RRCnt0hg$%&HP#yPZ; z;8*)p>5>AE1jVE@HX+9s473OjRra!hU!o>_#tIU@0f1T@eNZmZV2hh!ekxo|BO>>Z7E>64 z>x6+*^KGeJmQX{jI{#-ig1Q}hNID)X{jn3GAleg1?p2TK(&0K{>C$=JVtv5{v|P6u z;B|x_lEGCb7t59aWdoeEK#2?a!s-x(lvCUBDcH^`z_6?vBb5Vzqu@BK<*~p9rTw|s zGng-YYQ>di7Ogmi#K&~xk*TW|sMT+#BZ&S{poa81U*CWB-|P`@pKt*pV#i$G`43j^ z^(+XpP95Cj@agar10u7S;O#Vvj(8csxs{vSWIxuA()YF``7syY@$D^X)jkrF)`v2_ zGoizE#WiE$ppcK&U)Xefx1J{Ky&h?juYX$PIm+Ergqmjxn(KQNdZ6)glEEBldd@o& z@gU~(#=YWa`W>0q!fD}ih*Mdls7GD#)`wd==d=BYo4=)FBD$%h5_6t+5$(*(#3MZoDBGpcIYS*CA1=4X1y6>tGVi5o4SSv+h8ji*z5Mxptj;axU-=VG z!tM8eJlnca9DLkDrQz zoDatGa6nvEM3Btw_X^Y1*-OzU+XL(Hn2P38&GeqFkLlBQCqmZ2W_O=G`ps_B$%D=P#!~sbT(ib`bhun^!UB zn4EUj;;u7F-_5mJ*`i8K&a+hYMk`6Qe4{A|ZWa0@{-6@?cAU_*K4{Kr5msy12+e*m zhz|NGz#yq2CP33oky5#S>qBi6OZJ+3ubD1nx*~kxQzA+D@(1PD`Mv*0H0C}-$j?0A zMlCB*ApbGAm%l(Q*4A$4s9S@YG@(5|!m?^tS9pM@H}61)#D*>>x=Vs8qw)!jr4Y=# ztMP)e+3(&1VhlZAoijXC&@Y)|g9b{IG;aBtFd)HbXXl<%@Jd9(ca#W&eYa6QbN2q# zd1UZ3F8LAi7(TxM9vk@c?x{C|PQG0uga6I=^>5N#`o>HB${43D@k@b&^>>wb<@9q2 zLM}8S*`1WQ&XW2;>M&`CYvHj*An*@F6b0ckZeksw(8W-Yj2ke&2g`(Fe>xd89=^eIHB(+2A?TOZ7 zNk1OrZu1cKT&!oiuEs!#(C{3;B)ErQ1mH#*?}@PLX?pRkZyh)1NZ8sgJIIP_QXYkh zV(mpF!&eAf&U>wz=O0ASd9;seV-Ph3iu+NhDvV`jmS&0*(le9mN_%O@fVR)GY25i# z2jui1)ZX;NXMjElkWpm#_GKq;t>OF5!_Wvh7E%Ij+k7Jh>hN{ehf4j>kPx5PMQlQF zJN%eY+=|p7ry4;a90*~nsHeHr^fQ*zu5^=lfx{(fxX=Q9g0H&H~qiigE zk->?iQRPGEaQF_2Uw|6USbz$A^=K+CLFVIuQmxG-1DA3nG&eq`i@3l*DoTS2iZo-%x2REP$bkzv*$$Z|~F)zn7_&MZT z3!-S@AoG%#YrbX$Oc!*HBnl?v47^GY4Y2(ovFm4`AU0WFT>K(t7H}0jbL((k+mY}< zy}!)J`{4YXx&m+%tE=m$YR@{WFp24Ja>n*SK`H3^=^If2 zBD3)or6cLd#jEl{Q8uLXm=#$Jl=xhjO-mWd=qVpd1_zi&ZM{DQh@{3>_eo=(8UjCo zEbWw{>t(bLXFynXV~3g<*v5!WQdinNRIU1hPg9CF^2T0zkh61=0o!onr{{~(iXIzj zY0-Nsez;TWYO-Ts#-#FrAP8+Swh2N9^5V0VGD0gt+>K@r3Ytkwkq(8b9l!h#{gMSF1kyc>?n2;zH6W8D$Pa20`npX0yKCl&o zQIqkK^;CgQDIQQLqBi=a71A?FV{%AiW8wC?YQh5oP_@dtU@x^~FmMMpIwP3K!LLE}ntdfvIvwQd^pCo2g_ zPF~rAG%O{N#ITUDO?`I>*cDyq^7VQl$R2T-@sq&!CaW_g=-t(0+Q?aSw4vM!k{}~& zrBV)ZCi*=e*;!^;!|$=I1tv)Z-*HbnD_nSI9!0b>a@fNDoKx(Et1t458`Tm@c+WgL zScG6tdS3YT<#3w3q(4!7+vHj}Zzg^*TmSj2mFn#8+`P)A)TzYtOEWy4VVR9RYde!h zI6>MD#cOfUoq^G1JhS1~RN)e_($sWAU2zH4OFM!vGCZ97Y1CO8qG^O=X*17Ohu!Oo zb4ru{n9Dd~vOY4jH&3lxV0oIM^W1@t*fENOdyBs@8!wGbdY8+`0&`-^LuxL(jjWqpcJ=$bI_(vE`J7@loI~U9 zbnCOXuSYQJ){$Iq^@i4uxm^OBeWdG%QHSqa=;zlYc=!?y!|j~|SY8_~pNf3vn-JGNHRHs7#)G{HU2{I4kX+PEz<+w;-UUq_p9!*9CjDdz8%rFc5$uM=p+ue7=7 zt}Vo!+UOwJS~5lHKIDe%b72p(eOzAs_$uee*98&l#!}40PHRpbMU1k=*?0Qv@IANsQw}xoyI>67~?hC(k*HDu~$7NL4>`l|^aG zUq?@uiRkb*rSmRVZjVymaXqry>K&O5R!=YMO+0y#SjkHxZ_8>qxT{3#PZqtb9^!g= zK}p>d_vvt)%{adbpra=2xR4vJ2ud+jE4zeGG@+Lmjl1!ZgYAoHKZh<$nzV=C0-jLT znL-Py{)=lf#qUKzEUPxxe7Kr~aj;lZl3WD|vHGqLB&WjU$(^inzt4wv9e4<(9}Q;peoB_w4r3E3U78ZBW7r6vL>bnUI)d5akXd9t=Q^YPAX(B zw2>%Rl9#M(Np>O~$#j%y)TuM(i>Y+cj-SRP7o`d@B|Y|qEODr&1`95X`Mi!(9ukGDIUsifeRYFpRr@a~~ zgLT|J;?>0O)va1{vJT+d!VHT}CaM|5CG&?AijZ|#I#_V^;4U&q1M!Kh z4`p#^M3S;kWl;nWhJ4I^mmlu|)AC8&$k{jA7>PWtPtoyBu zijMAf{YwluLbN*Fyu!PCHI#adW*T)*_m0hQ`&|*V5p+S&_HGIB^GCEl@vP~JePCcS zV5hA<5Sk1oXaU2Z*hnbdYc7uTf8kKoof@t5>J)22eb7RYQ0*dHYCOG31_cFA?3m(s zgo*{)&%aE`Vv$2kj=8|HwrK~&E{Rq)L%PUCe2c2hmAfJz)o0VvcGUL(+GLgWW1U(x zRSHeGa0sGSn_x0n)HTFA8BPYuWpBRi;(y~*9jhHGsG6DUsR7=WT+j$`ieoSf`qx!s z5U5~-v1%F>a14-|^!Dlq_EWvB?7|R}AbJdWjms^Tfe`6!-~U%8h=CuH;0}|>Sdd?c z9}DKDLzidZ5ZMRi1@)(_BPTenT0zvosPW~b^!E!Y0Pd@nm5)MprP5~Yf?${zUguc@ zN2L}t=3_gF;Y3IBIK`ASJ+E2w;ES~~| zI`^Z!I>9zRZcvssj849NiK(;@v@U*GK{)Cj zL4ImRv2%bSbnpNtNSlg#K?C^J`u0jtYLHY+N~=;PFZ!=+eWN4Rd*5_5!x63@6Z*jK zo~HJ%Zlwe->U1o1cmDB^DP12ff?tp6@P1I&$r}pIfMF;%TN}>z|91Zsukg-#Fn=W$ zy=_@$d2mhUdNn@}ajMWJ|F>x(UwXMLI?gTMKcaLlJzcY%kO_GDa~}uq%LE|g7S{(< zuo=_8SiF>2`}=RN4BL3vTBu%t+~TyW-F!N6S9mwNJT1i|Cc%8$G06VL^!oL{xAh=O z-{rRE>$t{8y7SF5JS|yci|r`|7ZbOY8-1N^1J}G}cj@@ew^A(2{q%({cZAEX+n%L~ zxS@9K9l?pz9MRBuAXYzT0C@y$bkdfgRcx&p;hILuDEkW=L4CQK<%_pi^Z%%%t03>p z!|nUgszk{TyO-5aiNP2A{<8cmdu-1xks>m3JS`*%yWT8Ie^Q93Ipex^(`8-vPae($ zbdTOE$ym@Kr45jd=aIf~*%vCP`9w3`I;TjCL{KYt0aI=mu6T%QA@5 zCDsS^N`n40ZD)CD;im^1Ey<{0Ssc{H^hEieO*}?i^48-Lc@}~z$@ar7rlRCoW7ow-b*z?`zyj% zRvLH~^LY&YgoyydJsDUw1f*b=cp};IA??bqlRmyfbBVHXDELJ7x^Oni(uNPkcA9gG z5n+^cTegq;9Osfm1a7)AHp=GeFf!A%q*!}Im_CP2GVs7mUWsIfCBq8}(fk~sb`@!q z6`WFwl%avq^|*&dU~_f=T}~n~oj7ZW?yAg2XTGxI4g4N5ZFG`xSRDB9|Mr#Hf=>a0;!kXm3q?bB zzqxJJ+Q#AHi)KNyCM_<<3=_Ns(=oAgC04xrrz(`v}Mo_;J)`beh8UJFgOQfF!w3;EYi9OHoWu!nnEtei7Z_;R6d6!6YA6vbD zF|vb*W-eyunIo3YzN#0SZnOHN1 zL_hRc2zyB2|F_l-PYhmscp0+&wiEBuj(N+Ou}eUL*xWQ-Mz4?TcOj`J*mc9Bp?xaO zsr$czfCk}WRwiTqx!SRJ`mLlThyOmmaiDVXw}t)L6ubIM0qTmT@NiDG0JVvO1v4_( zEzPII{a`8WbmZ1Fjnf+^?&niJb>|fcEvDDJdTnY~6Jiyt3&YzQAVV{phF8{g`KxQ1 z)k7k!&S@~3F-Cbh)qmH^NGSKYtHv0_i}fudZ31UJb9C^21xW;bLhsPBJIZxc^jzC4 z1&64h^Nd%_|eoHg9RJd4IlU3uXG?#a5M}px-rP`W>JmohtP+Q3Okd1 z8_Ni0ew`U2qZpoI8A}L&Ee&#(lxvK?9;f>>7x`K^o2?$gX6gNjLX+`;yUUqHl9N-R zC4`_f`vwF(zN4KQ);ycs-mE{VZJ}$Oq*+iu-OV}=fmRH4@?{FHU<7`bHJ2=z75bDM8rcV}#3N{jg_3+V>9LGsUR zGTY6F`ASlUO`;p!b@MEKYt{A{#r{5my{!pEOm{29$(6Tqq zDdW4hU^utxv+1%-Y?Tr6YJ|Enas-%_HlVl9sqBP(StPTc60`_t^M83rw;bJSG@0+M zEH3Gu1aEAS_OjFC7Q-I;UT!~`|3N5!qA$~oxNS%Dz9%I9ZX*A+?;Xj?eEDlf^)xbr z=3TKjSeYZh^7RO#lSr7&I1=g^w(`(j$(4uwlVnK+C~5JAIX3jer$PglNV%Fk zHg*xBpJ5wBaWDq2OI#2}oYlc79xn<#w0j&JRX9Q!16)lMpU3ooW>Ar}KvpH02rgQ+ zlD9GR8i3S+=O%o`90w|QrqgYOp=N+A8uRk22D31=oLF`3l7i<0_~Xw%aEsc{SR6g? z`%lpqKsff!GvN|1tQO+)WLEiVwv0tnfy7#6NV>esK`>jA6risXD;Y40ZT-NpoL$U6 zWEpdQ`d^X=N{M)h(h*=V5|a_1-Uss(LYvl-K7H>hJpvA*Mt3vKS1$CFf2_$NRIHz8 z`adh`C<5}YNF~~(L4okykibY|ieTR8hJgElHDMnIRkH}h#jm|Y_$2m$1Fnh93w#z6 zGUsI4Ak<7 zAK-jXTeQ$_T$|Ucxgo0FAPqcH)Fve?equ`e#E9~=^v)@E67yY-yfr`0 z%DC87ntzR4LN$1hUsjle=l#SBTj4v};36|qLZ(e?3Lgq$f{@HTJamS`685<=T9!{ zdv0W~s}dCNvx=r|uuX-r3cE<`JFK^Ax3Bin)In#9)wSt{qx9;=S;k4?Y7A5S`XhFh zA6>Agkam(U2Lycuvm8GaIG(Je%hq-7E-7w8Sxrigo4H}%i%ID4M!TMKiL0IUb_K$@ zrF>lE3W_la`9V}^7Ugak8hx-cSNSB?H zvcY^kep1aM3cuNL?{TxnhQ6>M1tn+u$?((0A^mYd3UMqQ=WMe@O$A5F33kqyY3%3~!36Q=l8L4O+ z>GG!jMd~$xi(zjRbYd;|kosHWaY@hiH@I`<_4xH=amk0zFHBF$qw79dvo_VTNs+_7 z?B-iHOMYJ;O|7pA-w43#%&?aCzCPUIj4uo{ECr-rS8`NAi1#L#Gvx~mc@3XJK`Uy@ zxC;WjNL>&kodXsLzE9^1QNj~?ULR?Adl!ChqwA0S9-T}es*X$rqleYy`%b&I#LPYmOP6+2xHFP<6a^Y8rPy^K*J+)V^q1-y$`U|4(~m~^U7oM z;u`%p(abjvK4u!Nz3!-2XL;q?e>hR2CG^ZOY_!Cs#{F-aL`~@O(%= zZeGodsZ_+0{Msfxs+7RyDBmHAB{c z8ZQX#c@9@npu&emE$so4TIri2s-$(IB6aM6=iNs|n2J922TR(tz9_m?3O~!WJ$}j- zacHj1uuQf`8p+>9*@Ko`A{sJ7zlBuT=jM@B5!@P+Z_n;r)UJJanDxv{s`A|8oJs)T z1bDVa)E_H|NvJ#CX7DDRQoU)NE*;yraD~y6?9k)=em?xHu)hZNT30lDm6_2hkTwKw znMjb=l<&v8y(3bhR0`fONLcnTBOTLv&!7j)-Gc)U21a65HsTLP5|u#z3wWmfHwa6y zEEj~ulFe+3lqIxh%+Wo$grT+=gATuL`lKwSybN-RI-x4-m{%}H*ubs0Dg|MC^I zza*VoTM+BNz)&p+3qL-OH`+YG4(b#${p;G_^{rGr2$`$yG#Z5FzGM`u?4O9>^#FyM z)D*=iB65SsM$^_@cq_6Lx`^Al00JK5t29^K=ufGZ2cI>hjAH#8o@27lM3Il1ZBBeCQ@#K-|w9{l;@GDbwsbAxy8%_F!{_DA_VnH z>eD`{)SWygF=1=0D1iS30}}1SKfr2`=%WPvK%cbZa_jyP_!Sxn3Yj6Q-OJ#Js19uy zgmP4Oz6D|JVxV1igz+BUy5K=1fWM1YMC$u7(6L{woA+IIgYE_lEO_sjL@~*Bh+ZM$ z*V-|MEYTJvO4^#h*!3=mxW~ffs@xzvL!zNi@(BTFMUUl#1T;u2Uh)dbsM_7lt8Yaa z{05g@D7K5-m6u7h`MK9ZBvi;WzUT;R?1D+d#|u}%(KKYO+ekxE_4)zXF0sx_FKqpX zpKDxrsnc>~Qmhrxc|LbhWDrz?8r-(L4BiC=zghnic0np#LSi8^#*Bd|)=j&SP~P0npMJC%8hB z$jKfOI3@Sf`ur*IwRu6%FJ1WPfio~==>4zxMMD@Y&9l_7?C`>;j{sUGuY@rnFHbO` z7hYxen#UfubWXI5W<2~*-{)67_YeD-sU~xtOWp8knQG3NG#8^hudsMu+ZovO{QbJH zz=^xflE~@1|F@xDWxKouU9giNw|||meC1rKnrtDRw?~hiE&RXz9BFyC^~729$9>b* z1P*bi?1`MoWll}rzCk$s*TXe2#sXQy=wsXM@@7<V5nd4KlYpJG+`Kt>4h{38=Q&BJ=ih!6UyqEb$f&Yh0&@2dn%uNNPS0j3w zK#AV@Q?Jlu~_=2}N6ygM)B}V-`@M4_V_;OMS-no@gyKO5Z7~QUg|>ccA&5T^Y-;0qIwxu zBpVlxD?o#4p2Zph2R?w@uLJ%$z#nkc5^wO1CD>TY#hoo;)MK~WqvDN@dup$_!vu8 z9*{J6q-4r6+DOjFMZr6g{4-Z$x2lXD#bmxLW=VCVoEUiYl1-C*GT6=voA7`2rR?ng z-c1_ZBNX(e+A%vLtRc5aZ*r>+RyUw62uv+71c#W4F?RWpRB)aEB&1v)9`{!!wcx;ruQ3+{q67=2&Rh!eY%bxb?#?o=^B+TB}X+LjTRl65;*iXy@{OI(qU z=G8^Y_?QcOVs&cC?n?Tj@iZ~V-oUjpW(uwB+B zr<@MhzvLE{AF_)I>ix3@$N>9U4Ajsql3WSR04-dN@`&k?H&^N7* zzvmS~#Uy`JPk)nbMk8B_(-TW}wLrofic`q6`Qqxse9wBm?klLMt$I(YV`1f%G2u?j zEL|`C_X6lKtA{f#)Cpv43D{oZj)10%10R|Z0T#RLR+$M+2*_F#J&iTc)qI6Q1Pc+s zvM}&O&WSJ*dv(55f{aSe1IoUQd+~I~85z4)nQ)d7h1^b%lH#tRApYrIrb2oH{$HT{ zFW45h#?p3j`TbJQY+0@(>HcE~3Pv z(b7dmsRD|^-(pkZ+pK*5qza4o)jQaR`l)f!MfkTl(`k<}6Ghmq6urzp2m3fZ98iq{ z$3WJ`VRm6Y0qZc|_-GOY`%~fVjC_)ag#`u?e|D^hFE5}r*%GgPF7R4*C*uo6vizPt zxY~<&HQDX+X#XQZ`b~PC^`R*7&tR+vFlrE&-Dd}t@n@T1(9#h!sioqM7RuYLesb>c z3m0Gx9tPfg2jEeM2GXdM5g!3R;k}^8nBJy@-9Pm@+cn|(Ah!l%kboLSE6xnb?vPQ2 ziiUL)G`3yK6M4-iv|qb#M$VbM1115a?VRLzpVY$+%n5@^nrlu`K=?TBs)}DTFJR55 z*pWwA_o96^iEE7mkJl366CE`p681>}r)u249B(w|omz7Ej!!3&5_0ZLxN>fLl>nbO zFM`kXphOj zaU(aP;O+<=KfLGi$lya3VYYinS*JX=b&)jt(pD{`^aL@T8;0x&P@dr6#eSU0`wYYs zj8i0Z)gi`}?!68rm2@#%Py7xm1~h5wXUfK|Vh8UR3*{rHgr?diHVD4NHI%-QNYh+% zOw!+P;udb|aOCVP&z=rxpb&?xZJs+LdgniBCLE>o?gr`AOX$4}AKvXcIm|{?!JMy5 z|Aw4Uoq1Sjy7LWqN>b%3vO&mKziD-8oRmCvUnz`sDsm(-${~ z{zbt6`awrY^#A~ytPnopjhisJy9zHZX|u0g@|ferRlid<+4|!AvK|ZObosKAS@Gj< zGhYk+lu>O8qq4S;9vY{7829Y;=vxkCGtqxWeFgn z)_ujNMZzEu=q6Iu-&_+J3~J7>2kcMmoLaRaYssa@%?NukDe{Gn?HF~9pLd^|#Gq`t zZN|-NK_mt#JD112cHvb~TI3+x>wnUCOI*TOgEQSxj81m0$P1FBEn88!2LIOEPBq4aeWb|%UqIlt9kleDk z`|;jQj8c-6ya&k!rTc%JFu$P7VrMV9;RR%pX$t zj@PIEdNJLA1+~d_UqAZcHC6VhObmgHQ=&DO^!W(8e0vr-IU2wfV@@-1H@PU8pMl;K z`FH8(gmg=YWVnh*N9J$`Q*oG+ZR+byi^e$KMOn;Y!&n=GuyY632cP!hyK=+=HYHHR z>$yi+1{6_);_0DhzBwvCOC+A6j%?|U9JTxoB-L#v#MV#5(e5Fl-R0S%G9@*_^3$yJ z-ihLeX~P{kA8Xe|ehS28O^-`mzp6cUiT=SGc#9(u_$D=XKd$R%K4n)&XBIwY(W$~B$SG(QExQejK1k*%E(%eB8;c0nfV646i3)|behr=$ovdkEd3>h z33T`-cQGup7&>)rm!~H4S`?{5>Spz#Qcr_6`-h3O%_c6n&hKvCg9c?EFqB*Vb+m%C zR$@0Ss)F2*$3R98^zA8d|I2vo(axqpsFu$GNL(7#|5Gjd=tDxBOidMk$Qts~4iOU* zFwA10KWOWNR8TE_pLDpW8wjtq@`$!2vBm`na2_X#@Z_)!)UQ-{?530N zXmN4A{FWdl%TE}~m-bY+a2VFn?`p)CuX0}JY?2g`jH1q!7__;PA#q7#uQKLEb$43+lzm;Q%7$ZE;n1iHF)5fO5UFz+az|fsk5%h|)Gb zXXpckn0CXCi|*kK$lJ7n$XZpFhU^?PSNtSY^+LVo61TzvZoiGj-1wb#(x_LjlJ+VB zpQuDaL21Q!v*7SgL!a=T6IJaavGsEvcm6uNi-#B~UGz0CAh$WYG>MZ*Dx9 zE;p;ip02$r4Vy0b+*977@VyG=!ijV|oQV2gop$QzVsw5Q(kmkhzjO4V4;o2;)!msa zo*KWT60IYh==GNAZ6$Zwm!K#c!GSNw;Z`H?zUA@@)jL{K?hcK#w4bZ-wefP5{-m*n zkk;+e#v+ooAp`paY!W zw=XLfz}ruybs`cn!$RMQhF{8Cx~xA@bfOGV1 z55^R?31Y9l2a>-%f(CpWm32~mMd$8Asp*i7f_w%)KUo@ zp41UDflngm)G;=F;YuU>&#Hos$V&<+Wh&lN-IH0SJ^Hhp^8y!C238SAyy56Kk8$5r zsLSnq6R~Tlr@hzkaq(jfmSiZsV@xEJUf&D5&y@EOWWFpeq|x^cx}X%lC5^Y^oIA5m zBgIytrs`;zEoqd|L-v6v z1+UyqmKz{`fL0Z~nwLfrn3q{VzcXAv*0)OZc@Ou%#D>ub)_i>^HuT9Y4h9Qk`*;2F z&Gx7-O4U#XK8^-)VRVc_k6w*^mp&F*Y~XlnI^>x1qT?zwhx09OH6mx*63F94=1b6? z&dG3}oTS2Dp#C57eIYUOdPHxkuMrH)Yevo^-Z)m?nJSjGo>STE(#742Nk&^Kuw0M* zmGgk~oSA?%!L*c3%!)_Vh1M5vRXFU!==tdKc_&y<>p?-DPn1d6+x zg^4FY2SJ0FPQixNTn={lU{V^F5IDQQln*$w{KPLmbw_PLbE|gDu?Z#QL}Q+Bz~~X* zT%iErBPORX+0hxQ^uo)oyBY(yxs?`!nZ*BqH@ydp=73W2UcMziU!V~|F^N>OBzCyV zI1x*Ni%&GJOzDEDz3MN1>j=Y}j_%9VipF@JlD*ps>k6>IXU{XPk)36wHXR$vIpO&A z41eN{cRjaGPm*zSF+zR&M6D3RcPY_v(2da6+lYy7u0(}}@bhqpdTXhEW3|_k=ya6d zVBq*V%bCHWgPTTsF7ule^ZQ1KUemlV&MxRyQ#@P15RtKNU+tX+914`CO!`{xihbfbhbqc%A|#Kn=k<`n#~0sPxKThT6!^inwGtf(Kr z*^OdhL@Jpf3uGCmdbf&+QNVy z7_ExO7H*9S4ph`L?2i+-rstfN9v?Y#cr@a_5rL6=VRR1IsSD-=%GeSbRS@3ha#xz2 zj1!XVFCBEkle>;cB(4AFdzo})nH!>(F1FCiEDR|icwc=G_iYS3$D~?XcHn5|($3@l zhV@ypPt6M~A{k(6q~!u+q*33F5-GbR56AOD7R+T%?l=@Ak{Vpn12w) z$s92t{~&z(Nuxt)oUB_VCS>h}IufbvR_b3p!v60$girCni(+!!>DPB?Bo988tvNKf za08_pjwfCnQeEB&bYVT$UjJZ9G{-5}yLPmzb-|nBHi}uKiB78LrHL6Xd9~hxBYYwr zNf)85g5OKP@FX_$eL={8V}tZEyRZ(62oMyki8{J(MZd^}py%ns351ps-5x^9)12jg zhTI&m&qRpIxc%YYEjGrwvP-J*5?F9`B2L%1)#l^de z+iLvh8#C=8Xg*V&Ik62=IS-<{Q$3b438K2J5R&6@x{3MeT7SZUS!V{p+iQk0gt(^f z(fdD6Gfm+uYt4zTG#Nw({_+wu^!m>;jW#Ukkl)oz69ghGg+%I;@}oxDlSkYiH){h8q?qn2rzA9%lC3B!+``iQr>wtiVK_BVUJUbzsk z)-i&4bN68cF?DvD)j}+qKw39Zb>CyhNCF18T-=rT!ax6&M?aUze*4$b490r# zN}IK^9903^>Z<2DuxdciCY$C<%O#yp zM)RNZh{nT;-}1kjwpS6;jf|^f(av=f2VMnTrKFrqu9;$pw!cdXouD&}ddQEpQFV0` zvB3FUelu8GJ0pp{L9kiIcQ;ThgCi2=K*cBXfklU7Bcu2= z=5ak(Qirf-w|2}eP zc|sgP7amzlVQVMucH51O3>%3UqjU;gNT%-eiMKq?P#nM~G5FN0Ebi5K`O6%+Y0Ff# zyeG^h{P7(>4W4G3Ig{jvCX7H4BMsJ|LO=;AI#^(k$XG%!|9eFiEx{av)`!yhMUXoO zs@32z!|~xMwsQ{)cA2Nv4BPqzAly`{`0!C&uyvz6jRf>ZZ>yG{`H*W(D%}In7u0Lp zK`bXknE!gOF(jNAiR5%gvsK_fdHlOm$i1AKA3JH0y9k2LIn5N|WCne_LBiNHx=5Ah z?OASkK}o+uR>K8?xg{vEKTGE`hl?2Rl>(nFyP(d8;Slt0tN@@veC9Z=N;}qgRk(`% zzaj1mH`FLXQ>gGyN035PySE=s;77HD$L{sL(@?)$(@=*gmLq9$Z_cihKy%aWW zgK^c&xP+3SXT6i!e2;jaM?#mQg&Sp?m8^+}*tu~*p$+QH7lc|qk!MP;Ek4yyigMD) z+9?08clwY~9Iyw+3@Q(ZIJFJ24=qUiSX5;ax+s~&|EE?5H|*kl*o0^qpUOfpCBrZU zO29BAq6Y9T@z{W5JI(vaygI2&LByp%EJ6@S*L;T7IY{aK3q%^hQj} zFHy>;T=guLj6S4&liU6TD#JZxV}D8~i}|^B!=s`I&>Rn@vDRXCXB%G- zUPd$mg#o*SP7Ija1>d(nAt03+hi=d`7XQlVvKiAYzdQ?*3ichMpDn-m^9R~uSu(%% zF)K{HbF^#eZ$+anpojUaN3e=*#4Nr|g)6w|MC?EFij1cqh7bL3^hS(c1foKdF>RES z8@1slHCe<;=6KYV6QI?YEUIS@glpRrTIy04=tgL=)^idtepsG40&*o_VV>@<{@;W1 zDqvQoJH6DCbqS+8+c`@5<0*tMzn+{_@67X{j9WUUd%L@cSzQwijFZoKQK!@UdKZw~vF{UpC9G~UqYk-z7(Z&H?z{Ma zue}A9T+J-M&|D#iVL&;(EWA|-dsWAnUxi8M9POPTQr3QiPo_!%r zZG*AG7mPKpH~WX)F)N<_<`lV{rzvYu-Y<8@Hg?0jjoI*PJ*TARk_h!$72~(R;^1n| z2tLYHCkhD3O$K6Y4&ji_-HAe1JtUe|-$nRFoK`Vc⁢^Zl_TQ$H=+Y^5*BGvgQxb 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

axbNO7ABUTRuYH*-&5 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 x8WDVc8RO&?C>^Tt0y4AXn2*#rw~id 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 rdx6ivGD@9Xl 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

kr?WcW7@7C8Nc`6dn72dD%K=CH$)H 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

OAyDbv(8` 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­3EU z#kro%k2wTH)4MjkNCw$4ce$KkiJ^@pV;J0?Ub2M_a3(0X9C{4~x!l8N&}>+S(~h@Q z?}G_VO34%>E|J+AF$&}|7yHjsEagc;cVZA{Py8Ha2-(5#y?5t9iHa*TdN)TjCZW8xqEz^626l$r`ms=6O9| zzGrS@GDl?ZwK4x13w?X6U(&ozqkIy2Aivb?eS2Y`YlOsS*;L*v6t#H*4W9PLj(#}APr78;6* z5Ll24F=Mgf26oJ`y0CeGGi4H54Dd3L43TxTT54djeh4TMao2K@%0JaeQJ&j!!Ci%h z{a6Q&K`(5>vFtcbnFUS@D;5U)J0CD>vs7P+(eYx6CR2uq@Se$MUF$R|0P5{>qn@QJ zo0V82!jJT`FR1v|+wSMGL$8eY$$`qmOInK>Y^1aZZ7q*h(PLR}P9UJ;! zQKKZtHovcqz@*4t<*>NN1JXbL3p_f_4JpEi(v%jW{<~J>oCdiBJeZwWSV(q|{YIS0 zcm)Rp*?44O3~X9mToBhg%Bibn+cobzxhd!KLRdsul_-`oo6=Lon(Xt^daSt{M^u9U zAGQyUz(9bnL{4`1vBMin3-XiI>h99Tl04SxtosRwf;@>(n`uH@`cZbd{TwjgyEUrv z%r|HW6}O( zpL!Ud`dTJ^@EcfqZIe7(q)bTKEC!abZ8Bn8dZ+a^|5Qi=NZ!f@X1$!B2K*datLB1= zW~{Y(snU+;8)a@#j6AHOvdcXle^^`m@`eKkR%~8^_;@v!v)S@Q%xKd4_{Ubmt3}S1 zPro?Jk6)v+kBf=n6@!;gNx!lq^|2Mk!tW(5@u)Wi+#cf2t1~6d*Q*cJu&!*S8d!m~ z9}bw?rACBjHGW6h@vRVC`bda;d;ZaOGEykvk(qlbBg=JOrD3%)eaptr$t1VwI80~J z?5oq~e<%LeI?P~HWB_8a&l4-d`YJq;N$>Zkr(w+&A9Hy4#e6|=Fd5|!7k0RemL9+xg%R7LJ}{@q{)1LlP1RTU8UpRuhKK#7i4zqM#!uT zS1CS|Bt>Q3Nt<0BDL<1P4}dkLATAHdNE3)|0hvD}xO5VC$syKd@l@?!V@77J{>2~F z=s5K9Pl!N=*vXY7T(~E$+mRN{z-i%uGFT!J)-?Da7)el0Cahdh2ZIDIXV~iZQ(#MO zxSTi!z7ns*HWi0KLrKabEjJKN-XYCMl_B94pUOe=L(}K6z8P4wix9l!Ivt}tuXDTN z$R%ai%j0RcrQvKXBjY(wGkI^}{~_wF!}gD4|Mmr_G&q=a+`NQra{(v9?h zfpm&Ui-;)Q%|;_7sD#7_0ddNZR1ohM@6Y#l-2b454mdW(b-m8#^E@9XDHr<&-9qF? zf)skLbb_G-s^1jEOBzpUEbK3>mvxy$7$>1y^dj+3f0GuO!MbU8bqgpwl7=xK2@8FU zR>&t;Jj!?=bZkwB9AY#5)w#JZTOSIfE`I=qM>GLYn+O2sySp01`UU3CM4=I?)Z^*o zTHQYT8GvN?_w1K207u7?>LBNb>BGdt50Wq)G%aggn+~|$)3TLK$0m__*hhy1wUzdl zj&~z^aKGyKe{5|o!XaTHh4c}uT5BQZN#5*<_ze*NJ!Z~j1Nng^2l!;(*`z$kC3sFM z_DKzZdRJjl1ju!;fwP03X?GEnYjR~Sdx_@eyTg(JR1|P<3dzA6QhuW6e#eiQwXIzM z0X7IcN3EsCwuV#} zfHqD~P^>mYE~)vEI3!zD4!03+JiRS~^W*9h)xzem0eV9gjZ-?Cqc8G0o7fCeJHO!e z0H5A}!qc`(i`lCz{<@q`mSTh!7gA3|D=hOKh(%%Vn(8(rm9#->4@-VZwq<%0L>*`i zER@5B{tiGeqGYaFaWM!jwdb*iz>C?tS@AFo-bPoOpjSsX&xUMGyO;SHpbI+ri>9$j zHj(Owji9r3f|E~K6y1}o<0(MZwXL!7Y$tMM9|Wlw{&rSB{ii3m@lU#TE#z%~Z=5G@ zWK<7-ujn_w^V;G^3u{Lypu4tve{rz2brm_+x^n;Dp4spT)0(JM@G1-4-jg(I!VT-q zv*!SpdSAESA(oyc!Gzg`G850pBldSa<Ke*gRGlz8w3HfEa< z!EP5UVwCKI@%m}MEvfl@OXZS{on3%h_+{tJW*TM=qEnu>iRj5a_>ehTd6I%kUbKlo zgiib)k3i~lR}QC^S?OMu-%@$V*$sfr>&isxc$UbB*UzD~DdzRT3aYP-A(zWJtFMoX zkc2@jc15YrSloL^M7u8Ys?@wn;&DT`l+vgfMU)!MqI~K2r0u?v zWc6ahNIpPnuZ{V2E#23l@!I^V{PyPG()a5>;hug{H})t5*6e=P2<3HiUukS1wjIjl z(EV~F_6-XX9uj^DDwvD6TiT^fe!vv-;k~LsodnTKD9UaC1K)g32UP{TE!RL&l5tAq zUM9{R1!1D@irCWOa=8BsTF~mG0+gnw9c|ffi|lfi;6hlJWP&8-D{jp*-0lkQtgNx)BudXyWl>b zG2|q)DEMJ*Ir~Nq1^0(&MM}%pR5@XDuU6e>QE?BOkru*VR6NFBeSkcz#!e5Zv?Gq} z9LGLMRumNeu4~9(1=|9$Aw{&`*k1jTQoGU^nLP_SlQ4;TvR!Uf^A{nvUs^9u0l#qC z>Z;&T{)egKNJX-M-{E^=_wUiXFm19u%?>#lr!Dx8T<|LG>Cp=*oj>IPS~==JN`Aeb zO2FaYq{Ft5lM2s^T2D}z@7XFrzOVC>43e}`Ikm5o7~ z$O#ACBQIlda48nq)c5g| z*EKJpT)v=fTmg)iAE^iC#}O-pXN0j5x{&(VgRf8TKlfw%vsg#sq)Rl~E&*^S17g`P_j>KdoM_L&d zN=7(Imbg@|2P<5L@%3)iu7QHW@?N<3f%DdrSxJTE=AP!`75CzRNretlTB*Dzk-h)! zZNKXVKK;UOnxMaX7Ia0e#A)>Y<6-m=nW>~amwzJux|Y89ELL1zFqNRh0AcZc&hxFa zQGztN{hrk)NC5FZ?F71EE~^)n!EkY9<9)Cbdp(h;j2m_P3Pza<$c_Ji-728*8Kr$UwHz5 z-h)Klb$}AA@VYCd9B`g5;rBiAtz6_uP{32^;%ez|hrY+Cd$^6I2`S0RZ*Pogt}8toQJuL#NI5hty_H!^_%1p>?fp>jlnUsT}S zH8|RsdL0#b1-L*m#My1ez?1VB;!FK7aL1Nn@P7q~oC!DLIpjG>(dQhFAx@~Zt2$r#Z%gmg`ntTq129CDZzzMS5(ex?z0r}+VRkI4aNj z@bdOb>3FYa1oN=nIeKaOHg&YwBH-rRm6YgmueXT-hkuK-0S!yT*w%g+2;dNLQ3%xqZN`9b94xEsY^{WKiQM zvv_c~l%csHuZTMq#mcDp3D#CPc+R>!W^Z`Imx240A!9oa`O9Y+c-jxuLZ)h<3~G_rO$5zhr8E2&#!-~xHqF}(!m6ORaEeTglyS1;RPhdp&>ku z!Siw@Wc%d-twm^MzMR!s!u9*5Lz-?6m1hXG$SDiCKW?cscfi*P?1&+<&dFW253Tb2 zFkodqUrCpe$u=I=D+OKZP{BAfm=I@aV36tTq+fOBPNmvlU>QGX*NbyAK{pV>Sb`PPe7=)2=&eBX=i*1VeXkvO*sz(c#UtGzq}{+PsM1`R20U&eMLMJAV&cjXXmFsK<<2tcA#FG z1e$BPqbw`_%L71%FprJ~o*xHrNLqY=5Ed?wS!tmsYHdt6x+@Lwbn&^qG4P|t`?Hd; zFlp<2-;c|lT6<)WNa=7vAe5-kVd=}HN=p-*4#{%ooAR)u6NJqVV9G@@9MnUxfzSbu zez24S-w=_u9sAG~0MNoj*2H4sAlpkVO>HpaFkKAc1|1V)?P1?G9h;wpMcpBuEL9FH z(i~B8ltW};)XjAqnxQ4PXbz7CCKU0<&$w-WC4Niq@KBJjxDfeh^-FP@f2y(r85sw` zbkz&4;J!*nJqX20CrWOmI7Dzi$4ck@V41uD@_uV=gj0Uqr71o~e2CuPdc!1aSzh^H zq-SpGZ!k9INn?Mo#;#VjdMzEi_#G6|Xu}8TN+c9ipmmY-jR=_`$bUogD$esf z)h*xQc?xvb<#udMZ;4jkIZMGGr^Xk9@NrB(urG&TULITz;J^H^pcyUMLbJEH_A%AD{ zOZLgBEqqIk5KYNlY@OPu<0H66x3%D4t3A}<`E3Wia}YkH|xy$Rbpct zNyN5d)&arpMn!7>$yCp1uQWLTifzdhhsCDrEV5{rhma&;q_7X z3l205wKj7s*e#L}kFp(UpQskD3rD__^ZSA+XWEkpbK8w~i7+(!<>Krf#>LKG%hM_w z@N%R6-oJ)}NTD;mv3~|0LZ0d4(@=dYkC(P+Wegx`uZOy;y~ASZnHcx-xK--9IKG${ zv}t?$j!Xjp>_N#26_z<$vE*jnv4!{krB)BtA+`P(#IQCaS-yGEw3Jz9rV z0&UxZ3YPI(; zAk2niufcw*29`Xf*1GP)%DE72KdR&0mwf{1`%-+^$8vCk_jW<&!lWiu1b`SQxHTYC zbRPY8*8{RgL45#7m_X@;(ifOFIF1k%M+jTzbwN3AoZTq3Q>&PM1M+PH(#)TuONFpl z2h91ZW=d{JV%v~4SOXt0+%68+Fk!0Pyd~@`6`^x-KTd4R_7g@wq17Y`@$vo-xAFMn8 zSk9XNJ$m?7CasZH>1}D>cScT-|-H1tBfGbBd8goUCE3<@4hH-a!lT~OdhOVB%IpzWAYoN_!--F$G3+; zY)YG&vDF)wiL70+t@FdnB-_y45xKl?M69|H)7}~GINei92WO9dhH39FP9wh$+3hY) z{t;1#{`--``|O{{r*r8)8Da9hN7`ctUqq~2o6e*pgn(yq?jGxDe)?Q_xBg)B_aLjc z-)FhjXRCdTDiV(#^bX$ zaAG7691S;~oj4agk+@m5_~fo^#k@~+7rVq#BM!WPCV{jkz0LcI4}4Y|kc>dBm|vEf z;$<*&s$jwF|JXKh&Dsf4v#SK$QBXE85ny>#$P~)V+Jln(+s#MgcQGL32yV-@5z=R(!=MocA9KZUp}8dxN^S zkS;|hiS!YJfB|D(8RKwTVA7L~ko3N7%yj#j#XT0Pmr_TDWvbuMSA@dv0Eo{BXf0i# z#^{=S0^NAVpX@TLfkoK_R*r**8j`AuObZLegMHt0CY-Wp&;*8=b|D8$x&$Z*=MxoD zV+smxx$?JF)XF<(@Hi9QK-T~U0M9Q{SffwFDYe(}S#>CHXebB5L;8pB1!~6Mn#d~k z?DU+zb(>N4=zUzawW^e<$IP*r^QfLYUE08gVF;GEARPwyBMWJuuTi20r{p12*`PYP z9z7knDBzl3Ow;T~0m7>PE!4h_P)jD`l;q7U$)k_r>UJ%!=v(Q;H%e>z+O~t`XB)6x zf~cXv^9I7^CXTi-!ihUiIze7UmNqx%tLk3RGmOsL<11t|#veeZ*&&|@Fv9V9ssvt^ zXY+r%F3MS+slt`Gsm;R);M>Lhwo6D|3(&E9XFy(13b17Bb*LjU2|<1L7<%vQ-Ieui zS0~81f)-{Kz1?CHZ@v8wy0v`TYd6^3dlvj7<=>%eYdyT7pMpmYm)k)2w`hN_wJT!1 zZ!Wfr58x3GEPOqVAI|*-5*2;Y)7S1*;xM%IaO5lnZ0nLi33!197ZO~@uv=tVuy==W zkB#Ep@h96h-^UmKh&H=CUa@le^@m~N^XY)%6=0W{j9BhjS$GJOHl+AC6){IVX%GND z8ALB5$QRZGh2;v`W*CnxgXz`1CZhO=H?U?%m%iEl%87ulAX~Izx1}ktm_hiS#s&RL z_43>g&Hfn~>xANbJ{@8};?JCS6?Vq5P@0wj^PMRTkbqe@y?(Px?G*=X&Y~dq35h=d z%>&c}jj&LaIpvp}nP!Kze~0c_F-*KLpA~BT-+kj&nnkry*7xRb7N(-@5D^P2D~6^M z6;Ur;VS|resM0}l2FbFahxt6o*ClGoAr|N3Dw+r&X@ZPw3O7XBDg)7jlAk^esp!zO zF}CiN#R9Wf)9bhn7I}?sR5rfkk#5qHTSvEUR||EV#6F|FwWsP7VXeat3a6w=x#fvw z)~M*%Fih&fBT=R@AWLSk1}G-e*VstnN@lSDvdSDI(yszco4G9UY}~5a!J1R=ZU4reH!C(i$w`g=BZ&+sVVcb2$9=rb~^w@Xp=|C4ds3% zqaDoHoc!>?rqaq&R>rqLz1p33klyedH_d_l5&c2)hPQw1s7%bj%Q*Vh_T5~EW;?au z`%2QJJPKHv%Me%imVc3Zc8P` zLQuP!RI1HoCU}1|b3rSewa^37WmCQQ{j>PK8j59X|HJAGsZ(~<+iP0>=?&oa8BE#! z+0ci)Sfik4;lnz=Xn_eGk@7|?FnzMUoa0=JE}DC=M#th=&L}Ihgex;7Kr*x*Dixsp z*5CC#GC|w__Z7fka>^?R>DT%uj#zm(^{`|RK&0&BNFX;=@C8M=ek2hS2hvXvOWJ@F zu8j1#Or|-fhb8!+BnE%iiVLOAr#q*w17fGA4qQKPyRcXFhu~6}Kgy9bgR4i=CCCqn#kA+sW?!EEF=Uj~vi5NLu!v*m{gi?o5B&Uc;LB zrx~u~JI@4gy|+oBVFpCVm4}ptEuOIzf~&-tESXSPG;}t&}Ty}G3$pUts&x;PicOo_^e9LQC+Ufq*ok(w(LSj_JRc) z>wf|>3^Yw8{K{gn{-(^Us#`XoO>QiSrjbtSUK3_rHt@f_vM(EY`i^X^c| z)^hDxi9LMMdtJ~oMmM<~B#~J89eqWte@*gj!-qyTIj(2jEze`fh&{Z{F)knEdw)Zd zhVEYGN51>S1^!3NDFfZiJO)ins zu*6~_yuilaGr{woDRPd8)Bt=?RHe>-Yb_d_68`G^8C_k;5C3}Shn3&a9-l|sQIOtv z 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

ckL!uutfU33AjhsK(Ge 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

;pSd_1 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 E04Fupp#T5? literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/generate-key.png b/docs/src/developers_guide/assets/generate-key.png new file mode 100644 index 0000000000000000000000000000000000000000..ac894dc71b34f3aa2ed3165077ee785cb2880765 GIT binary patch literal 82385 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 literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/install-iris-actions.png b/docs/src/developers_guide/assets/install-iris-actions.png new file mode 100644 index 0000000000000000000000000000000000000000..db16dee55b4ed54577fa89a31d69008ef2907138 GIT binary patch literal 112917 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 z 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ƓSg2NqUhZ-FTpaH5P>kX(&Y(j6kLVG!y(Fr=Z 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 Sts4)(_F76ovP4|Z`~Lw%&d+iH literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/iris-actions-secret.png b/docs/src/developers_guide/assets/iris-actions-secret.png new file mode 100644 index 0000000000000000000000000000000000000000..f32456d0f26e2782ba2c9aa89a9a447e5a638510 GIT binary patch literal 55246 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

ce!^BNQi zwXOA+^rqkhV?o-TdZ_ZeZ-HFclGV@Inqlir-55RUNR40j=%}Dok?jlhphZK9go3g% zx3IAAGM!vjjo5*YRQn18!D}bHyu6sS%g=rlj2x{Cn#rMOB}$~@;_c~TcgkFL;X?0U zTMl3DnJ&5C7{U1o-g^g3TXQzf(BSLn=;$6E7O*O|M*{JHgm`h2l~cA@FW_k6glL3s zUeZ1;o*vWN*LTLq$cTx|x$i>3!I*+WB~E%;pR$^N1}J~=cv%?(WGI*!)>G3FYe;He zrV<%t&OP(3$BrGF6fey>b+_lmMK=Tew3{?jv4%?>pFZ7!vyASB_pJ0%C}_$olQF}v z;?oMGTWfU!Y&Qs+^Sc3bzr%66F>W}uzY^A4i8t@e&`i^gmq-`&W3Ehbt|hHrz|)$N z%Ci&rA#p(TlS;8PW0ls+@kFiO{74JA?UtesZ7;8^oTeIv-cRezF_|&(xT&)zh=B_W zf^O^^P-1y``J{Nk>e|{=mqcv{!v&sX7&>=*Va>S*dwbYQ>$9F7sukLnC2gV-7lHC8 z7>3s6D0Z%{u1*#!zFxc)>f;UY_(!q+kVen?EBntXNO_v10WB+0v1Oz#NDdKUs%GG_ zVwMbFmWa;>oT6orp2YqL)DbSB$zqAu-kU=e9?bL`ZMXQ{4j(zPi{HUX0mJPvOYC_e zHBju(g)U7nwg=FX6+D|pHl&=4+630D1mo>}G0jBD(R=*R9{vP-53$i#BI(z6F>zm& z@)m?>B8lZsv*g7ZA`FLaxIby19Eg+DAZ20sU3!jZ3EZR_)^__-X>^og5Aeiy;T=7P z<0@aS${M%{=og=lH+Ixd)8}{A+K@r*j{WoGeX-Hs7Yb2|og%bfv($GZ^}Z?@2;7KU ze_4+F+!>RHlt+gp8P3IH4BaO#nF&DM4|QB~E8HE+Ccn|x6?Zn^)|~buq})XVYJr%2 zh}UbB#(vL7#TGA|Qi*ryfyRPQr3E|fvO_7&Jj1`QNW_`T?khI>J^v%X5zR_&V&}+t X9f?T481p;3H1w|?r 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�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 Xf9_#7=QQ=}Xg0lM`3Lsm^*jFu)i%X9 literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/iris-secrets-created.png b/docs/src/developers_guide/assets/iris-secrets-created.png new file mode 100644 index 0000000000000000000000000000000000000000..19b0ba11dc38fe69e433fe93978c0307ad7fd90f GIT binary patch literal 170466 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

x09C-%1 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

!>H>v6Q#ugN?{`fjCji>r#x3c|CIG0kW@WQj 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
!2a@GVNfxDV=ScPt*A)6R$5zaQ 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 literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/org-perms-members.png b/docs/src/developers_guide/assets/org-perms-members.png new file mode 100644 index 0000000000000000000000000000000000000000..99fd8985e2ab4eff38a0a8dce4be12f6b81d5839 GIT binary patch literal 27562 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

@YhrTDjksZYiU%P`co8 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 literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/scitools-settings.png b/docs/src/developers_guide/assets/scitools-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7e728ab5b6029e81b8da422d11cbfa0d7a24f4 GIT binary patch literal 164331 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 Lo9dx%y2k!L9$FDK literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/user-perms.png b/docs/src/developers_guide/assets/user-perms.png new file mode 100644 index 0000000000000000000000000000000000000000..607c7dcdb6aa96f3756621de6721cb25c196b4fa GIT binary patch literal 105132 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 literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/webhook-active.png b/docs/src/developers_guide/assets/webhook-active.png new file mode 100644 index 0000000000000000000000000000000000000000..538362f3352690cba5b82793fbe5ec869c3f2b95 GIT binary patch literal 106889 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�%%Mj`o+X&~{Hwj|UA7%NEESm6c;7 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⁡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

    A~~v)BEQ{R9qvD_w7l{gbgHR2^Jl>Sozj~5 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 R=f?~{;OXk;vd$@?2>``|GF<=w literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/contributing_getting_involved.rst b/docs/src/developers_guide/contributing_getting_involved.rst index f4e677cea2..9ec6559114 100644 --- a/docs/src/developers_guide/contributing_getting_involved.rst +++ b/docs/src/developers_guide/contributing_getting_involved.rst @@ -50,6 +50,7 @@ If you are new to using GitHub we recommend reading the contributing_documentation contributing_codebase_index contributing_changes + github_app release diff --git a/docs/src/developers_guide/github_app.rst b/docs/src/developers_guide/github_app.rst new file mode 100644 index 0000000000..338166fd76 --- /dev/null +++ b/docs/src/developers_guide/github_app.rst @@ -0,0 +1,272 @@ +.. include:: ../common_links.inc + +Token GitHub App +---------------- + +.. note:: + + This section of the documentation is applicable only to GitHub `SciTools`_ + Organisation **owners** and **administrators**. + +This section describes how to create, configure, install and use our `SciTools`_ +GitHub App for generating tokens for use with *GitHub Actions* (GHA). + + +Background +^^^^^^^^^^ + +Our GitHub *Continuous Integration* (CI) workflows require fully reproducible +`conda`_ environments to test ``iris`` and build our documentation. + +The ``iris`` `refresh-lockfiles`_ GHA workflow uses the `conda-lock`_ package to routinely +generate a platform specific ``lockfile`` containing all the package dependencies +required by ``iris`` for a specific version of ``python``. + +The environment lockfiles created by the `refresh-lockfiles`_ GHA are contributed +back to ``iris`` though a pull-request that is automatically generated using the +third-party `create-pull-request`_ GHA. By default, pull-requests created by such an +action using the standard ``GITHUB_TOKEN`` **cannot** trigger other workflows, such +as our CI. + +As a result, we use a dedicated authentication **GitHub App** to securely generate tokens +for the `create-pull-request`_ GHA, which then permits our full suite of CI testing workflows +to be triggered against the lockfiles pull-request. Ensuring that the CI is triggered gives us +confidence that the proposed new lockfiles have not introduced a package level incompatibility +or issue within ``iris``. See :ref:`use gha`. + + +Create GitHub App +^^^^^^^^^^^^^^^^^ + +The **GitHub App** is created for the sole purpose of generating tokens for use with actions, +and **must** be owned by the `SciTools`_ organisation. + +To create a minimal `GitHub App`_ for this purpose, perform the following steps: + +1. Click the `SciTools`_ organisation ``⚙️ Settings`` option. + +.. figure:: assets/scitools-settings.png + :alt: SciTools organisation Settings option + :align: center + :width: 75% + +2. Click the ``GitHub Apps`` option from the ``<> Developer settings`` + section in the left hand sidebar. + +.. figure:: assets/developer-settings-github-apps.png + :alt: Developer settings, GitHub Apps option + :align: center + :width: 25% + +3. Now click the ``New GitHub App`` button to display the ``Register new GitHub App`` + form. + +Within the ``Register new GitHub App`` form, complete the following fields: + +4. Set the **mandatory** ``GitHub App name`` field to be ``iris-actions``. +5. Set the **mandatory** ``Homepage URL`` field to be ``https://github.com/SciTools/iris`` +6. Under the ``Webhook`` section, **uncheck** the ``Active`` checkbox. + Note that, **no** ``Webhook URL`` is required. + +.. figure:: assets/webhook-active.png + :alt: Webhook active checkbox + :align: center + :width: 75% + +7. Under the ``Repository permissions`` section, set the ``Contents`` field to + be ``Access: Read and write``. + +.. figure:: assets/repo-perms-contents.png + :alt: Repository permissions Contents option + :align: center + :width: 75% + +8. Under the ``Repository permissions`` section, set the ``Pull requests`` field + to be ``Access: Read and write``. + +.. figure:: assets/repo-perms-pull-requests.png + :alt: Repository permissions Pull requests option + :align: center + :width: 75% + +9. Under the ``Organization permissions`` section, set the ``Members`` field to + be ``Access: Read-only``. + +.. figure:: assets/org-perms-members.png + :alt: Organization permissions Members + :align: center + :width: 75% + +10. Under the ``User permissions`` section, for the ``Where can this GitHub App be installed?`` + field, **check** the ``Only on this account`` radio-button i.e., only allow + this GitHub App to be installed on the **SciTools** account. + +.. figure:: assets/user-perms.png + :alt: User permissions + :align: center + :width: 75% + +11. Finally, click the ``Create GitHub App`` button. + + +Configure GitHub App +^^^^^^^^^^^^^^^^^^^^ + +Creating the GitHub App will automatically redirect you to the ``SciTools settings / iris-actions`` +form for the newly created app. + +Perform the following GitHub App configuration steps: + +.. _app id: + +1. Under the ``About`` section, note of the GitHub ``App ID`` as this value is + required later. See :ref:`gha secrets`. +2. Under the ``Display information`` section, optionally upload the ``iris`` logo + as a ``png`` image. +3. Under the ``Private keys`` section, click the ``Generate a private key`` button. + +.. figure:: assets/generate-key.png + :alt: Private keys Generate a private key + :align: center + :width: 75% + +.. _private key: + +GitHub will automatically generate a private key to sign access token requests +for the app. Also a separate browser pop-up window will appear with the GitHub +App private key in ``OpenSSL PEM`` format. + +.. figure:: assets/download-pem.png + :alt: Download OpenSSL PEM file + :align: center + :width: 50% + +.. important:: + + Please ensure that you save the ``OpenSSL PEM`` file and **securely** archive + its contents. The private key within this file is required later. + See :ref:`gha secrets`. + + +Install GitHub App +^^^^^^^^^^^^^^^^^^ + +To install the GitHub App: + +1. Select the ``Install App`` option from the top left menu of the + ``Scitools settings / iris-actions`` form, then click the ``Install`` button. + +.. figure:: assets/install-app.png + :alt: Private keys Generate a private key + :align: center + :width: 75% + +2. Select the ``Only select repositories`` radio-button from the ``Install iris-actions`` + form, and choose the ``SciTools/iris`` repository. + +.. figure:: assets/install-iris-actions.png + :alt: Install iris-actions GitHub App + :align: center + :width: 75% + +3. Click the ``Install`` button. + + The successfully installed ``iris-actions`` GitHub App is now available under + the ``GitHub Apps`` option in the ``Integrations`` section of the `SciTools`_ + organisation ``Settings``. Note that, to reconfigure the installed app click + the ``⚙️ App settings`` option. + +.. figure:: assets/installed-app.png + :alt: Installed GitHub App + :align: center + :width: 80% + +4. Finally, confirm that the ``iris-actions`` GitHub App is now available within + the `SciTools/iris`_ repository by clicking the ``GitHub apps`` option in the + ``⚙️ Settings`` section. + +.. figure:: assets/iris-github-apps.png + :alt: Iris installed GitHub App + :align: center + :width: 80% + + +.. _gha secrets: + +Create Repository Secrets +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The GitHub Action that requests an access token from the ``iris-actions`` +GitHub App must be configured with the following information: + +* the ``App ID``, and +* the ``OpenSSL PEM`` private key + +associated with the ``iris-actions`` GitHub App. This **sensitive** information is +made **securely** available by creating `SciTools/iris`_ repository secrets: + +1. Click the `SciTools/iris`_ repository ``⚙️ Settings`` option. + +.. figure:: assets/iris-settings.png + :alt: Iris Settings + :align: center + :width: 75% + +2. Click the ``Actions`` option from the ``Security`` section in the left hand + sidebar. + +.. figure:: assets/iris-security-actions.png + :alt: Iris Settings Security Actions + :align: center + :width: 25% + +3. Click the ``New repository secret`` button. + +.. figure:: assets/iris-actions-secret.png + :alt: Iris Actions Secret + :align: center + :width: 75% + +4. Complete the ``Actions secrets / New secret`` form for the ``App ID``: + + * Set the ``Name`` field to be ``AUTH_APP_ID``. + * Set the ``Value`` field to be the numerical ``iris-actions`` GitHub ``App ID``. + See :ref:`here `. + * Click the ``Add secret`` button. + +5. Click the ``New repository secret`` button again, and complete the form + for the ``OpenSSL PEM``: + + * Set the ``Name`` field to be ``AUTH_APP_PRIVATE_KEY``. + * Set the ``Value`` field to be the entire contents of the ``OpenSSL PEM`` file. + See :ref:`here `. + * Click the ``Add secret`` button. + +A summary of the newly created `SciTools/iris`_ repository secrets is now available: + +.. figure:: assets/iris-secrets-created.png + :alt: Iris Secrets created + :align: center + :width: 75% + + +.. _use gha: + +Use GitHub App +^^^^^^^^^^^^^^ + +The following example workflow shows how to use the `github-app-token`_ GHA +to generate a token for use with the `create-pull-request`_ GHA: + +.. figure:: assets/gha-token-example.png + :alt: GitHub Action token example + :align: center + :width: 50% + + +.. _GitHub App: https://docs.github.com/en/developers/apps/building-github-apps/creating-a-github-app +.. _SciTools/iris: https://github.com/SciTools/iris +.. _conda-lock: https://github.com/conda-incubator/conda-lock +.. _create-pull-request: https://github.com/peter-evans/create-pull-request +.. _github-app-token: https://github.com/tibdex/github-app-token +.. _refresh-lockfiles: https://github.com/SciTools/iris/blob/main/.github/workflows/refresh-lockfiles.yml \ No newline at end of file diff --git a/docs/src/whatsnew/3.2.rst b/docs/src/whatsnew/3.2.rst index 63deb5d459..723f26345e 100644 --- a/docs/src/whatsnew/3.2.rst +++ b/docs/src/whatsnew/3.2.rst @@ -126,7 +126,7 @@ v3.2.1 (11 Mar 2022) of Iris (:issue:`4523`). #. `@pp-mo`_ removed broken tooling for deriving Iris metadata translations - from `Metarelate`_. From now we intend to manage phenonemon translation + from ``Metarelate``. From now we intend to manage phenonemon translation in Iris itself. (:pull:`4484`) #. `@pp-mo`_ improved printout of various cube data component objects : @@ -402,7 +402,6 @@ v3.2.1 (11 Mar 2022) Whatsnew resources in alphabetical order: .. _NEP-29: https://numpy.org/neps/nep-0029-deprecation_policy.html -.. _Metarelate: http://www.metarelate.net/ .. _UGRID: http://ugrid-conventions.github.io/ugrid-conventions/ .. _iris-emsf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid .. _faster documentation building: https://docs.readthedocs.io/en/stable/guides/conda.html#making-builds-faster-with-mamba From c2ff2ee35483d2243961d17b173da13a655d7fda Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 23 Jun 2022 17:54:24 +0100 Subject: [PATCH 147/319] Fix stock CDL headers for unlimited time dimension. (#4827) * Fix CDL headers for tests.stock.netcdf. * Whats new entry. * Improved unlimited dimension check in stock NetCDF generation. --- docs/src/whatsnew/latest.rst | 5 +++++ .../tests/stock/file_headers/xios_2D_face_half_levels.cdl | 4 ++++ .../tests/stock/file_headers/xios_3D_face_full_levels.cdl | 4 ++++ .../tests/stock/file_headers/xios_3D_face_half_levels.cdl | 4 ++++ lib/iris/tests/stock/netcdf.py | 2 +- 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a955604c3e..1dca323820 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -226,6 +226,11 @@ This document explains the changes made to Iris for this release bin in the system PATH. (:pull:`4794`) +#. `@trexfeathers`_ and `@pp-mo`_ fixed the CDL headers for + :mod:`iris.tests.stock.netcdf` to allow generation of NetCDF-4 files with an + unlimited time dimension. + (:pull:`4827`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl b/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl index b135546f2d..ba3522b491 100644 --- a/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl @@ -55,4 +55,8 @@ variables: :name = "${DATASET_NAME}" ; // original name = "lfric_ngvat_2D_1t_face_half_levels_main_conv_rain" :Conventions = "UGRID" ; + +// data +data: + time_instant = 0 ; } diff --git a/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl b/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl index e4f32de7b7..a87e3055c9 100644 --- a/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl @@ -58,4 +58,8 @@ variables: :name = "${DATASET_NAME}" ; // original name = "lfric_ngvat_3D_1t_full_level_face_grid_main_u3" :Conventions = "UGRID" ; + +// data +data: + time_instant = 0 ; } diff --git a/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl b/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl index a193dbe451..f9c9c148dd 100644 --- a/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl @@ -58,4 +58,8 @@ variables: :name = "${DATASET_NAME}" ; // original name = "lfric_ngvat_3D_1t_half_level_face_grid_derived_theta_in_w3" :Conventions = "UGRID" ; + +// data +data: + time_instant = 0 ; } diff --git a/lib/iris/tests/stock/netcdf.py b/lib/iris/tests/stock/netcdf.py index 3e95fce95e..c5ec5ce446 100644 --- a/lib/iris/tests/stock/netcdf.py +++ b/lib/iris/tests/stock/netcdf.py @@ -103,7 +103,7 @@ def _add_standard_data(nc_path, unlimited_dim_size=0): ds = netCDF4.Dataset(nc_path, "r+") unlimited_dim_names = [ - dim for dim in ds.dimensions if ds.dimensions[dim].size == 0 + dim for dim in ds.dimensions if ds.dimensions[dim].isunlimited() ] # Data addition dependent on this assumption: assert len(unlimited_dim_names) < 2 From 0efd78d9d9a249acebe40111ee8dbad53c069717 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Mon, 27 Jun 2022 09:33:29 +0100 Subject: [PATCH 148/319] Whatsnew minor fix (#4829) * Update latest.rst * PR link * add issue link --- docs/src/whatsnew/latest.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 1dca323820..f7601a78fb 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -119,10 +119,10 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ and `@rcomer`_ (reviewer) corrected the axis on which masking is applied when an aggregator adds a trailing dimension. (:pull:`4755`) -* `@rcomer`_ and `@pp-mo`_ ensured that all methods to create or modify a +#. `@rcomer`_ and `@pp-mo`_ ensured that all methods to create or modify a :class:`iris.cube.CubeList` check that it only contains cubes. According to code comments, this was supposedly already the case, but there were several bugs - and loopholes. + and loopholes. (:issue:`1897`, :pull:`4767`) 💣 Incompatible Changes From bbe75acdacd00ab65d9fe1cbd54e58e85b541155 Mon Sep 17 00:00:00 2001 From: "iris-actions[bot]" <107775138+iris-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 10:16:11 +0100 Subject: [PATCH 149/319] Updated environment lockfiles (#4831) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py38-linux-64.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 52500e1966..3cac933c63 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -52,7 +52,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1o-h166bdaf_0.tar.bz2#6172048796b123e542945d998f5150b7 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1p-h166bdaf_0.tar.bz2#995e819f901ee0c4411e4f50d9b31a82 https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 @@ -183,7 +183,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#e https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.22.4-py38h99721a1_0.tar.bz2#fc4f99d9d9296861d09d487c7c32069f +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py38h3a7f9d9_0.tar.bz2#bde7c584b811ef5aec0dd2204e502334 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 @@ -195,14 +195,14 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.3.4-py38h578d9bd_0.tar.bz2#c58b7548636be9acefa9ba579e5590a4 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py38h578d9bd_0.tar.bz2#4dbffb6d975f26cd71fb27aa20fc4761 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_0.tar.bz2#41427ff3fd8d35e5ab1cdcec4d94ea6b https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30_0.tar.bz2#bcc387154aae535f8b4f84822621b5f7 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.0-pyhd8ed1ab_0.tar.bz2#6ef8483950258ea82fe2154c857c8775 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.3.0-hf9f4e7c_0.tar.bz2#2a9c6660562d7e3fdeda0f0159e1046d @@ -210,7 +210,7 @@ https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.2-py38h47df419_2.tar.bz2#64cca81aa93bbf0a5ab3c6d5d956b976 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee From 45ae194b34ac4d387827a6b397b2884a980c2bb8 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 27 Jun 2022 12:48:02 +0100 Subject: [PATCH 150/319] rationalise doctests gha (#4819) * rationalise doctests gha * update job name with session * add nox session as composite input * minor tweak * review action - tidy nox composite inputs --- .github/workflows/ci-docs-linkcheck.yml | 94 ------------------- .github/workflows/ci-docs-tests.yml | 8 +- .github/workflows/ci-tests.yml | 7 +- .../workflows/composite/nox-cache/action.yml | 2 +- README.md | 3 - noxfile.py | 17 +++- 6 files changed, 26 insertions(+), 105 deletions(-) delete mode 100644 .github/workflows/ci-docs-linkcheck.yml diff --git a/.github/workflows/ci-docs-linkcheck.yml b/.github/workflows/ci-docs-linkcheck.yml deleted file mode 100644 index 2fc5722d7f..0000000000 --- a/.github/workflows/ci-docs-linkcheck.yml +++ /dev/null @@ -1,94 +0,0 @@ -# reference: -# - https://github.com/actions/cache -# - https://github.com/actions/checkout -# - https://github.com/marketplace/actions/setup-miniconda - -name: ci-docs-linkcheck - -on: - push: - branches: - - "main" - - "v*x" - tags: - - "v*" - pull_request: - branches: - - "*" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - tests: - name: "linkcheck ${{ matrix.os }} ${{ matrix.python-version }}" - - runs-on: ${{ matrix.os }} - - defaults: - run: - shell: bash -l {0} - - strategy: - matrix: - os: ["ubuntu-latest"] - python-version: ["3.8"] - - env: - ENV_NAME: "ci-docs-linkcheck" - - steps: - - name: "checkout" - uses: actions/checkout@v3 - - - name: "environment configure" - env: - # Maximum cache period (in weeks) before forcing a cache refresh. - CACHE_WEEKS: 2 - run: | - echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} - echo "LOCK_FILE=requirements/ci/nox.lock/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} - - - name: "conda package cache" - uses: ./.github/workflows/composite/conda-pkg-cache - with: - cache_build: 1 - cache_period: ${{ env.CACHE_PERIOD }} - env_name: ${{ env.ENV_NAME }} - - - name: "conda install" - uses: conda-incubator/setup-miniconda@v2 - with: - miniforge-version: latest - channels: conda-forge,defaults - activate-environment: ${{ env.ENV_NAME }} - auto-update-conda: false - use-only-tar-bz2: true - - - name: "conda environment cache" - uses: ./.github/workflows/composite/conda-env-cache - with: - cache_build: 1 - cache_period: ${{ env.CACHE_PERIOD }} - env_name: ${{ env.ENV_NAME }} - install_packages: "nox pip" - - - name: "conda info" - run: | - conda info - conda list - - - name: "nox cache" - uses: ./.github/workflows/composite/nox-cache - with: - cache_build: 1 - env_name: ${{ env.ENV_NAME }} - lock_file: ${{ env.LOCK_FILE }} - - - name: "iris linkcheck" - env: - PY_VER: ${{ matrix.python-version }} - run: | - nox --session linkcheck -- --verbose diff --git a/.github/workflows/ci-docs-tests.yml b/.github/workflows/ci-docs-tests.yml index 95ea891733..15fd15fc5f 100644 --- a/.github/workflows/ci-docs-tests.yml +++ b/.github/workflows/ci-docs-tests.yml @@ -23,7 +23,7 @@ concurrency: jobs: tests: - name: "doctests ${{ matrix.os }} ${{ matrix.python-version }}" + name: "${{ matrix.session }} ${{ matrix.os }} py${{ matrix.python-version }}" runs-on: ${{ matrix.os }} @@ -32,9 +32,11 @@ jobs: shell: bash -l {0} strategy: + fail-fast: false matrix: os: ["ubuntu-latest"] python-version: ["3.8"] + session: ["doctest", "gallery", "linkcheck"] env: IRIS_TEST_DATA_VERSION: "2.12" @@ -120,8 +122,8 @@ jobs: echo "image.cmap : viridis" >> ${MPL_RC} cat ${MPL_RC} - - name: "iris doctests and gallery" + - name: "iris ${{ matrix.session }}" env: PY_VER: ${{ matrix.python-version }} run: | - nox --session doctest -- --verbose + nox --session ${{ matrix.session }} -- --verbose diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 7f3ce7ae38..e4234ede25 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -23,7 +23,7 @@ concurrency: jobs: tests: - name: "tests ${{ matrix.os }} ${{ matrix.python-version }}" + name: "${{ matrix.session }} ${{ matrix.os }} py${{ matrix.python-version }}" runs-on: ${{ matrix.os }} @@ -36,6 +36,7 @@ jobs: matrix: os: ["ubuntu-latest"] python-version: ["3.8"] + session: ["tests"] env: IRIS_TEST_DATA_VERSION: "2.12" @@ -115,8 +116,8 @@ jobs: echo "doc_dir = ${GITHUB_WORKSPACE}/docs" >> ${SITE_CFG} cat ${SITE_CFG} - - name: "iris tests" + - name: "iris ${{ matrix.session }}" env: PY_VER: ${{ matrix.python-version }} run: | - nox --session tests -- --verbose + nox --session ${{ matrix.session }} -- --verbose diff --git a/.github/workflows/composite/nox-cache/action.yml b/.github/workflows/composite/nox-cache/action.yml index 9d92ad7226..468dd22d81 100644 --- a/.github/workflows/composite/nox-cache/action.yml +++ b/.github/workflows/composite/nox-cache/action.yml @@ -19,4 +19,4 @@ runs: - uses: actions/cache@v3 with: path: ${{ github.workspace }}/.nox - key: ${{ runner.os }}-nox-${{ inputs.env_name }}-py${{ matrix.python-version }}-b${{ inputs.cache_build }}-${{ hashFiles(inputs.lock_file) }} + key: ${{ runner.os }}-nox-${{ inputs.env_name }}-s${{ matrix.session }}-py${{ matrix.python-version }}-b${{ inputs.cache_build }}-${{ hashFiles(inputs.lock_file) }} diff --git a/README.md b/README.md index 686a4558c6..1217cd9b38 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,6 @@ ci-docs-tests - -ci-docs-linkcheck Documentation Status diff --git a/noxfile.py b/noxfile.py index 5838ea5fab..2b1df8fb00 100755 --- a/noxfile.py +++ b/noxfile.py @@ -220,7 +220,22 @@ def doctest(session: nox.sessions.Session): "doctest", external=True, ) - session.cd("..") + + +@nox.session(python=_PY_VERSION_DOCSBUILD, venv_backend="conda") +def gallery(session: nox.sessions.Session): + """ + Perform iris gallery doc-tests. + + Parameters + ---------- + session: object + A `nox.sessions.Session` object. + + """ + prepare_venv(session) + session.install("--no-deps", "--editable", ".") + session.env.update(ENV) session.run( "python", "-m", From be4fdc3741637d407efb01fca530b6f032187adb Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 27 Jun 2022 18:41:27 +0100 Subject: [PATCH 151/319] Remove pin, update hashes, improve script (#4826) * Remove pin, update hashes, improve script * What's new * Lockfile and whatsnew * Shareable urls * Later lockfile * Shareable... * Check for consistency before fiddling * Updates for new test data (idiff'ed) * Keep lockstep between imagerepo and data * idiff always runs after imagerepo.json has been updated anyway * Gallery test updates (also idiff'd) * Update test data version --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci-docs-tests.yml | 2 +- .github/workflows/ci-tests.yml | 2 +- docs/src/whatsnew/latest.rst | 2 + lib/iris/tests/graphics/idiff.py | 13 +- lib/iris/tests/graphics/recreate_imagerepo.py | 31 ++- lib/iris/tests/results/imagerepo.json | 182 +++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 17 +- requirements/ci/py38.yml | 1 - 9 files changed, 129 insertions(+), 123 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b1539860ed..df4a4fc9da 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -15,7 +15,7 @@ jobs: env: IRIS_TEST_DATA_LOC_PATH: benchmarks IRIS_TEST_DATA_PATH: benchmarks/iris-test-data - IRIS_TEST_DATA_VERSION: "2.12" + IRIS_TEST_DATA_VERSION: "2.13" # Lets us manually bump the cache to rebuild ENV_CACHE_BUILD: "0" TEST_DATA_CACHE_BUILD: "2" diff --git a/.github/workflows/ci-docs-tests.yml b/.github/workflows/ci-docs-tests.yml index 15fd15fc5f..9e200c124e 100644 --- a/.github/workflows/ci-docs-tests.yml +++ b/.github/workflows/ci-docs-tests.yml @@ -39,7 +39,7 @@ jobs: session: ["doctest", "gallery", "linkcheck"] env: - IRIS_TEST_DATA_VERSION: "2.12" + IRIS_TEST_DATA_VERSION: "2.13" ENV_NAME: "ci-docs-tests" steps: diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index e4234ede25..31cccfb0bb 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -39,7 +39,7 @@ jobs: session: ["tests"] env: - IRIS_TEST_DATA_VERSION: "2.12" + IRIS_TEST_DATA_VERSION: "2.13" ENV_NAME: "ci-tests" steps: diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f7601a78fb..a16ce598cc 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -167,6 +167,8 @@ This document explains the changes made to Iris for this release we no longer use the deprecated :class:`nc_time_axis.CalendarDateTime` when plotting against time coordinates. (:pull:`4584`) +#. `@wjbenfold`_ and `@bjlittle`_ (reviewer) unpinned ``pillow``. (:pull:`4826`) + 📚 Documentation ================ diff --git a/lib/iris/tests/graphics/idiff.py b/lib/iris/tests/graphics/idiff.py index 2391111e1d..a355f2cf82 100755 --- a/lib/iris/tests/graphics/idiff.py +++ b/lib/iris/tests/graphics/idiff.py @@ -128,18 +128,6 @@ def step_over_diffs(result_dir, display=True): reference_image_dir = Path(iris.tests.get_data_path("images")) repo = graphics.read_repo_json() - repo_from_baseline_images = graphics.generate_repo_from_baselines( - reference_image_dir - ) - - if not graphics.repos_equal(repo, repo_from_baseline_images): - msg = ( - f"The hashes from {reference_image_dir} and the contents of " - f"{graphics.IMAGE_REPO_PATH} are inconsistent. \nConsider " - f"recreating {graphics.IMAGE_REPO_PATH.name} using " - f"{Path(graphics.__file__).parent}/recreate_imagerepo.py" - ) - raise AssertionError(msg) # Filter out all non-test result image files. results = [] @@ -215,5 +203,6 @@ def step_over_diffs(result_dir, display=True): if not result_dir.is_dir(): emsg = f"Invalid results directory: {result_dir}" raise ValueError(emsg) + for args in step_over_diffs(result_dir): diff_viewer(*args) diff --git a/lib/iris/tests/graphics/recreate_imagerepo.py b/lib/iris/tests/graphics/recreate_imagerepo.py index 40a9872220..756e48688d 100755 --- a/lib/iris/tests/graphics/recreate_imagerepo.py +++ b/lib/iris/tests/graphics/recreate_imagerepo.py @@ -12,29 +12,44 @@ import argparse from pathlib import Path +from imagehash import hex_to_hash + import iris.tests import iris.tests.graphics as graphics def update_json(baseline_image_dir: Path, dry_run: bool = False): - old_repo = graphics.read_repo_json() - new_repo = graphics.generate_repo_from_baselines(baseline_image_dir) + repo = graphics.read_repo_json() + suggested_repo = graphics.generate_repo_from_baselines(baseline_image_dir) - if graphics.repos_equal(old_repo, new_repo): + if graphics.repos_equal(repo, suggested_repo): msg = ( f"No change in contents of {graphics.IMAGE_REPO_PATH} based on " f"{baseline_image_dir}" ) print(msg) else: - for key in set(old_repo.keys()) | set(new_repo.keys()): - old_val = old_repo.get(key, None) - new_val = new_repo.get(key, None) - if str(old_val) != str(new_val): + for key in set(repo.keys()) | set(suggested_repo.keys()): + old_val = repo.get(key, None) + new_val = suggested_repo.get(key, None) + if old_val is None: + repo[key] = suggested_repo[key] + print(key) + print(f"\t{old_val} -> {new_val}") + elif new_val is None: + del repo[key] print(key) print(f"\t{old_val} -> {new_val}") + else: + difference = hex_to_hash(str(old_val)) - hex_to_hash( + str(new_val) + ) + if difference > 0: + print(key) + print(f"\t{old_val} -> {new_val} ({difference})") + repo[key] = suggested_repo[key] if not dry_run: - graphics.write_repo_json(new_repo) + graphics.write_repo_json(repo) if __name__ == "__main__": diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index aeabf47c4d..5ae8046c5b 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -1,6 +1,6 @@ { "gallery_tests.test_plot_COP_1d.TestCOP1DPlot.test_plot_COP_1d.0": "aefec91c3601249cc9b3336dc4c8cdb31a64c6d997b3c0eccb5932d285e42f33", - "gallery_tests.test_plot_COP_maps.TestCOPMaps.test_plot_cop_maps.0": "ea9130db95668524913d6ac168991f0d956e917ec76396b96a853dcf94696935", + "gallery_tests.test_plot_COP_maps.TestCOPMaps.test_plot_cop_maps.0": "ea9130db95668524913e6ac168991f0d956e917ec76396b96a853dcf94696935", "gallery_tests.test_plot_SOI_filtering.TestSOIFiltering.test_plot_soi_filtering.0": "fa56f295c5e0694a3c17a58d95e8da536233da99984c5af4c6739b4a9a444eb4", "gallery_tests.test_plot_TEC.TestTEC.test_plot_TEC.0": "e5a761b69a589a4bc46f9e48c65c6631ce61d1ce3982c13739b33193c0ee3f8c", "gallery_tests.test_plot_anomaly_log_colouring.TestAnomalyLogColouring.test_plot_anomaly_log_colouring.0": "ec4464e384a39b13931a9b1c85696da968d5e6e63e26847bdbd399938d3c5a4c", @@ -8,37 +8,37 @@ "gallery_tests.test_plot_atlantic_profiles.TestAtlanticProfiles.test_plot_atlantic_profiles.1": "eeea64dd6ea8cd99991d1322b3741e2684571cd89995b3131f32a4765ee2a1cc", "gallery_tests.test_plot_coriolis.TestCoriolisPlot.test_plot_coriolis.0": "e68665de9a699659c1fe99a5896965966996c46e3e19c1da3a652669c51e1a26", "gallery_tests.test_plot_cross_section.TestCrossSection.test_plot_cross_section.0": "ea91b17b9562e4d1609f5a05856e4ca45a52957e5ea5f13b1bca9dc0b17b1ac1", - "gallery_tests.test_plot_cross_section.TestCrossSection.test_plot_cross_section.1": "ea9521fb956a394068931e9be07e4aa5856cc47e4a91957a1ba55bb5b17a3b81", + "gallery_tests.test_plot_cross_section.TestCrossSection.test_plot_cross_section.1": "ea9521fb956a394068931e93e07e4aa5856cc47e4a91957b1ba55bb5b17a3b81", "gallery_tests.test_plot_custom_aggregation.TestCustomAggregation.test_plot_custom_aggregation.0": "ee816f81917e907eb03ec73f856f7ac198d070186e90811f1be33ee1a57a6e18", - "gallery_tests.test_plot_custom_file_loading.TestCustomFileLoading.test_plot_custom_file_loading.0": "faa1cb47845e34bc912797436cccc8343f11359b73523746c48c72d9d9b34da5", + "gallery_tests.test_plot_custom_file_loading.TestCustomFileLoading.test_plot_custom_file_loading.0": "fa81cb47845e34bc932797436cccc8343f11359b73523746c48c72d9d9b34da5", "gallery_tests.test_plot_deriving_phenomena.TestDerivingPhenomena.test_plot_deriving_phenomena.0": "ec97681793689768943c97e8926669d186e8c33f6c99c32e6b936c83d33e2c98", - "gallery_tests.test_plot_global_map.TestGlobalMap.test_plot_global_map.0": "fa997b958466846ed13e87467a997a898d66d17e2cc9906684696f99d3162f81", + "gallery_tests.test_plot_global_map.TestGlobalMap.test_plot_global_map.0": "fb997b958466846ed13e87467a997a898d66d17e2cc9906684696f99d3162e81", "gallery_tests.test_plot_hovmoller.TestGlobalMap.test_plot_hovmoller.0": "eeb46cb4934b934bc07e974bc14b38949943c0fe3e94c17f6ea46cb4c07b3f00", - "gallery_tests.test_plot_inset.TestInsetPlot.test_plot_inset.0": "ebff6992b50096ad9267dac4d64094b294924cdbc95d4b699d29952dcda46e94", + "gallery_tests.test_plot_inset.TestInsetPlot.test_plot_inset.0": "ebff6992b50096ad9267dac4d640949294924cdbc95d4b699d29952dcda46ed4", "gallery_tests.test_plot_lagged_ensemble.TestLaggedEnsemble.test_plot_lagged_ensemble.0": "bbbb31b1c44e64e4b1579b5b917133cecc61f146c414668eb1119b1bb197ce34", "gallery_tests.test_plot_lagged_ensemble.TestLaggedEnsemble.test_plot_lagged_ensemble.1": "aafec5e9e5e03e099a07e0f86542db879438261ec3b13ce78d8dc65a92d83d89", "gallery_tests.test_plot_lineplot_with_legend.TestLineplotWithLegend.test_plot_lineplot_with_legend.0": "eafd9e12a5a061e9925ec716de489e9685078ec981b229e70ddb79219cc3768d", - "gallery_tests.test_plot_load_nemo.TestLoadNemo.test_plot_load_nemo.0": "a3ff34e87f0049496d17c4d9c04fc225d256971392d39f1696df0f16cec00f36", + "gallery_tests.test_plot_load_nemo.TestLoadNemo.test_plot_load_nemo.0": "a3ff34e87f0049496d17c4d9c04fc225d256971392db9f1696df0f16cec00736", "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.0": "bb11721a87cce5e4cce79e81d19b3b5e1e1cd3783168e07835853485e65e2e1e", "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.1": "e58661969e799659c1f719a6c867359a1996c0773649c09c3e612679c07b3f66", "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.2": "a58660ce9e739b31c93d1c89c8df33863783e23b3f11c07f2664366cc8ee3cc1", "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.3": "be817a8784dea56cec79817a919e338437a5c1e73fa16c726c4a3e816a1c6b1c", "gallery_tests.test_plot_polar_stereo.TestPolarStereo.test_plot_polar_stereo.0": "ba1e615ec7e097ad961f9cb190f038e091c2c1e73f07c11f6f386b3cc1793e01", "gallery_tests.test_plot_polynomial_fit.TestPolynomialFit.test_plot_polynomial_fit.0": "aeffcb34d244348be5a2c96c3a4fc6d0c4b69f2d87294ccb9f1a125684cd7c11", - "gallery_tests.test_plot_projections_and_annotations.TestProjectionsAndAnnotations.test_plot_projections_and_annotations.0": "fa854f19851a30e4cc76cd0bb0f932dca7c665b1c92ccb4b4ed19e9c3721b5c8", - "gallery_tests.test_plot_projections_and_annotations.TestProjectionsAndAnnotations.test_plot_projections_and_annotations.1": "e3856d999c389662734331afcd2d5a7184dba492b9b69b64d26dc29974b185b2", - "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.0": "ee46607e97a19781c0df1f81d0bb3e241f20c16f3fc0c1fe39263d33d06f3e80", + "gallery_tests.test_plot_projections_and_annotations.TestProjectionsAndAnnotations.test_plot_projections_and_annotations.0": "fa854f19851a30e4cc76cd0bb0f932dca7c665b0c93ccb4b4ed19e9c3721b5c8", + "gallery_tests.test_plot_projections_and_annotations.TestProjectionsAndAnnotations.test_plot_projections_and_annotations.1": "e3856d999c389662734331afcd2d5a7184dba592b9b69b64d26dc29954b185b2", + "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.0": "ee46607e97a19781c0de1f81d0bb3e241f20c16f3fc0c1fe3d263d33d06f3e80", "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.1": "ea57685f95a886a1c0de9da090be3e2697e1c0ff3f00c17e6b266c17c07f3f00", - "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.2": "ea57685f95a886a1c0de9da090be3e2497e1c0ef3f01c17e6b366c17c07b3f01", + "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.2": "ea57685f95a886a1c0de9da090be3e2497e1c0ff3f01c17e6b366c17c07b3f00", "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.3": "fa8172c6857ecd38cb3392ce36c564311931d85ec64e9787719a39993c316e66", "gallery_tests.test_plot_wind_barbs.TestWindBarbs.test_wind_barbs.0": "e9e161e996169316c1fe9e96c29e36739e13c07c3d61c07f39813929c07f3f01", - "gallery_tests.test_plot_wind_speed.TestWindSpeed.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e13c06c3d61c07f39a139e1c07f3f01", - "gallery_tests.test_plot_wind_speed.TestWindSpeed.test_plot_wind_speed.1": "e9e960e996169306c1ee9e96c29e36739653c06c3d61c07f3da139e1c07f3f01", - "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e7e81857e7e817e81c07e7e81c17e7a81817e817e8c2a", - "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e6a85817e7a81857e7e817e81957a7e81817e7a81817e817e843e", - "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.2": "be81857ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2b", + "gallery_tests.test_plot_wind_speed.TestWindSpeed.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e03c06c3d61c07f3da139e1c07f3f01", + "gallery_tests.test_plot_wind_speed.TestWindSpeed.test_plot_wind_speed.1": "e9e960e996169306c1ee9f96c29e36739653c06c3d61c07f39a139e1c07f3f01", + "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e", + "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e", + "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f", "iris.tests.integration.plot.test_plot_2d_coords.Test.test_2d_coord_bounds_northpolarstereo.0": "e59661969e699659c0f719a6c967339a1992c07f3649c09c3f612669c07b3f66", - "iris.tests.integration.plot.test_plot_2d_coords.Test.test_2d_coord_bounds_platecarree.0": "ee856299954a1da699b6915ec25b6e419729c42c3f84bd8fa7d262d1d1dac076", + "iris.tests.integration.plot.test_plot_2d_coords.Test.test_2d_coord_bounds_platecarree.0": "ee856299954a1da699b6915ec25b6e419729c42c3f84bd9fa6d262d1d1dac076", "iris.tests.integration.plot.test_plot_2d_coords.Test2dContour.test_2d_coords_contour.0": "b4b2643ecb05cb43b0f23d80c53c4e1d3e5990eb1f81c19f2f983cb1c4ff3e42", "iris.tests.integration.plot.test_vector_plots.TestBarbs.test_2d_plain_latlon.0": "eb036726c47c9273918e6e2c6f216336787590eb969a165890ee6c676925b3b3", "iris.tests.integration.plot.test_vector_plots.TestBarbs.test_2d_plain_latlon_on_polar_map.0": "e66d673c999031cd6667663398dc332c676364e798959336636660d933998666", @@ -53,22 +53,22 @@ "iris.tests.test_analysis.TestProject.test_cartopy_projection.0": "9e1952c9c165b4fc668a9d47c1461d7a60fb2e853eb426bd62fd229c9f04c16d", "iris.tests.test_mapping.TestBasic.test_contourf.0": "e97a346c9685cb899685c9c39695c79396ec634969ce2c74697a3864697b3c8c", "iris.tests.test_mapping.TestBasic.test_pcolor.0": "e97a347c96858b8d9685c9c39696c393966c634969ce3c64697a3864697b3c9c", - "iris.tests.test_mapping.TestBasic.test_unmappable.0": "ea853e48957ac1df957ac8be852bc1b1944e7a9878e03f4c6a253e6c7a912dc2", + "iris.tests.test_mapping.TestBasic.test_unmappable.0": "ea853e48957ac1df957ac8be852bc1b1944e7a9a78e02f4c6a253e6c7a912dc2", "iris.tests.test_mapping.TestBoundedCube.test_grid.0": "fa81857e857e7a81857e7a817a81817e7a81857e857e7a81857e7a817a81857e", - "iris.tests.test_mapping.TestBoundedCube.test_pcolormesh.0": "fa81c17a857e1ea5857e734a7a81cd257e8484da857e3b29817a68f47a81c799", + "iris.tests.test_mapping.TestBoundedCube.test_pcolormesh.0": "fa81c17e857e1ea1857e634a7a81cd257e8484da857e3b29817e68f47a81c799", "iris.tests.test_mapping.TestLimitedAreaCube.test_grid.0": "fa81857e857e7a81857e7a817a817a817a81817e7a81857e857e857e857e7a81", "iris.tests.test_mapping.TestLimitedAreaCube.test_outline.0": "fa81857e857e3e81857e7a857a817e817a81857a7a81817e857e857a857e7a81", - "iris.tests.test_mapping.TestLimitedAreaCube.test_pcolormesh.0": "ea813b49957ec4b7917e3f60266978d97a9562366e81954a914ec6cc957a0f98", - "iris.tests.test_mapping.TestLimitedAreaCube.test_scatter.0": "ea05bd3a91eac2d984983d346b2473477acf69ad1d3296d8c696e126c1ab1e71", + "iris.tests.test_mapping.TestLimitedAreaCube.test_pcolormesh.0": "ea813949957ec4b7917e3f60266978d97a9562376e81954a914ec6cc957a0f98", + "iris.tests.test_mapping.TestLimitedAreaCube.test_scatter.0": "ea05bd3e91eac2d984983d346b2473477acf69ad1d3296d8c696e126c1ab1a71", "iris.tests.test_mapping.TestLowLevel.test_keywords.0": "be21a71bc1de58e43a63a71b3e016061c1fe9b8c3e01a473847e5b94d1fb9ac3", - "iris.tests.test_mapping.TestLowLevel.test_keywords.1": "fa819097857e6560957e7bcc7a819c316e81951e857e62c281fe79a17aa19637", - "iris.tests.test_mapping.TestLowLevel.test_params.0": "fa8190be857e6739917a7bc47a8594337bb1911c857e6ec3913279007e819637", + "iris.tests.test_mapping.TestLowLevel.test_keywords.1": "fa819897857e6530957e7bcc7a819c316ea1951e857e62c2857e79a17a819633", + "iris.tests.test_mapping.TestLowLevel.test_params.0": "fa8190be857e6739913a7bc47a8594337bb1911c857e6ec3913279807e819637", "iris.tests.test_mapping.TestLowLevel.test_params.1": "be21a71bc1de58e43a63a71b3e016061c1fe9b8c3e01a473847e5b94d1fb9ac3", - "iris.tests.test_mapping.TestLowLevel.test_params.2": "fa81909f857e6520957e5bcc7a8194716e31851e857e6ac281fe3f817a81963f", - "iris.tests.test_mapping.TestLowLevel.test_simple.0": "faa0e558855fd9e7857a1ab16a85a51d36a1e55a854e58a5c13837096e8fe17a", + "iris.tests.test_mapping.TestLowLevel.test_params.2": "fa81909f857e6520957e7acc7a8194716e31851e857e6ac281fe3ba17a81963f", + "iris.tests.test_mapping.TestLowLevel.test_simple.0": "faa0e558855f9de7857a1ab16a85a51d36a1e55a854e58a5c13837096e8fe17a", "iris.tests.test_mapping.TestMappingSubRegion.test_simple.0": "b9913d90c66eca6ec66ec2f3689195aecf5b2f00392cb3496495e21da4db6c92", - "iris.tests.test_mapping.TestUnmappable.test_simple.0": "fa81b54a817eca35817ec701857e3e64943e7bb41b846f996e817e006ee1b19b", - "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord.0": "8bfec2577e01b5a5ed013b4ac4521c94817d4e4d91ff63369c6d61991e3278cc", + "iris.tests.test_mapping.TestUnmappable.test_simple.0": "fa81b54a817eca37817ec701857e3e64943e7bb41b806f996e817e006ee1b19b", + "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord.0": "8bfec2577e01a5a5ed013b4ac4521c94817d4e6d91ff63369c6d61991e3278cc", "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord_coord.0": "8fff941e7e01e1c2f801c878a41e5b0d85cf36e1837e2d9992c62f21769e6a4d", "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord_coord_map.0": "bbe0c214cd979dc3b05e4b68db0771b48698961b7962d2446e8ca5bb36716c6e", "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord_cube.0": "8ff897066a01f0f2f818ee1eb007ca41853e3b81c57e36a991fe2ca9725e29ed", @@ -76,7 +76,7 @@ "iris.tests.test_plot.Test1dPlotMultiArgs.test_cube_coord.0": "8fffc1dc7e019c70f001b70ee4386de1814e7938837b6a7f84d07c9f15b02f21", "iris.tests.test_plot.Test1dPlotMultiArgs.test_cube_cube.0": "8ff8c0567a01b296e4019d2ff10b464bd4da6391943678e5879f7e3903e63f1c", "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord.0": "83fec2777e002427e801bb4ae65a1c94813dcec999db4bbc9ccd79991f3238cc", - "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord_coord.0": "83ff9d9f7e01e1c2b001c8f8f63e1b1d81cf36e1837e258982c66f215c9a6a6c", + "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord_coord.0": "83ff9d9f7e01e1c2b001c8f8f63e1b1d81cf36e1837e258982ce6f215c9a626c", "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord_coord_map.0": "bbe0c214cd979dc3b05e4b68db0771b48698961b7962d2446e8ca5bb36716c6e", "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord_cube.0": "87ffb79e7f0060d8303fcd1eb007d801c52699e18d769e2199e60ce1da5629ed", "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_cube.0": "a3ffc1de7e009c7030019786f438cde3810fd93c9b734a778ce47c9799b02731", @@ -94,63 +94,63 @@ "iris.tests.test_plot.Test1dScatter.test_cube_cube.0": "edb23c529649c78de38773e538650c729e92279be12de1edc4f246b2139c3b01", "iris.tests.test_plot.Test2dPoints.test_circular_changes.0": "fa81c57a857e93bd9b193e436ec4ccb03b01c14a857e3e34911f3b816e81c57b", "iris.tests.test_plot.TestAttributePositive.test_1d_positive_down.0": "a7fe781b708487c360079e3bb4789869816bdb64c76b4a3cce7b4e749a6130c5", - "iris.tests.test_plot.TestAttributePositive.test_1d_positive_up.0": "a7ff958b7a00b09c661761c9907fcb0d9163ce7895289a618f381bffccf97200", + "iris.tests.test_plot.TestAttributePositive.test_1d_positive_up.0": "a7ff958b7a00b09c6617e1c1907fcb0d9163ce7895289a618f381bffccf97200", "iris.tests.test_plot.TestAttributePositive.test_2d_positive_down.0": "fb966ba6846194dbd01f3665c0e4399a3f1bc2653f90c99e2f613e64c01e3f81", "iris.tests.test_plot.TestAttributePositive.test_2d_positive_up.0": "ebc06be1941e941ec07f941f907f6fa0950fc07e6f80c07f6b806be1c07f3f80", "iris.tests.test_plot.TestContour.test_tx.0": "eeece0173c07951fbd038748914964e8c14e72e9c1531ee1cc746bb293973ecd", "iris.tests.test_plot.TestContour.test_ty.0": "ebfa8553fc01b15af4055a069546caa5954b7e9bc0f97d2cc2d62d360b362b49", - "iris.tests.test_plot.TestContour.test_tz.0": "8bfe805ffc00857ef0007a01d4027e80815fd56a83ff7a8085ff3aaa03ff6af5", - "iris.tests.test_plot.TestContour.test_yx.0": "e85e36cb95b199999e654d3694b26c78c7396329958434c2cacb6c6d69ce9392", - "iris.tests.test_plot.TestContour.test_zx.0": "affe8057fc00855cf8007e01d0027e808557d5ea815f7ea0817f2fea815f2bff", + "iris.tests.test_plot.TestContour.test_tz.0": "8bfe805ffc00857ef0007e01d4027e80815fd56a81ff7a8085ff3aaa03ff6af5", + "iris.tests.test_plot.TestContour.test_yx.0": "e85e36cb95b199998e6d4d3694b26c78c7396329958434c2cacb6c6d69ce9392", + "iris.tests.test_plot.TestContour.test_zx.0": "affe8057fc00855cf8007e00d0027e808557d5ea815f7ea0817f2fea817f2bff", "iris.tests.test_plot.TestContour.test_zy.0": "abff817ff801857afc017a80d4027e00855ec42a81fe7a8185fe6a8f05fe2abf", - "iris.tests.test_plot.TestContourf.test_tx.0": "ea857a81957ac57e957a857a957a958ac5723b0d6ac56b833e856e606a923e90", - "iris.tests.test_plot.TestContourf.test_ty.0": "ea851f00957ac0f7957ac07f957a628d815e7b126ab13e816a953ae46a859ed3", - "iris.tests.test_plot.TestContourf.test_tz.0": "fa81857e857e7a81857e7a81857e7a81857e7a806a85857a7a85857e7a85817e", - "iris.tests.test_plot.TestContourf.test_yx.0": "e97a3c7e968597b19685c9c696a7c79491c16e59691a387f6978396e68683184", + "iris.tests.test_plot.TestContourf.test_tx.0": "ea857a81957a857e957a857a857a958ac5723b0d7ac56b833e856e606a933e90", + "iris.tests.test_plot.TestContourf.test_ty.0": "ea851f00957ac0f3957ac07f957a628d815e7b926ab13e816a953aac6a859ed3", + "iris.tests.test_plot.TestContourf.test_tz.0": "fa81857e857e7a81857a7a81857e7a81857e7a806a95857a7a85857e7a85817e", + "iris.tests.test_plot.TestContourf.test_yx.0": "e97a386e968597b19685c9c296a7c79493c16e59691a387f6978396e6c6a3184", "iris.tests.test_plot.TestContourf.test_zx.0": "fa81817e857e7a81857a7a81957a6e81917a6caa3a85c57a3a8585fa6a8591fe", "iris.tests.test_plot.TestContourf.test_zy.0": "fa81817e857e7a81857e7a81817a7e81817a668f7a91857e7a81857e7a85817e", "iris.tests.test_plot.TestHybridHeight.test_bounds.0": "ee856aa5957a955ac0bf954bc17e3b819548c07f3e81c07e2ec46ea4c07f3e84", "iris.tests.test_plot.TestHybridHeight.test_bounds.1": "bf813e85c07ec57ec17e9073c07e3f81856ec17a3f80c0fe3e813f84c2733e80", "iris.tests.test_plot.TestHybridHeight.test_bounds.2": "ee856aa5957a955ac0bf954bc17e3b819548c07f3e81c07e2ec46ea4c07f3e84", - "iris.tests.test_plot.TestHybridHeight.test_orography.0": "fa817a91917a957ac4ff248cc07f6ea466a5c03f3b81c17f1b321b01935b3fc0", + "iris.tests.test_plot.TestHybridHeight.test_orography.0": "fa817a91917a957ac4ff240cc07f6ea466a5c03f3b81c17f1b321b01d35b3fc0", "iris.tests.test_plot.TestHybridHeight.test_orography.1": "ea07695f95e0d2b4c09d95e0956a3da99294c2be3e85c07f3fa92b05c15e3f42", - "iris.tests.test_plot.TestHybridHeight.test_points.0": "fe857b91917a847ec0bd3f01c47e6ca43b11915a3ea4db3b1b4a84c4c03f3fc1", + "iris.tests.test_plot.TestHybridHeight.test_points.0": "fe857b91917a847ec4bd3f01c47c6ca43b11915a3ea4db3b1b4a84c4c03f3fc1", "iris.tests.test_plot.TestHybridHeight.test_points.1": "be813a81c17ec57ec17e952ac07f3f808556c17e3f80c07f3e813f80c27e3f81", - "iris.tests.test_plot.TestHybridHeight.test_points.2": "fe856a85957a955ac03f956ac17f3f809552c07f3e81c07e3e806e85c07e3f84", - "iris.tests.test_plot.TestHybridHeight.test_points.3": "fe857b91917a847ec0bd3f01c47e6ca43b11915a3ea4db3b1b4a84c4c03f3fc1", + "iris.tests.test_plot.TestHybridHeight.test_points.2": "fe856a85957a955ac03f956ac17f3f809552c07f3e81c07e3e807e85c07e3f80", + "iris.tests.test_plot.TestHybridHeight.test_points.3": "fe857b91917a847ec4bd3f01c47c6ca43b11915a3ea4db3b1b4a84c4c03f3fc1", "iris.tests.test_plot.TestHybridHeight.test_points.4": "b878387e978ec2f0c0f09f83878f3f81c070c0fe78d0c1763fa13856d03e3f0f", - "iris.tests.test_plot.TestMissingCS.test_missing_cs.0": "fa816ac1857e853cc17f957ac15f3e849486c8f43e81c13b3f813e91c07e3f46", - "iris.tests.test_plot.TestMissingCoord.test_no_u.0": "ea856a95955a954ac17f954a807e3f48951ac07e3e81c0ff7ea16a81c0ff3f81", - "iris.tests.test_plot.TestMissingCoord.test_no_u.1": "ea956ab5954a954ac17e954a817f2f60950ac07f3e80c07f7a856aa5c2ff3f80", + "iris.tests.test_plot.TestMissingCS.test_missing_cs.0": "fa816ac1857e853cc17e957ac15f3e8494c6c8f43e81c13b3f813e91c07e3f46", + "iris.tests.test_plot.TestMissingCoord.test_no_u.0": "ea856a95955a954ac17f954a807e3f48951ac07e3f81c0ff7ea16a81c0bf3f81", + "iris.tests.test_plot.TestMissingCoord.test_no_u.1": "ea956ab5954a954ac17e9542817f2f60950ac07f3e80c0ff7a856aa5c2ff3f80", "iris.tests.test_plot.TestMissingCoord.test_no_v.0": "fa816a85957a857ac17e954ac17e1fa2950bc07e3e81c07f3e807a85c17f3f81", "iris.tests.test_plot.TestMissingCoord.test_no_v.1": "fa856a85957a857ac17e954ac17e9d02954ac07e3e81c07f3e857a85c2fd3f80", - "iris.tests.test_plot.TestMissingCoord.test_none.0": "fa816a85957a857ac17e954ac17e3fa29506c07e3e81c07f3e807a84c1ff3f81", - "iris.tests.test_plot.TestMissingCoord.test_none.1": "fa856a85957a957ac17e954ac17a1f06954ac07e3e81c07f3e817a85c0ff3f80", + "iris.tests.test_plot.TestMissingCoord.test_none.0": "fa816a85957a857ac17e954ac17e3fa2950ac07e3e80c07f3e807a85c1ff3f81", + "iris.tests.test_plot.TestMissingCoord.test_none.1": "fa856a85957a957ac17e954ac17a1f06954ac07f3e81c07f3e817a85c0fd3f80", "iris.tests.test_plot.TestPcolor.test_tx.0": "ea817a81957e857e957e953e957e857e857e6aa06a816ac16a017a816a9585fa", - "iris.tests.test_plot.TestPcolor.test_ty.0": "ea953f83954ac2bc956ac07e956a3509c0de61796ab57a816a856ad16ab590fb", - "iris.tests.test_plot.TestPcolor.test_tz.0": "fa81857e857a7a84857a7a85857e7a813a2f7a817a85857a7a85857a7a85857a", - "iris.tests.test_plot.TestPcolor.test_yx.0": "e97a387e968596319697c3c19284a62c93a560c36933393a6c7e793b6c6b31cd", - "iris.tests.test_plot.TestPcolor.test_zx.0": "fa81857e857a7e01857e7a81857e7a81e8177a816a8585fa7a85857e7a81857e", - "iris.tests.test_plot.TestPcolor.test_zy.0": "fa81857e857e7e80857e7a81857e7a812d557a817a85857e7a81857e7a80857e", - "iris.tests.test_plot.TestPcolorNoBounds.test_tx.0": "ea858782957a703f957a3878957a7a65957a6bc06ae76f806ad50fd06a859c50", - "iris.tests.test_plot.TestPcolorNoBounds.test_ty.0": "ea85857a857e7e81957a7a81957a6a85857acac6c1fb6aa67a81956e6a81b506", - "iris.tests.test_plot.TestPcolorNoBounds.test_tz.0": "fa817e81857e857a857e7a81857e6a85817b81e63a913e857e81c17e7a81956e", - "iris.tests.test_plot.TestPcolorNoBounds.test_yx.0": "e96ac78796953c4c9685383996c538e69692637263696b49693ac796693ac71b", - "iris.tests.test_plot.TestPcolorNoBounds.test_zx.0": "fa817a81857e857e857e7a81857e6a81c17f95786aa77a807e81c17c7e819558", + "iris.tests.test_plot.TestPcolor.test_ty.0": "ea953f83954ac2fc956ac07e956a3509c0de61796ab57a916a854a916ab590fb", + "iris.tests.test_plot.TestPcolor.test_tz.0": "fa81857e857a7a84857a7a81857e7a813e2f7a817a85857a7a85857a7a85857a", + "iris.tests.test_plot.TestPcolor.test_yx.0": "e97a387e968596319697c3c19284a62c93ad60c36933393a6c7a793b6c6b31cd", + "iris.tests.test_plot.TestPcolor.test_zx.0": "fa81857e857a6e05857e7a81857e7a81e0577a816a8585fa7a85857e7a81857e", + "iris.tests.test_plot.TestPcolor.test_zy.0": "fa81857e857e7e80857e7a81857e7a812d577a816a85857e7a81857e7a80857e", + "iris.tests.test_plot.TestPcolorNoBounds.test_tx.0": "ea858782957a603f957a3878957a7a7d957a6bc06ae56f806ad50fd06a859c50", + "iris.tests.test_plot.TestPcolorNoBounds.test_ty.0": "ea85857a857e7e81957a7a81957a6a85857acaa6c1fb6aa67a81956e6a81b506", + "iris.tests.test_plot.TestPcolorNoBounds.test_tz.0": "fa817e81857e857a857e7a81857e6a85817b81e63e813e857e81c17e7a81956e", + "iris.tests.test_plot.TestPcolorNoBounds.test_yx.0": "e96ac78796953c4c9685383996c538e69792637063696b49693ac796693ac71b", + "iris.tests.test_plot.TestPcolorNoBounds.test_zx.0": "fa817a81857e857e857e7a81857e6a84c17f95786aa77a807e81c17c7e819558", "iris.tests.test_plot.TestPcolorNoBounds.test_zy.0": "fa817a80857e857e857e7a81817e3e81817e857f6aa07a857e80c17f7e80c15f", "iris.tests.test_plot.TestPcolormesh.test_tx.0": "ea817a81957e857e957e953e957e857e857e6aa06a816ac16a017a816a9585fa", - "iris.tests.test_plot.TestPcolormesh.test_ty.0": "ea953f83954ac2bc956ac07e956a3509c0de61796ab57a916a856a916ab590fb", - "iris.tests.test_plot.TestPcolormesh.test_tz.0": "fa81857e857a7a84857a7a85857e7a813a2f7a817a85857a7a85857a7a85857a", + "iris.tests.test_plot.TestPcolormesh.test_ty.0": "ea953f83954ac2fc956ac07e956a3509c0de71796ab57a816a854a916ab590fb", + "iris.tests.test_plot.TestPcolormesh.test_tz.0": "fa81857e857a7e84857a7a81857e7a813a0f7a817a85857b7a85857a7a85857a", "iris.tests.test_plot.TestPcolormesh.test_yx.0": "e97a387e968596319697c3c19284a62c93ad60c36933393a6c7e793a6c6b31cd", - "iris.tests.test_plot.TestPcolormesh.test_zx.0": "fa81857e857a7e01857e7a81857e7a81a0577a816a8585fa7a85857e7a85857e", - "iris.tests.test_plot.TestPcolormesh.test_zy.0": "fa81857e857e7e80857e7a81857e7a8129577a817a85857e7a81857e7a80857e", - "iris.tests.test_plot.TestPcolormeshNoBounds.test_tx.0": "ea858782957a703f957a3878957a7a65957a6b806ae56f846ad50fd46a859c50", - "iris.tests.test_plot.TestPcolormeshNoBounds.test_ty.0": "ea85857a857e7e81957a7a81957a6a85857acae6c1fb6aa47a81956e6a81b506", + "iris.tests.test_plot.TestPcolormesh.test_zx.0": "fa81857e857a7e01857e7a81857e7a81e0577a816a8585fa7a85857e7a81857e", + "iris.tests.test_plot.TestPcolormesh.test_zy.0": "fa81857e857e7e80857e7a81857e7a8125577a817a85817f7a81857e7a80857e", + "iris.tests.test_plot.TestPcolormeshNoBounds.test_tx.0": "ea858782957a603f957a387a957a7a6d957a6bc06ae56f806ad50fd06a859c50", + "iris.tests.test_plot.TestPcolormeshNoBounds.test_ty.0": "ea85857a857e3e81957a7a81957a6a85857acae6c1fb6aa67a81956e6a81b506", "iris.tests.test_plot.TestPcolormeshNoBounds.test_tz.0": "fa813e81857e857a857e7a81857e6a85817b0aa63e993e857e81c17e7a81956e", "iris.tests.test_plot.TestPcolormeshNoBounds.test_yx.0": "e96ac79796953c4c9685383996c538e69692637261696b49693ac796693ac71b", - "iris.tests.test_plot.TestPcolormeshNoBounds.test_zx.0": "fa817a85857a857e857e7a81857e7a81817f95506aaf7a807e81c17c7a81957a", - "iris.tests.test_plot.TestPcolormeshNoBounds.test_zy.0": "fa817a80857a857e857e7a81857e3e81817e2fc56aa07a857e80c17f7e80c17f", - "iris.tests.test_plot.TestPlot.test_t.0": "8ffe9c1a7e05e718f305d9d2e46312718138049e824e2fa783db2bed76b4fe00", + "iris.tests.test_plot.TestPcolormeshNoBounds.test_zx.0": "fa817a85857a857e857e7a81857e7a81c17f95506aaf7a807e81c17c7a81857a", + "iris.tests.test_plot.TestPcolormeshNoBounds.test_zy.0": "fa817a80857a857e857e7a81817e3e81817e2f756aa47a817e80c17f7e80c17f", + "iris.tests.test_plot.TestPlot.test_t.0": "8ffe9c1a7e05e718f305d9d2e463127181380c9e824e2fa781db2bed76b4fe00", "iris.tests.test_plot.TestPlot.test_t_dates.0": "87fc9d8b7e044d81f5037bd4c14324749279a73e8d9d864f09e4a7b348dc2769", "iris.tests.test_plot.TestPlot.test_x.0": "8bfe956b7c01c2f26300929dfc1e3c6690736f91817e3b0c84be6be5d1603ed1", "iris.tests.test_plot.TestPlot.test_y.0": "aff8946c7a14c99fb193d263e42432d8d00c2d27944a3f8dc5223ef703ff6b90", @@ -159,26 +159,26 @@ "iris.tests.test_plot.TestPlotCitation.test_axes.0": "abf895467a1d9506f811783485437abd85427ab995067ab9f00687f96afe87c8", "iris.tests.test_plot.TestPlotCitation.test_figure.0": "abf895467a1d9506f811783485437abd85427ab995067ab9f00687f96afe87c8", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_non_cube_coordinate.0": "fa81857e857e3e85857e7a81857e7a81857e7a817e81780b7a81c56a7a81857e", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.0": "ea853f10956ac1e3957a854e957a203e955e6aa76ae17aa16a856aaf6ab19e12", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.0": "ea853f10956ac1e1957a854e957a207e955e6aa76ae17aa16a856aaf6ab19e12", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.1": "ea853a85857a857a957a857a957ed05a857b3e946a606b917a816f247a853af4", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.2": "eafdcec9f4219530b696a56694c3852a95656b7b85986acdc06516adad186e9a", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.3": "aff24ab7fd05952dbd0f950f910fcd48c47868f3e1b9329094266e345a850f6c", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.3": "aff24ab7fd05952dbd0f950f910fed48c47868f2e1b9329094266e345a850f6c", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.4": "eaa9b5699556854e9456854ed05625f9d0a92bfdc0a90afd81f97e00855e7ab6", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_tx.5": "eaf73e0d9503852c950395ac9528c1fad06cc0f2d1ec6af2c0fc6a536a1797f3", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_x.0": "afea950ddb13c03e34359ad8a4c86f24913f2693807e3ff1f4087b4285fd28f2", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_y.0": "afee9632de05c9d9f180d168c454a53e931b3e84956a3b8c85d94ce703ff7284", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.0": "ea853f00957ac07c957ac0be951a69f3c47c7a5f3a6127816b953e646b813761", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_x.0": "afea950ddb13c03e34359ad8a4c86f24913f2693806e3ff1f4087b4285fd2af2", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_y.0": "afee9632de05c9d9f180d168c454a53e931b3e84954a3b8c85f94ce703ff7284", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.0": "ea853f00957ac07c957ac0bf951a69f3c47c7a5f3a4127816b953e646b813761", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.1": "e97a346c9685cb899685c9c39695c79396ec634969ce2c74697a3864697b3c8c", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.2": "ebfeca44f102b3649c309c9b940d19add1bb63b3a7843e4acc5a6aa56acc6b64", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.3": "e85a6b6c86a595a791c9349b94b73b69c7926b5bccca66646b3869b031a52ca6", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.4": "ea153e0395aac1f895eac0f8940e69e56a743e5f7a432787691ef860c3c1938f", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.2": "ebffca44f102b3609c309c9b940d19add1bb63b3a7843e4acc5a6aa56acc6b64", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.3": "e85a6b6c86a595a791c9349b94b63b69c7926b5bccca66646b3869b831a52ca6", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.4": "ea153e0395aac0f895eac1f8941e69e56a743e5d7a432787691ef860c3c1938f", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_yx.5": "e96930749696cb9d9697cdc39692671b696c306969eb3c76697319942a0d8699", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.0": "be813ea0c17ec55ac17ed23dc07e295ac57e3b653f803f813e816e853e81b542", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.1": "ea85856e857e4893957a7aa1956a7b81954b3b817a856fd46a85846e6e85857e", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.0": "be813ea0c17ec55ac17ed23dc07e295ac57e2b653f803f813e816e853e85b542", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.1": "ea85856e857e4893957a7aa1956a7b81954b3b817a856fd46a85847c6e85857e", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.2": "cbedcd25bc02a4929c103a5bf03fdbbc81cb364d84e46da70f86899b3a0f6ec1", "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.3": "aee1793a6b168569b852d697913c622cc5ca2e4b952d3bb4c2b66bd1426b3c71", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.4": "bec13a81c13ec56ac13e5afdd11e256a3e412afd3e4002ff2ee0fe0035fa817a", - "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.5": "ea1595ec95ea681d95ea7b0595ab3b13950d7a536a1cc6f26a0cc4f26e0c85f2", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.4": "bec13a81c13ec54ac13e5afdd11e256a3e412afd3e4002ff2ee0fe0035fa817e", + "iris.tests.test_plot.TestPlotCoordinatesGiven.test_zx.5": "ea1594ec95ea6c1d95ea7b0595ab3b13950f6a536a1cc6f26a0cc4f26e0c85f2", "iris.tests.test_plot.TestPlotDimAndAuxCoordsKwarg.test_coord_names.0": "b87830b0c786cf269ec766c99399cce998d3b3166f2530d3658c692d30ec6735", "iris.tests.test_plot.TestPlotDimAndAuxCoordsKwarg.test_coord_names.1": "b8a53b59c71ac5a6b8791c1867876b63d9e0e65c96199d871cc23339633664ce", "iris.tests.test_plot.TestPlotDimAndAuxCoordsKwarg.test_coords.0": "b87830b0c786cf269ec766c99399cce998d3b3166f2530d3658c692d30ec6735", @@ -192,33 +192,33 @@ "iris.tests.test_plot.TestQuickplotPlot.test_x.0": "82ff950b7f81c0d6620199bcfc5e986695734da1816e1b2c85be2b65d96276d1", "iris.tests.test_plot.TestQuickplotPlot.test_y.0": "a2fbb46e7f10c99f2013d863e46498dcd06c0d2798421fa5dd221e7789ff6f10", "iris.tests.test_plot.TestQuickplotPlot.test_z.0": "a3ffc1de7e009c7030019786f438cde3810fd93c9b734a778ce47c9799b02731", - "iris.tests.test_plot.TestSimple.test_bounds.0": "ea856a85954a957ac17e954ac17a9c3e956ac07e3e80c07f3e857aa5c27d3f80", - "iris.tests.test_plot.TestSimple.test_points.0": "fa856a85957a957ac17e954ac17e1ca2950bc07e3e80c07f3e807a85c1ff3f81", + "iris.tests.test_plot.TestSimple.test_bounds.0": "ea856a85954a957ac17e954ac17a9d3a956ac07e3e80c07f3e857aa5c27d3f80", + "iris.tests.test_plot.TestSimple.test_points.0": "ea856a85957a957ac17e954ac17e1ea2950bc07e3e80c07f3e807a85c1ff3f81", "iris.tests.test_plot.TestSymbols.test_cloud_cover.0": "eb5291e494ad6e136b5291ec94ad6e136b5291ec94ad6e136b5291ec94ad6e13", "iris.tests.test_quickplot.TestLabels.test_alignment.0": "be813fe0954ac07fc0ff3e81c03fc97a6d0094af3f80c17f36a53240d97f2d82", "iris.tests.test_quickplot.TestLabels.test_contour.0": "a7fd955a7a016d1a3217c962e4819a56c96f3c859b624d2584de3a6999b662db", - "iris.tests.test_quickplot.TestLabels.test_contour.1": "bf802f81c17fc17fc07eb42ac07f3f929130c07e3f81c07f7aa02e85c07f3e81", + "iris.tests.test_quickplot.TestLabels.test_contour.1": "bf802f85c17fc17fc07eb42ac17f3f929130c06e3f80c07f7aa02e85c07f3e81", "iris.tests.test_quickplot.TestLabels.test_contourf.0": "be816a95957a957ac0fe1e8bc07f7f806e01c07f3f80c07f3fa23f00c07f3d00", - "iris.tests.test_quickplot.TestLabels.test_contourf.1": "bf802f81c17fc17fc07eb42ac07f3f929130c07e3f81c07f7aa02e85c07f3e81", + "iris.tests.test_quickplot.TestLabels.test_contourf.1": "bf802f85c17fc17fc07eb42ac17f3f929130c06e3f80c07f7aa02e85c07f3e81", "iris.tests.test_quickplot.TestLabels.test_contourf.2": "be816a95907ae508c17e955ac07f3fa0945bc07f3f80c07f3aa36f01c0ff3f80", - "iris.tests.test_quickplot.TestLabels.test_contourf_nameless.0": "be816af5907ee508c17e955ac03f3f809499c07f3f80c07f3a836f81c0ff3f80", + "iris.tests.test_quickplot.TestLabels.test_contourf_nameless.0": "be816af5907ee508c17e955ac03f3f809419c07f3f80c07f3a8b6f81c0ff3f80", "iris.tests.test_quickplot.TestLabels.test_map.0": "e85a634c86a597a793c9349b94b79969c396c95bcce69a64d938c9b039a58ca6", "iris.tests.test_quickplot.TestLabels.test_map.1": "e85a636c86a597a793c9349b94b69969c396c95bcce69a64d938c9b039a58ca6", "iris.tests.test_quickplot.TestLabels.test_pcolor.0": "eea16affc05ab500956e974ac53f3d80925ac03f2f81c07e3fa12da1c2fe3f80", "iris.tests.test_quickplot.TestLabels.test_pcolormesh.0": "eea16affc05ab500956e974ac53f3d80925ac03f2f81c07e3fa12da1c2fe3f80", "iris.tests.test_quickplot.TestLabels.test_pcolormesh_str_symbol.0": "eea16affc05ab500956e974ac53f3d80925ac03f3f80c07e3fa12d21c2ff3f80", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_non_cube_coordinate.0": "fe816a85857a957ac07f957ac07f3e80956ac07f3e80c07f3e813e85c07e3f80", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.0": "ea856a95955a956ac17f950a807e3f4c951ac07e3f81c0ff3ea16aa1c0bd3f81", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.1": "fa856a85957a957ac17e954ac17e1ca2950bc07e3e80c07f3e807a85c1ff3f81", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.2": "eafdeec9f729943032168d66d4cb896e9567497b81304aedc96514ad8d18669a", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.0": "ea856a95955a956ac17f950a807e3f4e951ac07e3f81c0ff3ea16aa1c0bd3e81", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.1": "ea856a85957a957ac17e954ac17e1ea2950bc07e3e80c07f3e807a85c1ff3f81", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.2": "eaf9eec9f729943032168d66d4db896e9567497b81304aedc96514ad8d18669a", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.3": "a6fb4b967f00950eb00f9d0f900fcd62dc7868f2c1bb3a909c266e34daa52f6c", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.4": "eaa9b549f756854ea0168d6ed556896dd8e909ed88290afdd9e97e008d6e2296", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.5": "aad73e0df78085ac840395ac9428d9fad56cd8f2906c48f2d0ec7a536a1737f3", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.4": "eaa9b549f756854ea0168d6ed556896fd8a909ed88290afdd9e97e008d6e2296", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_tx.5": "aad73e0df78085ac840195ac9528d9fad56cd8f2906c48f2d0ec7a536a1737f3", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_x.0": "a6fb958dff50c03e203598dca4c9cd26933f9cf3886e1de1dc047b4289ec2672", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_y.0": "a2ffb6127f0dc9993085d960c6748d3e9b121ca49d6a1b048df34ce789ff7205", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.0": "ea856a95957a957ac07e954ac17e3e87950bc07f3ea4c27d3e833ac1c1e03f80", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_y.0": "a2ffb6127f0dc9992085d960c6748d3edb121ca49d6a1b048df34ce789ff7205", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.0": "ea856a95957a957ac07e954ac17e3e86950bc17f3ea4c27d3e833ac1c1e03f80", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.1": "e5a761a79a589e58c07d1e48c07c3f819e41c07f3d84c17e3fa62585c0fe3f83", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.2": "aaffead4f7cab16490109c9b946d99add1b34bb385a41c4acd526a254acc6365", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.2": "aaffead4f7cab16490109c9b946d99add1b74bb385a41c4acd526a254acc6325", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.3": "e85a634c86a597a793c9349b94b79969c396c95bcce69a64d938c9b039a58ca6", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.4": "ea153f0395eac1f895eac9fa941c79e56a741e4f68430f876916f860c9c1938d", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_yx.5": "e96930749696cf9d9697cdc39692670b696c386969eb3866696399a41a0d8e99", @@ -226,8 +226,8 @@ "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.1": "fa816a85957a957ac03f957ac07f3ba1954ac07e3e81c07f3ea47a85c07e3e80", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.2": "a3eded04ff11a492b000985af07fdbb4d1eb366d8c644da79fa68993180f6e81", "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.3": "aef9793a770085e9205fd696d03ccb2485ca1e43952f1934daa66bd1ca6b3c71", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.4": "bec13e81c5bec55ac03dd896d17e8d6a1e410af7380008ff1de6fe0099ea237b", - "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.5": "ea1595ac95e8689d95fb7b0595291963916f3b73487fccf2680484f2486ec6f0", - "iris.tests.test_quickplot.TestTimeReferenceUnitsLabels.test_not_reference_time_units.0": "82f8a19e7f51888c6001dda6855fd9a2dd7f986281ee19f389ff03ffdc007e00", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.4": "bec13e81c5bec55ac03dd8b4d17a8d6a1e4108f7384008ff1de6fe0099ee237b", + "iris.tests.test_quickplot.TestQuickplotCoordinatesGiven.test_zx.5": "ea1595ac95e8689d95fb7b0595291943916f3b73487fccf2680484f2486ec7f0", + "iris.tests.test_quickplot.TestTimeReferenceUnitsLabels.test_not_reference_time_units.0": "82f8a19e7f51888c6001dda6855fd9e2dd7f986281ee19f389ef03ffdc007e00", "iris.tests.test_quickplot.TestTimeReferenceUnitsLabels.test_reference_time_units.0": "82fa80997f547799a0037a00d52f0956ddaf9f7e98a1816e09f5d8260bfffe00" } \ No newline at end of file diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 3cac933c63..e3604c3ea8 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e8c72613e1b6a6c446856d7d11fc97571678e85f18691bed5be685cf1437b728 +# input_hash: 1cd97f6f80cce9e7fa719a886fb090579568d5909bfcac6cb42a820ad562dad3 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -34,7 +34,7 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.10-h7f98852_0.tar.bz2#ffa3a757a97e851293909b49f49f28fb +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 @@ -95,7 +95,7 @@ https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openb https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.3.0-h0fcbabc_4.tar.bz2#2cdb3944029c2cb99cbe981b182a2e9a +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 @@ -112,6 +112,7 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.b https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.70.2-h780b84a_4.tar.bz2#c66c6df8ef582a3b78702201b1eb8e94 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.5-default_h2e3cab8_0.tar.bz2#8b1cd508fcf54a5c8c5766c549272b6e https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 @@ -120,6 +121,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 @@ -146,9 +148,8 @@ https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/noarch/olefile-0.46-pyh9f0ad1d_1.tar.bz2#0b2e68acc8c78c8cc392b90983481f58 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.0-h93bde94_1.tar.bz2#cf908994f24ea526afc59f295d5b07c1 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_0.tar.bz2#42d593cd1b23a43d7ac7edd9a2e84fa9 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc @@ -186,7 +187,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar. https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py38h3a7f9d9_0.tar.bz2#bde7c584b811ef5aec0dd2204e502334 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-6.2.1-py38hd70f55b_1.tar.bz2#80d719bee2b77a106b199150c0829107 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.1.1-py38h0ee0e06_1.tar.bz2#cd653a4a951ca80adb96ff6cd3b36883 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 @@ -213,7 +214,7 @@ https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38h5b5ac8f_0.tar.bz2#5d91e0c583547eb69345c3acf4d753ee +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe @@ -233,7 +234,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar. https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py38hfa26641_1.tar.bz2#40f4eeb2cb0f0ab25d0640f5f7a34de8 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38h90a48c0_5.tar.bz2#8d4a94a9c0dec7e5edeff344cbfe4c38 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38hb3c56ba_6.tar.bz2#9ec1f7a5fe50f16f5103845db2ebd6d8 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index b79d4638f3..6f782a831d 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -34,7 +34,6 @@ dependencies: # Test dependencies. - filelock - imagehash >=4.0 - - pillow <7 - pre-commit - psutil - pytest From 59eccc3413e56a83074ffea941f1c9612124fe27 Mon Sep 17 00:00:00 2001 From: Will Benfold <69585101+wjbenfold@users.noreply.github.com> Date: Mon, 27 Jun 2022 22:15:59 +0100 Subject: [PATCH 152/319] Implement `iris.plot.fill_between` (#4647) * Remove pin, update hashes, improve script * What's new * Lockfile and whatsnew * Shareable urls * Later lockfile * Shareable... * Check for consistency before fiddling * Updates for new test data (idiff'ed) * Keep lockstep between imagerepo and data * idiff always runs after imagerepo.json has been updated anyway * Gallery test updates (also idiff'd) * Implement fill_between and write tests * Simplify error check * Test images * What's new * Image test results * Fix error message * Pre-emptive test data version update Co-authored-by: Bill Little --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci-docs-tests.yml | 2 +- .github/workflows/ci-tests.yml | 2 +- docs/src/whatsnew/latest.rst | 4 + lib/iris/plot.py | 110 +++++++++++++++++++++++++- lib/iris/quickplot.py | 14 ++++ lib/iris/tests/results/imagerepo.json | 8 ++ lib/iris/tests/test_image_json.py | 10 +-- lib/iris/tests/test_plot.py | 103 ++++++++++++++++++++++++ 9 files changed, 246 insertions(+), 9 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index df4a4fc9da..9ebc7a4c45 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -15,7 +15,7 @@ jobs: env: IRIS_TEST_DATA_LOC_PATH: benchmarks IRIS_TEST_DATA_PATH: benchmarks/iris-test-data - IRIS_TEST_DATA_VERSION: "2.13" + IRIS_TEST_DATA_VERSION: "2.14" # Lets us manually bump the cache to rebuild ENV_CACHE_BUILD: "0" TEST_DATA_CACHE_BUILD: "2" diff --git a/.github/workflows/ci-docs-tests.yml b/.github/workflows/ci-docs-tests.yml index 9e200c124e..c2e3fcfcf8 100644 --- a/.github/workflows/ci-docs-tests.yml +++ b/.github/workflows/ci-docs-tests.yml @@ -39,7 +39,7 @@ jobs: session: ["doctest", "gallery", "linkcheck"] env: - IRIS_TEST_DATA_VERSION: "2.13" + IRIS_TEST_DATA_VERSION: "2.14" ENV_NAME: "ci-docs-tests" steps: diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 31cccfb0bb..18cb1f5e21 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -39,7 +39,7 @@ jobs: session: ["tests"] env: - IRIS_TEST_DATA_VERSION: "2.13" + IRIS_TEST_DATA_VERSION: "2.14" ENV_NAME: "ci-tests" steps: diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a16ce598cc..0e4d51ffec 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -72,6 +72,10 @@ This document explains the changes made to Iris for this release :func:`numpy.percentile` keywords through the :obj:`~iris.analysis.PERCENTILE` aggregator. (:pull:`4791`) +#. `@wjbenfold`_ and `@bjlittle`_ (reviewer) implemented + :func:`iris.plot.fill_between` and :func:`iris.quickplot.fill_between`. + (:issue:`3493`, :pull:`4647`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 74e5d5788c..47c63dc173 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -645,7 +645,30 @@ def _u_object_from_v_object(v_object): def _get_plot_objects(args): - if len(args) > 1 and isinstance( + if len(args) > 2 and isinstance( + args[2], (iris.cube.Cube, iris.coords.Coord) + ): + # three arguments + u_object, v_object1, v_object2 = args[:3] + u1, v1 = _uv_from_u_object_v_object(u_object, v_object1) + _, v2 = _uv_from_u_object_v_object(u_object, v_object2) + args = args[3:] + if u1.size != v1.size or u1.size != v2.size: + msg = "The x and y-axis objects are not all compatible. They should have equal sizes but got ({}: {}), ({}: {}) and ({}: {})" + raise ValueError( + msg.format( + u_object.name(), + u1.size, + v_object1.name(), + v1.size, + v_object2.name(), + v2.size, + ) + ) + u = u1 + v = (v1, v2) + v_object = (v_object1, v_object2) + elif len(args) > 1 and isinstance( args[1], (iris.cube.Cube, iris.coords.Coord) ): # two arguments @@ -823,6 +846,52 @@ def _draw_1d_from_points(draw_method_name, arg_func, *args, **kwargs): return result +def _draw_two_1d_from_points(draw_method_name, arg_func, *args, **kwargs): + """ + This function is equivalend to _draw_two_1d_from_points but expects two + y-axis variables rather than one (such as is required for .fill_between). It + can't be used where the y-axis variables are string coordinates. The y-axis + variable provided first has precedence where the two differ on whether the + axis should be inverted or whether a map should be drawn. + """ + # NB. In the interests of clarity we use "u" to refer to the horizontal + # axes on the matplotlib plot and "v" for the vertical axes. + + # retrieve the objects that are plotted on the horizontal and vertical + # axes (cubes or coordinates) and their respective values, along with the + # argument tuple with these objects removed + u_object, v_objects, u, vs, args = _get_plot_objects(args) + + v_object1, _ = v_objects + v1, v2 = vs + + # if both u_object and v_object are coordinates then check if a map + # should be drawn + if ( + isinstance(u_object, iris.coords.Coord) + and isinstance(v_object1, iris.coords.Coord) + and _can_draw_map([v_object1, u_object]) + ): + # Replace non-cartopy subplot/axes with a cartopy alternative and set + # the transform keyword. + kwargs = _ensure_cartopy_axes_and_determine_kwargs( + u_object, v_object1, kwargs + ) + + axes = kwargs.pop("axes", None) + draw_method = getattr(axes if axes else plt, draw_method_name) + if arg_func is not None: + args, kwargs = arg_func(u, v1, v2, *args, **kwargs) + result = draw_method(*args, **kwargs) + else: + result = draw_method(u, v1, v2, *args, **kwargs) + + # Invert y-axis if necessary. + _invert_yaxis(v_object1, axes) + + return result + + def _replace_axes_with_cartopy_axes(cartopy_proj): """ Replace non-cartopy subplot/axes with a cartopy alternative @@ -1599,6 +1668,45 @@ def scatter(x, y, *args, **kwargs): return _draw_1d_from_points("scatter", _plot_args, *args, **kwargs) +def fill_between(x, y1, y2, *args, **kwargs): + """ + Plots y1 and y2 against x, and fills the space between them. + + Args: + + * x: :class:`~iris.cube.Cube` or :class:`~iris.coords.Coord` + A cube or a coordinate to plot on the x-axis. + + * y1: :class:`~iris.cube.Cube` or :class:`~iris.coords.Coord` + First cube or a coordinate to plot on the y-axis. + + * y2: :class:`~iris.cube.Cube` or :class:`~iris.coords.Coord` + Second cube or a coordinate to plot on the y-axis. + + Kwargs: + + * axes: :class:`matplotlib.axes.Axes` + The axes to use for drawing. Defaults to the current axes if none + provided. + + See :func:`matplotlib.pyplot.fill_between` for details of additional valid + keyword arguments. + + """ + # here we are more specific about argument types than generic 1d plotting + if not isinstance(x, (iris.cube.Cube, iris.coords.Coord)): + raise TypeError("x must be a cube or a coordinate.") + if not isinstance(y1, (iris.cube.Cube, iris.coords.Coord)): + raise TypeError("y1 must be a cube or a coordinate.") + if not isinstance(y1, (iris.cube.Cube, iris.coords.Coord)): + raise TypeError("y2 must be a cube or a coordinate.") + args = (x, y1, y2) + args + _plot_args = None + return _draw_two_1d_from_points( + "fill_between", _plot_args, *args, **kwargs + ) + + # Provide convenience show method from pyplot show = plt.show diff --git a/lib/iris/quickplot.py b/lib/iris/quickplot.py index 2c4a94b1d0..14f9e5d2d5 100644 --- a/lib/iris/quickplot.py +++ b/lib/iris/quickplot.py @@ -311,5 +311,19 @@ def scatter(x, y, *args, **kwargs): return result +def fill_between(x, y1, y2, *args, **kwargs): + """ + Draws a labelled fill_between plot based on the given cubes or coordinates. + + See :func:`iris.plot.fill_between` for details of valid arguments and + keyword arguments. + + """ + axes = kwargs.get("axes") + result = iplt.fill_between(x, y1, y2, *args, **kwargs) + _label_1d_plot(x, y1, axes=axes) + return result + + # Provide a convenience show method from pyplot. show = plt.show diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index 5ae8046c5b..28d6f0bb03 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -68,6 +68,10 @@ "iris.tests.test_mapping.TestLowLevel.test_simple.0": "faa0e558855f9de7857a1ab16a85a51d36a1e55a854e58a5c13837096e8fe17a", "iris.tests.test_mapping.TestMappingSubRegion.test_simple.0": "b9913d90c66eca6ec66ec2f3689195aecf5b2f00392cb3496495e21da4db6c92", "iris.tests.test_mapping.TestUnmappable.test_simple.0": "fa81b54a817eca37817ec701857e3e64943e7bb41b806f996e817e006ee1b19b", + "iris.tests.test_plot.Test1dFillBetween.test_coord_coord.0": "f31432798cebcd87723835b4a5c5c2dbcf139c6c8cf4730bf3c36d801e380378", + "iris.tests.test_plot.Test1dFillBetween.test_coord_cube.0": "ea17352b92f0cbd42d6c8d25e59d36dc3a538d2bb2e42d26c6d2c2c8e4a1ce99", + "iris.tests.test_plot.Test1dFillBetween.test_cube_coord.0": "aff8e44af2019b3d3d03e0d1865e272cc1643de292db4b98c53c7ce5b0c37b2c", + "iris.tests.test_plot.Test1dFillBetween.test_cube_cube.0": "ea1761f695a09c0b70cc938d334b4e4f4c3671f2cd8b7996973c2c68e1c39e26", "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord.0": "8bfec2577e01a5a5ed013b4ac4521c94817d4e6d91ff63369c6d61991e3278cc", "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord_coord.0": "8fff941e7e01e1c2f801c878a41e5b0d85cf36e1837e2d9992c62f21769e6a4d", "iris.tests.test_plot.Test1dPlotMultiArgs.test_coord_coord_map.0": "bbe0c214cd979dc3b05e4b68db0771b48698961b7962d2446e8ca5bb36716c6e", @@ -75,6 +79,10 @@ "iris.tests.test_plot.Test1dPlotMultiArgs.test_cube.0": "8fffc1dc7e019c70f001b70ee4386de1814e7938837b6a7f84d07c9f15b02f21", "iris.tests.test_plot.Test1dPlotMultiArgs.test_cube_coord.0": "8fffc1dc7e019c70f001b70ee4386de1814e7938837b6a7f84d07c9f15b02f21", "iris.tests.test_plot.Test1dPlotMultiArgs.test_cube_cube.0": "8ff8c0567a01b296e4019d2ff10b464bd4da6391943678e5879f7e3903e63f1c", + "iris.tests.test_plot.Test1dQuickplotFillBetween.test_coord_coord.0": "f314b2798ce3cd87723835a4a5c5c2dbcf139c6c8cf4730bd3c36d801c3c6378", + "iris.tests.test_plot.Test1dQuickplotFillBetween.test_coord_cube.0": "ea17352bd2f0cbd4256c8da5e59c36dc1a538d2b92e41d26ced2c2c8eca1ce99", + "iris.tests.test_plot.Test1dQuickplotFillBetween.test_cube_coord.0": "a3ffe44af6009b3d2907c8f1f6588f2cc96619e290fb4b88cd2c3ce590e3770c", + "iris.tests.test_plot.Test1dQuickplotFillBetween.test_cube_cube.0": "ea17e1f695a09c0b60cc938d334b4e4f4c3671f2cd8b7996973c2c69e1c31e26", "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord.0": "83fec2777e002427e801bb4ae65a1c94813dcec999db4bbc9ccd79991f3238cc", "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord_coord.0": "83ff9d9f7e01e1c2b001c8f8f63e1b1d81cf36e1837e258982ce6f215c9a626c", "iris.tests.test_plot.Test1dQuickplotPlotMultiArgs.test_coord_coord_map.0": "bbe0c214cd979dc3b05e4b68db0771b48698961b7962d2446e8ca5bb36716c6e", diff --git a/lib/iris/tests/test_image_json.py b/lib/iris/tests/test_image_json.py index 68f70753bf..b5213156f8 100644 --- a/lib/iris/tests/test_image_json.py +++ b/lib/iris/tests/test_image_json.py @@ -30,17 +30,17 @@ def test_json(self): missing_from_json = test_data_name_set - repo_name_set if missing_from_json: amsg = ( - "Missing images: Image names are referenced in " - "imagerepo.json, that are not present in the iris-test-data " - "repo" + "Missing images: Images are present in the iris-test-data " + "repo, that are not referenced in imagerepo.json" ) # Always fails when we get here: report the problem. self.assertEqual(missing_from_json, set(), msg=amsg) missing_from_test_data = repo_name_set - test_data_name_set if missing_from_test_data: amsg = ( - "Missing images: Images are present in the iris-test-data " - "repo, that are not referenced in imagerepo.json" + "Missing images: Image names are referenced in " + "imagerepo.json, that are not present in the iris-test-data " + "repo" ) # Always fails when we get here: report the problem. self.assertEqual(missing_from_test_data, set(), msg=amsg) diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py index 458616a6fb..0c47bd6d3a 100644 --- a/lib/iris/tests/test_plot.py +++ b/lib/iris/tests/test_plot.py @@ -16,6 +16,7 @@ import numpy as np import iris +import iris.analysis import iris.coords as coords import iris.tests.stock @@ -341,6 +342,108 @@ def test_circular_changes(self): self.check_graphic() +class Test1dFillBetween(tests.GraphicsTest): + def setUp(self): + super().setUp() + self.cube = iris.load_cube( + tests.get_data_path( + ("NetCDF", "testing", "small_theta_colpex.nc") + ), + "air_potential_temperature", + )[0, 0] + self.draw_method = iplt.fill_between + + def test_coord_coord(self): + x = self.cube.coord("grid_latitude") + y1 = self.cube.coord("surface_altitude")[:, 0] + y2 = self.cube.coord("surface_altitude")[:, 1] + self.draw_method(x, y1, y2) + self.check_graphic() + + def test_coord_cube(self): + x = self.cube.coord("grid_latitude") + y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) + y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) + self.draw_method(x, y1, y2) + self.check_graphic() + + def test_cube_coord(self): + x = self.cube.collapsed("grid_longitude", iris.analysis.MEAN) + y1 = self.cube.coord("surface_altitude")[:, 0] + y2 = y1 + 10 + self.draw_method(x, y1, y2) + self.check_graphic() + + def test_cube_cube(self): + x = self.cube.collapsed("grid_longitude", iris.analysis.MEAN) + y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) + y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) + self.draw_method(x, y1, y2) + self.check_graphic() + + def test_incompatible_objects_x_odd(self): + # cubes/coordinates of different sizes cannot be plotted + x = self.cube.coord("grid_latitude")[:-1] + y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) + y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) + with self.assertRaises(ValueError): + self.draw_method(x, y1, y2) + + def test_incompatible_objects_y1_odd(self): + # cubes/coordinates of different sizes cannot be plotted + x = self.cube.coord("grid_latitude") + y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN)[:-1] + y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) + with self.assertRaises(ValueError): + self.draw_method(x, y1, y2) + + def test_incompatible_objects_y2_odd(self): + # cubes/coordinates of different sizes cannot be plotted + x = self.cube.coord("grid_latitude") + y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) + y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX)[:-1] + with self.assertRaises(ValueError): + self.draw_method(x, y1, y2) + + def test_incompatible_objects_all_odd(self): + # cubes/coordinates of different sizes cannot be plotted + x = self.cube.coord("grid_latitude") + y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN)[:-1] + y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX)[:-2] + with self.assertRaises(ValueError): + self.draw_method(x, y1, y2) + + def test_multidimensional(self): + # multidimensional cubes/coordinates are not allowed + x = self.cube.coord("grid_latitude") + y1 = self.cube + y2 = self.cube + with self.assertRaises(ValueError): + self.draw_method(x, y1, y2) + + def test_not_cube_or_coord(self): + # inputs must be cubes or coordinates + x = np.arange(self.cube.shape[0]) + y1 = self.cube.collapsed("grid_longitude", iris.analysis.MIN) + y2 = self.cube.collapsed("grid_longitude", iris.analysis.MAX) + with self.assertRaises(TypeError): + self.draw_method(x, y1, y2) + + +@tests.skip_data +@tests.skip_plot +class Test1dQuickplotFillBetween(Test1dFillBetween): + def setUp(self): + tests.GraphicsTest.setUp(self) + self.cube = iris.load_cube( + tests.get_data_path( + ("NetCDF", "testing", "small_theta_colpex.nc") + ), + "air_potential_temperature", + )[0, 0] + self.draw_method = qplt.fill_between + + @tests.skip_data @tests.skip_plot class TestAttributePositive(tests.GraphicsTest): From a908a27937e4ca8eb810001ebd64f11ff75dce72 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 28 Jun 2022 10:32:30 +0100 Subject: [PATCH 153/319] Python 3.9 CI & Combine CI workflows (#4832) * combine ci workflows * remove badge * ci unpin python --- .github/workflows/ci-docs-tests.yml | 129 --------- .github/workflows/ci-tests.yml | 26 +- README.md | 3 - .../common/metadata/test__NamedTupleMeta.py | 2 +- lib/iris/tests/unit/coords/test_Coord.py | 2 +- requirements/ci/iris.yml | 2 +- requirements/ci/nox.lock/py38-linux-64.lock | 14 +- requirements/ci/nox.lock/py39-linux-64.lock | 254 ++++++++++++++++++ requirements/ci/py39.yml | 49 ++++ 9 files changed, 331 insertions(+), 150 deletions(-) delete mode 100644 .github/workflows/ci-docs-tests.yml create mode 100644 requirements/ci/nox.lock/py39-linux-64.lock create mode 100644 requirements/ci/py39.yml diff --git a/.github/workflows/ci-docs-tests.yml b/.github/workflows/ci-docs-tests.yml deleted file mode 100644 index c2e3fcfcf8..0000000000 --- a/.github/workflows/ci-docs-tests.yml +++ /dev/null @@ -1,129 +0,0 @@ -# reference: -# - https://github.com/actions/cache -# - https://github.com/actions/checkout -# - https://github.com/marketplace/actions/setup-miniconda - -name: ci-docs-tests - -on: - push: - branches: - - "main" - - "v*x" - tags: - - "v*" - pull_request: - branches: - - "*" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - tests: - name: "${{ matrix.session }} ${{ matrix.os }} py${{ matrix.python-version }}" - - runs-on: ${{ matrix.os }} - - defaults: - run: - shell: bash -l {0} - - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest"] - python-version: ["3.8"] - session: ["doctest", "gallery", "linkcheck"] - - env: - IRIS_TEST_DATA_VERSION: "2.14" - ENV_NAME: "ci-docs-tests" - - steps: - - name: "checkout" - uses: actions/checkout@v3 - - - name: "environment configure" - env: - # Maximum cache period (in weeks) before forcing a cache refresh. - CACHE_WEEKS: 2 - run: | - echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} - echo "LOCK_FILE=requirements/ci/nox.lock/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} - - - name: "data cache" - uses: ./.github/workflows/composite/iris-data-cache - with: - cache_build: 1 - env_name: ${{ env.ENV_NAME }} - version: ${{ env.IRIS_TEST_DATA_VERSION }} - - - name: "conda package cache" - uses: ./.github/workflows/composite/conda-pkg-cache - with: - cache_build: 1 - cache_period: ${{ env.CACHE_PERIOD }} - env_name: ${{ env.ENV_NAME }} - - - name: "conda install" - uses: conda-incubator/setup-miniconda@v2 - with: - miniforge-version: latest - channels: conda-forge,defaults - activate-environment: ${{ env.ENV_NAME }} - auto-update-conda: false - use-only-tar-bz2: true - - - name: "conda environment cache" - uses: ./.github/workflows/composite/conda-env-cache - with: - cache_build: 1 - cache_period: ${{ env.CACHE_PERIOD }} - env_name: ${{ env.ENV_NAME }} - install_packages: "cartopy nox pip" - - - name: "conda info" - run: | - conda info - conda list - - - name: "cartopy cache" - uses: ./.github/workflows/composite/cartopy-cache - with: - cache_build: 1 - cache_period: ${{ env.CACHE_PERIOD }} - env_name: ${{ env.ENV_NAME }} - - - name: "nox cache" - uses: ./.github/workflows/composite/nox-cache - with: - cache_build: 1 - env_name: ${{ env.ENV_NAME }} - lock_file: ${{ env.LOCK_FILE }} - - # TODO: drop use of site.cfg and explicit use of mplrc - - name: "iris configure" - env: - SITE_CFG: lib/iris/etc/site.cfg - MPL_RC: ${HOME}/.config/matplotlib/matplotlibrc - run: | - mkdir -p $(dirname ${SITE_CFG}) - echo ${SITE_CFG} - echo "[Resources]" >> ${SITE_CFG} - echo "test_data_dir = ${HOME}/iris-test-data/test_data" >> ${SITE_CFG} - echo "doc_dir = ${GITHUB_WORKSPACE}/docs" >> ${SITE_CFG} - cat ${SITE_CFG} - mkdir -p $(dirname ${MPL_RC}) - echo ${MPL_RC} - echo "backend : agg" >> ${MPL_RC} - echo "image.cmap : viridis" >> ${MPL_RC} - cat ${MPL_RC} - - - name: "iris ${{ matrix.session }}" - env: - PY_VER: ${{ matrix.python-version }} - run: | - nox --session ${{ matrix.session }} -- --verbose diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 18cb1f5e21..abe9fd871a 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -23,7 +23,7 @@ concurrency: jobs: tests: - name: "${{ matrix.session }} ${{ matrix.os }} py${{ matrix.python-version }}" + name: "${{ matrix.session }} py${{ matrix.python-version }} ${{ matrix.os }}" runs-on: ${{ matrix.os }} @@ -35,8 +35,12 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest"] - python-version: ["3.8"] - session: ["tests"] + python-version: ["3.9"] + session: ["tests", "doctest", "gallery", "linkcheck"] + include: + - os: "ubuntu-latest" + python-version: "3.8" + session: "tests" env: IRIS_TEST_DATA_VERSION: "2.14" @@ -57,14 +61,14 @@ jobs: - name: "data cache" uses: ./.github/workflows/composite/iris-data-cache with: - cache_build: 1 + cache_build: 0 env_name: ${{ env.ENV_NAME }} version: ${{ env.IRIS_TEST_DATA_VERSION }} - name: "conda package cache" uses: ./.github/workflows/composite/conda-pkg-cache with: - cache_build: 1 + cache_build: 0 cache_period: ${{ env.CACHE_PERIOD }} env_name: ${{ env.ENV_NAME }} @@ -80,7 +84,7 @@ jobs: - name: "conda environment cache" uses: ./.github/workflows/composite/conda-env-cache with: - cache_build: 1 + cache_build: 0 cache_period: ${{ env.CACHE_PERIOD }} env_name: ${{ env.ENV_NAME }} install_packages: "cartopy nox pip" @@ -93,14 +97,14 @@ jobs: - name: "cartopy cache" uses: ./.github/workflows/composite/cartopy-cache with: - cache_build: 1 + cache_build: 0 cache_period: ${{ env.CACHE_PERIOD }} env_name: ${{ env.ENV_NAME }} - name: "nox cache" uses: ./.github/workflows/composite/nox-cache with: - cache_build: 1 + cache_build: 0 env_name: ${{ env.ENV_NAME }} lock_file: ${{ env.LOCK_FILE }} @@ -108,6 +112,7 @@ jobs: - name: "iris configure" env: SITE_CFG: lib/iris/etc/site.cfg + MPL_RC: ${HOME}/.config/matplotlib/matplotlibrc run: | mkdir -p $(dirname ${SITE_CFG}) echo ${SITE_CFG} @@ -115,6 +120,11 @@ jobs: echo "test_data_dir = ${HOME}/iris-test-data/test_data" >> ${SITE_CFG} echo "doc_dir = ${GITHUB_WORKSPACE}/docs" >> ${SITE_CFG} cat ${SITE_CFG} + mkdir -p $(dirname ${MPL_RC}) + echo ${MPL_RC} + echo "backend : agg" >> ${MPL_RC} + echo "image.cmap : viridis" >> ${MPL_RC} + cat ${MPL_RC} - name: "iris ${{ matrix.session }}" env: diff --git a/README.md b/README.md index 1217cd9b38..ac2781f469 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,6 @@ ci-tests - -ci-docs-tests Documentation Status diff --git a/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py b/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py index efcbde8965..4ffeb7a67a 100644 --- a/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py +++ b/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py @@ -51,7 +51,7 @@ def _members(self): self.assertEqual(self.names(Metadata.__mro__), expected) emsg = ( "Can't instantiate abstract class .* with abstract " - "methods _members" + "method.* _members" ) with self.assertRaisesRegex(TypeError, emsg): _ = Metadata() diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index 5f707f91db..f8f1ff1d69 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -1064,7 +1064,7 @@ class Test___init____abstractmethod(tests.IrisTest): def test(self): emsg = ( "Can't instantiate abstract class Coord with abstract" - " methods __init__" + " method.* __init__" ) with self.assertRaisesRegex(TypeError, emsg): _ = Coord(points=[0, 1]) diff --git a/requirements/ci/iris.yml b/requirements/ci/iris.yml index a76932b56e..26a7748cce 120000 --- a/requirements/ci/iris.yml +++ b/requirements/ci/iris.yml @@ -1 +1 @@ -py38.yml \ No newline at end of file +py39.yml \ No newline at end of file diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index e3604c3ea8..39a203a241 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -73,7 +73,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.ta https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 -https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.5-he0ac6c6_0.tar.bz2#63fbbbc5bd02f007a88ef7c4b58e9a62 +https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 @@ -88,7 +88,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar. https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a -https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.5-default_h3a83d3e_0.tar.bz2#493aec1de0f0e09e921eff6206cafff6 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f @@ -99,7 +99,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.38.5-h4ff8645_0.tar.bz2#a1448f0c31baec3946d2dcf09f905c9e +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -113,7 +113,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.70.2-h780b84a_4.tar.bz2#c66c6df8ef582a3b78702201b1eb8e94 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f -https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.5-default_h2e3cab8_0.tar.bz2#8b1cd508fcf54a5c8c5766c549272b6e +https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 @@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py38h578d9bd_0.tar.bz2#4dbffb6d975f26cd71fb27aa20fc4761 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.14.1-py38h578d9bd_0.tar.bz2#41427ff3fd8d35e5ab1cdcec4d94ea6b +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.0-py38h578d9bd_0.tar.bz2#87e1283dc05d80ceaa21ebd8550722ce https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30_0.tar.bz2#bcc387154aae535f8b4f84822621b5f7 @@ -210,7 +210,7 @@ https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.3.0-hf9f4e7c_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.6.0-pyhd8ed1ab_0.tar.bz2#0941325bf48969e2b3b19d0951740950 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 @@ -245,7 +245,7 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.0-pyhd8ed1ab_0.tar.bz2#80c4854bb29f39f202819c4d4294d7c5 +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.0-pyhd8ed1ab_1.tar.bz2#5db4d14905f98da161e2153b1c9d2bce https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock new file mode 100644 index 0000000000..a32ca66ce5 --- /dev/null +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -0,0 +1,254 @@ +# Generated by conda-lock. +# platform: linux-64 +# input_hash: 8892442b4a37d8bf563ef33883a79ad3f126a997042a926a12309d6a655e8b70 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 +https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548 +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 +https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f +https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d11cf000ee8b976b5ce3b425477b5689 +https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d +https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 +https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 +https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 +https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c +https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 +https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 +https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229 +https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f +https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_1.tar.bz2#58eaff4f91891978af3625e7bbf958af +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1p-h166bdaf_0.tar.bz2#995e819f901ee0c4411e4f50d9b31a82 +https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa +https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 +https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 +https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a +https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.0.10-h7f98852_0.tar.bz2#d6b0b50b49eccfe0be0373be628be0f3 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908 +https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 +https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 +https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 +https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 +https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 +https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 +https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_1.tar.bz2#3db63b53bb194dbaa7dc3d8833e98da2 +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a +https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c +https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.70.2-h780b84a_4.tar.bz2#c66c6df8ef582a3b78702201b1eb8e94 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f +https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b +https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 +https://conda.anaconda.org/conda-forge/linux-64/python-3.9.13-h9a8a25e_0_cpython.tar.bz2#69bc307cc4d7396c5fccb26bbcc9c379 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb +https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d +https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 +https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b +https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 +https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 +https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_0.tar.bz2#42d593cd1b23a43d7ac7edd9a2e84fa9 +https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 +https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 +https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.1-pyhd8ed1ab_0.tar.bz2#d821b295c4bd18ad27e1e19543a5784a +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 +https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 +https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 +https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c +https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb +https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 +https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py39h4bc2ebd_0.tar.bz2#f6191bf565dee581e77549d63737751c +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.3-py39hf939315_0.tar.bz2#d8bdd7cb2f0e571665bbf0ec09c94da9 +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_1.tar.bz2#a0475f4b801777d641058ca5ff08f07f +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py39hba7629e_0.tar.bz2#0e48a6f61637735a88644359d90f5f1e +https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 +https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.1.1-py39hae2aec6_1.tar.bz2#9039c6b86ddb65d7e8bf464337ae9053 +https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 +https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 +https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar.bz2#d34b97a2386932b97c7cb80916a673e7 +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py39hf3d152e_0.tar.bz2#ddfb37b6e91b8c41be3976d19af47ade +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py39hb9d737c_3.tar.bz2#5e13a2d214ed4184969df363a1aab420 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.0-py39hf3d152e_0.tar.bz2#baf424316921dd61ac57a1c21ddf2711 +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py39hd257fcd_1.tar.bz2#d7acdd66aea024ddf51a957e006305f0 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py39hd97740a_0.tar.bz2#11780968ae65fdeb1a0bc294d211597d +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py39hb9d737c_0.tar.bz2#43f3c538bbcf6ed0da225891e11bf0a8 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.3.0-hf9f4e7c_0.tar.bz2#2a9c6660562d7e3fdeda0f0159e1046d +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 +https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39he49c0e8_0.tar.bz2#b1b4cc4216e555168e88d6a2b1914af1 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h4fbd0eb_2.tar.bz2#d4039ba3e744a45777763df7a22250f9 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py39he80948d_2.tar.bz2#e93686e0252282dd8ccba5c6cbcd8295 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py39hce5d2b2_2.tar.bz2#599cee42b88c9afba10033c01fb40d74 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_0.tar.bz2#ab1bcd0fd24e375f16d662e4cc783cab +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py39h64b754b_101.tar.bz2#75875ec36c7d80400bd7bb0b23fab429 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py39h5a03fae_1.tar.bz2#bff0f5f688af0dc337d11517c5a46402 +https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py39ha2ae0e9_6.tar.bz2#34e933f8cd0e61990d50859a3eadca9e +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py39hf3d152e_0.tar.bz2#dddc330e78d96d59c0cf68bf39dc09b1 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py39h18e9c17_1.tar.bz2#111647767bc84d4795f829bdc07dbb27 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py39hf3d152e_0.tar.bz2#d65d073d186977a2a9a9d5a68b2b77ef +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.0-pyhd8ed1ab_1.tar.bz2#5db4d14905f98da161e2153b1c9d2bce +https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 +https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba +https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml new file mode 100644 index 0000000000..3d0dab94f7 --- /dev/null +++ b/requirements/ci/py39.yml @@ -0,0 +1,49 @@ +name: iris-dev + +channels: + - conda-forge + +dependencies: + - python =3.9 + +# Setup dependencies. + - setuptools >=40.8.0 + +# Core dependencies. + - cartopy >=0.20 + - cf-units >=3 + - cftime >=1.5 + - dask-core >=2 + - matplotlib + - netcdf4 + - numpy >=1.19 + - python-xxhash + - pyproj + - scipy + +# Optional dependencies. + - esmpy >=7.0 + - graphviz + - iris-sample-data >=2.4.0 + - mo_pack + - nc-time-axis >=1.4 + - pandas + - pip + - python-stratify + +# Test dependencies. + - filelock + - imagehash >=4.0 + - pre-commit + - psutil + - pytest + - pytest-xdist + - requests + +# Documentation dependencies. + - sphinx + - sphinxcontrib-napoleon + - sphinx-copybutton + - sphinx-gallery + - sphinx-panels + - pydata-sphinx-theme = 0.8.1 From 92f58408cc86d8c5f879da11327472fca304103b Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 28 Jun 2022 15:50:28 +0100 Subject: [PATCH 154/319] post #4759 review actions (#4833) --- lib/iris/tests/graphics/__init__.py | 8 ++++---- lib/iris/tests/graphics/recreate_imagerepo.py | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/iris/tests/graphics/__init__.py b/lib/iris/tests/graphics/__init__.py index 2ef635128e..a083de3934 100755 --- a/lib/iris/tests/graphics/__init__.py +++ b/lib/iris/tests/graphics/__init__.py @@ -57,9 +57,9 @@ _lock = threading.Lock() #: Default perceptual hash size. -_HASH_SIZE = 16 +HASH_SIZE = 16 #: Default maximum perceptual hash hamming distance. -_HAMMING_DISTANCE = 2 +HAMMING_DISTANCE = 2 # Prefix for image test results (that aren't yet verified as good to add to # reference images) RESULT_PREFIX = "result-" @@ -135,7 +135,7 @@ def get_phash(input: Path) -> str: from PIL import Image import imagehash - return imagehash.phash(Image.open(input), hash_size=_HASH_SIZE) + return imagehash.phash(Image.open(input), hash_size=HASH_SIZE) def generate_repo_from_baselines(baseline_image_dir: Path) -> Dict[str, str]: @@ -220,7 +220,7 @@ def _create_missing(phash: str) -> None: # Calculate hamming distance vector for the result hash. distance = expected - phash - if distance > _HAMMING_DISTANCE: + if distance > HAMMING_DISTANCE: if dev_mode: _create_missing(phash) else: diff --git a/lib/iris/tests/graphics/recreate_imagerepo.py b/lib/iris/tests/graphics/recreate_imagerepo.py index 756e48688d..02ddaad2cb 100755 --- a/lib/iris/tests/graphics/recreate_imagerepo.py +++ b/lib/iris/tests/graphics/recreate_imagerepo.py @@ -29,9 +29,9 @@ def update_json(baseline_image_dir: Path, dry_run: bool = False): ) print(msg) else: - for key in set(repo.keys()) | set(suggested_repo.keys()): - old_val = repo.get(key, None) - new_val = suggested_repo.get(key, None) + for key in sorted(set(repo.keys()) | set(suggested_repo.keys())): + old_val = repo.get(key) + new_val = suggested_repo.get(key) if old_val is None: repo[key] = suggested_repo[key] print(key) From b21edd05a12f8e3ae69ca441cf8615f15283f142 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 28 Jun 2022 15:57:50 +0100 Subject: [PATCH 155/319] pytest less verbose (#4837) --- .github/workflows/ci-tests.yml | 2 +- pyproject.toml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index abe9fd871a..9277451efd 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -23,7 +23,7 @@ concurrency: jobs: tests: - name: "${{ matrix.session }} py${{ matrix.python-version }} ${{ matrix.os }}" + name: "${{ matrix.session }} (py${{ matrix.python-version }} ${{ matrix.os }})" runs-on: ${{ matrix.os }} diff --git a/pyproject.toml b/pyproject.toml index 26e6ae727a..d72d9aba3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,3 +37,7 @@ extend_skip = [ ] skip_gitignore = "True" verbose = "False" + +[tool.pytest.ini_options] +addopts = "-ra -q" +testpaths = "lib/iris" From ded2a4de69169d4b05fbdd3a8a4acc9741b01c9c Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 28 Jun 2022 16:21:22 +0100 Subject: [PATCH 156/319] Remove redundant testing assert methods (#4838) * remove assertWarnsRegexp * remove assertStringEqual * post rebase tidyings * whatsnew --- docs/src/developers_guide/testing_tools.rst | 3 +- docs/src/whatsnew/latest.rst | 2 ++ lib/iris/tests/__init__.py | 29 ------------------- lib/iris/tests/integration/test_netcdf.py | 14 ++++----- lib/iris/tests/test_cf.py | 2 +- lib/iris/tests/test_coordsystem.py | 6 ++-- .../representation/test_CubeRepresentation.py | 2 +- .../nc_load_rules/actions/__init__.py | 6 ++-- .../actions/test__grid_mappings.py | 14 +++++---- .../actions/test__hybrid_formulae.py | 4 +-- .../actions/test__time_coords.py | 2 +- .../unit/fileformats/netcdf/test_Saver.py | 15 ++++++---- .../tests/unit/fileformats/pp/test_PPField.py | 6 ++-- .../tests/unit/io/test_expand_filespecs.py | 2 +- lib/iris/tests/unit/test_Future.py | 3 +- 15 files changed, 45 insertions(+), 65 deletions(-) diff --git a/docs/src/developers_guide/testing_tools.rst b/docs/src/developers_guide/testing_tools.rst index f4faee084f..dd628d37fc 100755 --- a/docs/src/developers_guide/testing_tools.rst +++ b/docs/src/developers_guide/testing_tools.rst @@ -24,8 +24,7 @@ Custom assertions ================= :class:`iris.tests.IrisTest` supports a variety of custom unittest-style -assertions, such as :meth:`~iris.tests.IrisTest_nometa.assertStringEqual`, -:meth:`~iris.tests.IrisTest_nometa.assertArrayEqual`, +assertions, such as :meth:`~iris.tests.IrisTest_nometa.assertArrayEqual`, :meth:`~iris.tests.IrisTest_nometa.assertArrayAlmostEqual`. .. _create-missing: diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 0e4d51ffec..8dacecfd32 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -237,6 +237,8 @@ This document explains the changes made to Iris for this release unlimited time dimension. (:pull:`4827`) +#. `@rcomer`_ removed some now redundant testing methods. (:pull:`4838`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py index 99e2a5604b..75b7882221 100644 --- a/lib/iris/tests/__init__.py +++ b/lib/iris/tests/__init__.py @@ -223,25 +223,6 @@ def get_result_path(relative_path): relative_path = os.path.join(*relative_path) return os.path.abspath(os.path.join(_RESULT_PATH, relative_path)) - def assertStringEqual( - self, reference_str, test_str, type_comparison_name="strings" - ): - if reference_str != test_str: - diff = "\n".join( - difflib.unified_diff( - reference_str.splitlines(), - test_str.splitlines(), - "Reference", - "Test result", - "", - "", - 0, - ) - ) - self.fail( - "{} do not match:\n{}".format(type_comparison_name, diff) - ) - def result_path(self, basename=None, ext=""): """ Return the full path to a test result, generated from the \ @@ -560,16 +541,6 @@ def _recordWarningMatches(self, expected_regexp=""): expr = re.compile(expected_regexp) matches.extend(message for message in messages if expr.search(message)) - @contextlib.contextmanager - def assertWarnsRegexp(self, expected_regexp=""): - # Check that a warning is raised matching a given expression. - with self._recordWarningMatches(expected_regexp) as matches: - yield - - msg = "Warning matching '{}' not raised." - msg = msg.format(expected_regexp) - self.assertTrue(matches, msg) - @contextlib.contextmanager def assertLogs(self, logger=None, level=None, msg_regex=None): """ diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index ca8c4c7697..3feb637bf8 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -599,15 +599,15 @@ def test_load_datum_wkt(self): cube = iris.load_cube(nc_path) test_crs = cube.coord("projection_y_coordinate").coord_system actual = str(test_crs.as_cartopy_crs().datum) - self.assertStringEqual(expected, actual) + self.assertMultiLineEqual(expected, actual) def test_no_load_datum_wkt(self): nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) - with self.assertWarnsRegexp("iris.FUTURE.datum_support"): + with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): cube = iris.load_cube(nc_path) test_crs = cube.coord("projection_y_coordinate").coord_system actual = str(test_crs.as_cartopy_crs().datum) - self.assertStringEqual(actual, "unknown") + self.assertMultiLineEqual(actual, "unknown") def test_load_datum_cf_var(self): expected = "OSGB 1936" @@ -616,15 +616,15 @@ def test_load_datum_cf_var(self): cube = iris.load_cube(nc_path) test_crs = cube.coord("projection_y_coordinate").coord_system actual = str(test_crs.as_cartopy_crs().datum) - self.assertStringEqual(expected, actual) + self.assertMultiLineEqual(expected, actual) def test_no_load_datum_cf_var(self): nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) - with self.assertWarnsRegexp("iris.FUTURE.datum_support"): + with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): cube = iris.load_cube(nc_path) test_crs = cube.coord("projection_y_coordinate").coord_system actual = str(test_crs.as_cartopy_crs().datum) - self.assertStringEqual(actual, "unknown") + self.assertMultiLineEqual(actual, "unknown") def test_save_datum(self): expected = "OSGB 1936" @@ -663,7 +663,7 @@ def test_save_datum(self): test_crs = cube.coord("projection_y_coordinate").coord_system actual = str(test_crs.as_cartopy_crs().datum) - self.assertStringEqual(expected, actual) + self.assertMultiLineEqual(expected, actual) def _get_scale_factor_add_offset(cube, datatype): diff --git a/lib/iris/tests/test_cf.py b/lib/iris/tests/test_cf.py index fa5e2a008d..034fb1dbda 100644 --- a/lib/iris/tests/test_cf.py +++ b/lib/iris/tests/test_cf.py @@ -295,7 +295,7 @@ def test_destructor(self): except OSError: pass buf.seek(0) - self.assertStringEqual("", buf.read()) + self.assertMultiLineEqual("", buf.read()) @tests.skip_data diff --git a/lib/iris/tests/test_coordsystem.py b/lib/iris/tests/test_coordsystem.py index 4497e77903..7cd15297cc 100644 --- a/lib/iris/tests/test_coordsystem.py +++ b/lib/iris/tests/test_coordsystem.py @@ -548,18 +548,18 @@ class Test_Datums(tests.IrisTest): def test_default_none(self): cs = GeogCS(6543210, 6500000) # Arbitrary radii cartopy_crs = cs.as_cartopy_crs() - self.assertStringEqual(cartopy_crs.datum.name, "unknown") + self.assertMultiLineEqual(cartopy_crs.datum.name, "unknown") def test_set_persist(self): cs = GeogCS.from_datum(datum="WGS84") cartopy_crs = cs.as_cartopy_crs() - self.assertStringEqual( + self.assertMultiLineEqual( cartopy_crs.datum.name, "World Geodetic System 1984" ) cs = GeogCS.from_datum(datum="OSGB36") cartopy_crs = cs.as_cartopy_crs() - self.assertStringEqual(cartopy_crs.datum.name, "OSGB 1936") + self.assertMultiLineEqual(cartopy_crs.datum.name, "OSGB 1936") if __name__ == "__main__": diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py index 99f7e7f2dd..e6b1425110 100644 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py @@ -28,7 +28,7 @@ def setUp(self): def test_cube_attributes(self): self.assertEqual(id(self.cube), self.representer.cube_id) - self.assertStringEqual(str(self.cube), self.representer.cube_str) + self.assertMultiLineEqual(str(self.cube), self.representer.cube_str) def test__heading_contents(self): content = set(self.representer.sections_data.values()) diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py index 7bcb451d95..c18bdb8399 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py @@ -117,7 +117,7 @@ def load_cube_from_cdl(self, cdl_string, cdl_path, nc_path): # Always returns a single cube. return cube - def run_testcase(self, warning=None, **testcase_kwargs): + def run_testcase(self, warning_regex=None, **testcase_kwargs): """ Run a testcase with chosen options, returning a test cube. @@ -133,10 +133,10 @@ def run_testcase(self, warning=None, **testcase_kwargs): print(cdl_string) print("------\n") - if warning is None: + if warning_regex is None: context = self.assertNoWarningsRegexp() else: - context = self.assertWarnsRegexp(warning) + context = self.assertWarnsRegex(UserWarning, warning_regex) with context: cube = self.load_cube_from_cdl(cdl_string, cdl_path, nc_path) diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py index a56ef5e754..a367e7709c 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py @@ -407,7 +407,9 @@ def test_latlon_bad_gridmapping_varname(self): # Notes: # * behaviours all the same as 'test_bad_gridmapping_nameproperty' warning = "Missing.*grid mapping variable 'grid'" - result = self.run_testcase(warning=warning, gridmapvar_name="grid_2") + result = self.run_testcase( + warning_regex=warning, gridmapvar_name="grid_2" + ) self.check_result(result, cube_no_cs=True) def test_latlon_bad_latlon_unit(self): @@ -633,7 +635,7 @@ def test_mapping__mismatch__latlon_coords_missing_system(self): # * coords built : lat + lon, with no coord-system (see above) warning = "Missing.*grid mapping variable 'grid'" result = self.run_testcase( - warning=warning, + warning_regex=warning, gridmapvar_name="moved", xco_name="longitude", xco_units="degrees_east", @@ -690,7 +692,7 @@ def test_mapping__mismatch__rotated_coords_missing_system(self): # * coords built : rotated lat + lon, with no coord-system (see above) warning = "Missing.*grid mapping variable 'grid'" result = self.run_testcase( - warning=warning, + warning_regex=warning, gridmapvar_name="moved", xco_name="grid_longitude", xco_units="degrees", @@ -752,7 +754,7 @@ def test_mapping__mismatch__nonll_coords_missing_system(self): # * effectively, just like previous 2 cases warning = "Missing.*grid mapping variable 'grid'" result = self.run_testcase( - warning=warning, + warning_regex=warning, gridmapvar_name="moved", xco_name="projection_x", xco_units="m", @@ -872,7 +874,9 @@ def test_nondim_lats(self): # * in terms of rule triggering, this is not distinct from the # "normal" case : but latitude is now created as an aux-coord. warning = "must be.* monotonic" - result = self.run_testcase(warning=warning, yco_values=[0.0, 0.0]) + result = self.run_testcase( + warning_regex=warning, yco_values=[0.0, 0.0] + ) self.check_result(result, yco_is_aux=True) diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py index 3413090a3d..d962fc2758 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py @@ -207,7 +207,7 @@ def test_unrecognised_verticaltype(self): result = self.run_testcase( formula_root_name="unknown", term_names=["a", "b"], - warning="Ignored formula of unrecognised type: 'unknown'.", + warning_regex="Ignored formula of unrecognised type: 'unknown'.", ) # Check that it picks up the terms, but *not* the factory root coord, # which is simply discarded. @@ -226,7 +226,7 @@ def test_two_formulae(self): extra_type = "ocean_sigma_coordinate" result = self.run_testcase( - extra_formula_type=extra_type, warning=warning + extra_formula_type=extra_type, warning_regex=warning ) # NOTE: FOR NOW, check expected behaviour : only one factory will be # built, but there are coordinates (terms) for both types. diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py index 47760aadcb..59ffa30684 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py @@ -313,7 +313,7 @@ def test_dim_nonmonotonic(self): # 002 : fc_provides_coordinate_(time[[_period]]) # 003 : fc_build_coordinate_(time[[_period]]) msg = "Failed to create.* dimension coordinate" - result = self.run_testcase(values_all_zero=True, warning=msg) + result = self.run_testcase(values_all_zero=True, warning_regex=msg) self.check_result(result, "aux") def test_dim_fails_typeident(self): diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py index 37bcee7da2..e17082b5e9 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py @@ -549,8 +549,9 @@ def test_contains_fill_value_passed(self): # Test that a warning is raised if the data contains the fill value. cube = self._make_cube(">f4") fill_value = 1 - with self.assertWarnsRegexp( - "contains unmasked data points equal to the fill-value" + with self.assertWarnsRegex( + UserWarning, + "contains unmasked data points equal to the fill-value", ): with self._netCDF_var(cube, fill_value=fill_value): pass @@ -560,8 +561,9 @@ def test_contains_fill_value_byte(self): # when it is of a byte type. cube = self._make_cube(">i1") fill_value = 1 - with self.assertWarnsRegexp( - "contains unmasked data points equal to the fill-value" + with self.assertWarnsRegex( + UserWarning, + "contains unmasked data points equal to the fill-value", ): with self._netCDF_var(cube, fill_value=fill_value): pass @@ -571,8 +573,9 @@ def test_contains_default_fill_value(self): # value if no fill_value argument is supplied. cube = self._make_cube(">f4") cube.data[0, 0] = nc.default_fillvals["f4"] - with self.assertWarnsRegexp( - "contains unmasked data points equal to the fill-value" + with self.assertWarnsRegex( + UserWarning, + "contains unmasked data points equal to the fill-value", ): with self._netCDF_var(cube): pass diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPField.py b/lib/iris/tests/unit/fileformats/pp/test_PPField.py index 266c26aa59..d1e194aa3c 100644 --- a/lib/iris/tests/unit/fileformats/pp/test_PPField.py +++ b/lib/iris/tests/unit/fileformats/pp/test_PPField.py @@ -92,7 +92,7 @@ def field_checksum(data): data_64 = np.linspace(0, 1, num=10, endpoint=False).reshape(2, 5) checksum_32 = field_checksum(data_64.astype(">f4")) msg = "Downcasting array precision from float64 to float32 for save." - with self.assertWarnsRegexp(msg): + with self.assertWarnsRegex(UserWarning, msg): checksum_64 = field_checksum(data_64.astype(">f8")) self.assertEqual(checksum_32, checksum_64) @@ -105,7 +105,7 @@ def test_masked_mdi_value_warning(self): [1.0, field.bmdi, 3.0], dtype=np.float32 ) msg = "PPField data contains unmasked points" - with self.assertWarnsRegexp(msg): + with self.assertWarnsRegex(UserWarning, msg): with self.temp_filename(".pp") as temp_filename: with open(temp_filename, "wb") as pp_file: field.save(pp_file) @@ -117,7 +117,7 @@ def test_unmasked_mdi_value_warning(self): # Make float32 data, as float64 default produces an extra warning. field.data = np.array([1.0, field.bmdi, 3.0], dtype=np.float32) msg = "PPField data contains unmasked points" - with self.assertWarnsRegexp(msg): + with self.assertWarnsRegex(UserWarning, msg): with self.temp_filename(".pp") as temp_filename: with open(temp_filename, "wb") as pp_file: field.save(pp_file) diff --git a/lib/iris/tests/unit/io/test_expand_filespecs.py b/lib/iris/tests/unit/io/test_expand_filespecs.py index c28e4f9b2e..0299a415b4 100644 --- a/lib/iris/tests/unit/io/test_expand_filespecs.py +++ b/lib/iris/tests/unit/io/test_expand_filespecs.py @@ -94,7 +94,7 @@ def test_files_and_none(self): .format(self.tmpdir) ) - self.assertStringEqual(str(err.exception), expected) + self.assertMultiLineEqual(str(err.exception), expected) if __name__ == "__main__": diff --git a/lib/iris/tests/unit/test_Future.py b/lib/iris/tests/unit/test_Future.py index dddc752b6f..f0c161b0c4 100644 --- a/lib/iris/tests/unit/test_Future.py +++ b/lib/iris/tests/unit/test_Future.py @@ -12,6 +12,7 @@ import warnings from iris import Future +import iris._deprecation def patched_future(value=False, deprecated=False, error=False): @@ -45,7 +46,7 @@ def test_valid_setting(self): def test_deprecated_warning(self): future = patched_future(deprecated=True, error=False) msg = "'Future' property 'example_future_flag' is deprecated" - with self.assertWarnsRegexp(msg): + with self.assertWarnsRegex(iris._deprecation.IrisDeprecation, msg): future.example_future_flag = False def test_deprecated_error(self): From 32b515b943610d3d862955d3ea9131e8244f0d91 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Tue, 28 Jun 2022 17:43:34 +0100 Subject: [PATCH 157/319] NetCDF Generation Adjustments (#4836) --- .../benchmarks/experimental/ugrid/regions_combine.py | 3 +-- benchmarks/benchmarks/load/ugrid.py | 12 +++++++----- benchmarks/benchmarks/save.py | 3 +-- docs/src/whatsnew/latest.rst | 6 ++---- .../stock/file_headers/xios_2D_face_half_levels.cdl | 4 ---- .../stock/file_headers/xios_3D_face_full_levels.cdl | 4 ---- .../stock/file_headers/xios_3D_face_half_levels.cdl | 4 ---- lib/iris/tests/stock/netcdf.py | 4 ++-- 8 files changed, 13 insertions(+), 27 deletions(-) diff --git a/benchmarks/benchmarks/experimental/ugrid/regions_combine.py b/benchmarks/benchmarks/experimental/ugrid/regions_combine.py index 8ebf210416..3b2d77a80a 100644 --- a/benchmarks/benchmarks/experimental/ugrid/regions_combine.py +++ b/benchmarks/benchmarks/experimental/ugrid/regions_combine.py @@ -11,8 +11,7 @@ * minimal: enables detection of regressions in parts of the run-time that do NOT scale with data size. * large: large enough to exclusively detect regressions in parts of the - run-time that scale with data size. Aim for benchmark time ~20x - that of the minimal benchmark. + run-time that scale with data size. """ import os diff --git a/benchmarks/benchmarks/load/ugrid.py b/benchmarks/benchmarks/load/ugrid.py index 8227a4c5a0..350a78e128 100644 --- a/benchmarks/benchmarks/load/ugrid.py +++ b/benchmarks/benchmarks/load/ugrid.py @@ -10,8 +10,7 @@ * minimal: enables detection of regressions in parts of the run-time that do NOT scale with data size. * large: large enough to exclusively detect regressions in parts of the - run-time that scale with data size. Aim for benchmark time ~20x - that of the minimal benchmark. + run-time that scale with data size. """ @@ -39,7 +38,7 @@ def load_mesh(*args, **kwargs): class BasicLoading: - params = [1, int(4.1e6)] + params = [1, int(2e5)] param_names = ["number of faces"] def setup_common(self, **kwargs): @@ -58,6 +57,9 @@ def time_load_mesh(self, *args): class BasicLoadingTime(BasicLoading): """Same as BasicLoading, but scaling over a time series - an unlimited dimension.""" + # NOTE iris#4834 - careful how big the time dimension is (time dimension + # is UNLIMITED). + param_names = ["number of time steps"] def setup(self, *args): @@ -75,7 +77,7 @@ class DataRealisation: warmup_time = 0.0 timeout = 300.0 - params = [1, int(4e6)] + params = [1, int(2e5)] param_names = ["number of faces"] def setup_common(self, **kwargs): @@ -102,7 +104,7 @@ def setup(self, *args): class Callback: - params = [1, int(4.5e6)] + params = [1, int(2e5)] param_names = ["number of faces"] def setup_common(self, **kwargs): diff --git a/benchmarks/benchmarks/save.py b/benchmarks/benchmarks/save.py index bb1d2d8c82..3551c72528 100644 --- a/benchmarks/benchmarks/save.py +++ b/benchmarks/benchmarks/save.py @@ -10,8 +10,7 @@ * minimal: enables detection of regressions in parts of the run-time that do NOT scale with data size. * large: large enough to exclusively detect regressions in parts of the - run-time that scale with data size. Aim for benchmark time ~20x - that of the minimal benchmark. + run-time that scale with data size. """ from iris import save diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 8dacecfd32..849d44a44c 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -232,10 +232,8 @@ This document explains the changes made to Iris for this release bin in the system PATH. (:pull:`4794`) -#. `@trexfeathers`_ and `@pp-mo`_ fixed the CDL headers for - :mod:`iris.tests.stock.netcdf` to allow generation of NetCDF-4 files with an - unlimited time dimension. - (:pull:`4827`) +#. `@trexfeathers`_ and `@pp-mo`_ improved generation of stock NetCDF files. + (:pull:`4827`, :pull:`4836`) #. `@rcomer`_ removed some now redundant testing methods. (:pull:`4838`) diff --git a/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl b/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl index ba3522b491..b135546f2d 100644 --- a/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl @@ -55,8 +55,4 @@ variables: :name = "${DATASET_NAME}" ; // original name = "lfric_ngvat_2D_1t_face_half_levels_main_conv_rain" :Conventions = "UGRID" ; - -// data -data: - time_instant = 0 ; } diff --git a/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl b/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl index a87e3055c9..e4f32de7b7 100644 --- a/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl @@ -58,8 +58,4 @@ variables: :name = "${DATASET_NAME}" ; // original name = "lfric_ngvat_3D_1t_full_level_face_grid_main_u3" :Conventions = "UGRID" ; - -// data -data: - time_instant = 0 ; } diff --git a/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl b/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl index f9c9c148dd..a193dbe451 100644 --- a/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl @@ -58,8 +58,4 @@ variables: :name = "${DATASET_NAME}" ; // original name = "lfric_ngvat_3D_1t_half_level_face_grid_derived_theta_in_w3" :Conventions = "UGRID" ; - -// data -data: - time_instant = 0 ; } diff --git a/lib/iris/tests/stock/netcdf.py b/lib/iris/tests/stock/netcdf.py index c5ec5ce446..8a448f7d34 100644 --- a/lib/iris/tests/stock/netcdf.py +++ b/lib/iris/tests/stock/netcdf.py @@ -51,13 +51,13 @@ def ncgen_from_cdl( f_out.write(cdl_str) if cdl_path: # Create netcdf from stored CDL file. - call_args = [NCGEN_PATHSTR, cdl_path, "-k4", "-o", nc_path] + call_args = [NCGEN_PATHSTR, cdl_path, "-k3", "-o", nc_path] call_kwargs = {} else: # No CDL file : pipe 'cdl_str' directly into the ncgen program. if not cdl_str: raise ValueError("Must provide either 'cdl_str' or 'cdl_path'.") - call_args = [NCGEN_PATHSTR, "-k4", "-o", nc_path] + call_args = [NCGEN_PATHSTR, "-k3", "-o", nc_path] call_kwargs = dict(input=cdl_str, encoding="ascii") subprocess.run(call_args, check=True, **call_kwargs) From 07b223ae92847cb5ce128aea8284e25d20874ccd Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 29 Jun 2022 13:31:44 +0100 Subject: [PATCH 158/319] remove pillow pin from setup.cfg (#4839) --- setup.cfg | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 233b9241d6..116095e64a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -75,7 +75,6 @@ docs = test = filelock imagehash>=4.0 - pillow<7 pre-commit requests pytest From 44fae030e4bf1404df84b67b6d715a972c85e37f Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 30 Jun 2022 15:03:33 +0100 Subject: [PATCH 159/319] Support python 3.10 (#4840) * extend ci support for py310 * fix Cell.__hash__ py310 nan behaviour * review actions --- .github/workflows/ci-tests.yml | 5 +- .github/workflows/refresh-lockfiles.yml | 2 +- lib/iris/coords.py | 19 +- requirements/ci/iris.yml | 2 +- requirements/ci/nox.lock/py310-linux-64.lock | 254 +++++++++++++++++++ requirements/ci/nox.lock/py38-linux-64.lock | 4 +- requirements/ci/nox.lock/py39-linux-64.lock | 4 +- requirements/ci/py310.yml | 49 ++++ 8 files changed, 331 insertions(+), 8 deletions(-) create mode 100644 requirements/ci/nox.lock/py310-linux-64.lock create mode 100644 requirements/ci/py310.yml diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 9277451efd..4cdc3f5e94 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -35,9 +35,12 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest"] - python-version: ["3.9"] + python-version: ["3.10"] session: ["tests", "doctest", "gallery", "linkcheck"] include: + - os: "ubuntu-latest" + python-version: "3.9" + session: "tests" - os: "ubuntu-latest" python-version: "3.8" session: "tests" diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index e817131c5e..72b330c815 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -33,7 +33,7 @@ jobs: steps: - uses: actions/checkout@v3 - id: get_py - run: echo "::set-output name=matrix::$(ls -1 requirements/ci/py??.yml | xargs -n1 basename | sed 's/....$//' | jq -cnR '[inputs]')" + run: echo "::set-output name=matrix::$(ls -1 requirements/ci/py*.yml | xargs -n1 basename | sed 's/....$//' | jq -cnR '[inputs]')" gen_lockfiles: # this is a matrix job: it splits to create new lockfiles for each diff --git a/lib/iris/coords.py b/lib/iris/coords.py index d176a0bb20..48fabfaf64 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -1345,7 +1345,24 @@ def __add__(self, mod): return Cell(point, bound) def __hash__(self): - return super().__hash__() + # Required for >py39 and >np1.22.x due to changes in Cell behaviour for + # point=np.nan, as calling super().__hash__() returns a different + # hash 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. + # 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 + + try: + point = "nan" if np.isnan(self.point) else self.point + except TypeError: + point = self.point + return hash((point,)) def __eq__(self, other): """ diff --git a/requirements/ci/iris.yml b/requirements/ci/iris.yml index 26a7748cce..1e473d36d5 120000 --- a/requirements/ci/iris.yml +++ b/requirements/ci/iris.yml @@ -1 +1 @@ -py39.yml \ No newline at end of file +py310.yml \ No newline at end of file diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock new file mode 100644 index 0000000000..1b1c973e15 --- /dev/null +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -0,0 +1,254 @@ +# Generated by conda-lock. +# platform: linux-64 +# input_hash: 6e4ca9b4ac3d61e97093bdf528ea99b9569aafeab377acd24324cf6d63069a97 +@EXPLICIT +https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 +https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb +https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 +https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7 +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 +https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab +https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d +https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548 +https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 +https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f +https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d11cf000ee8b976b5ce3b425477b5689 +https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d +https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 +https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 +https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 +https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c +https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 +https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 +https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d +https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 +https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229 +https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f +https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 +https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_1.tar.bz2#58eaff4f91891978af3625e7bbf958af +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 +https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1p-h166bdaf_0.tar.bz2#995e819f901ee0c4411e4f50d9b31a82 +https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa +https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 +https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 +https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a +https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.0.10-h7f98852_0.tar.bz2#d6b0b50b49eccfe0be0373be628be0f3 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.9-h7f98852_0.tar.bz2#bf6f803a544f26ebbdc3bfff272eb179 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.tar.bz2#be93aabceefa2fac576e971aef407908 +https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 +https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 +https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 +https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 +https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 +https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa +https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 +https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_1.tar.bz2#3db63b53bb194dbaa7dc3d8833e98da2 +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a +https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c +https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.70.2-h780b84a_4.tar.bz2#c66c6df8ef582a3b78702201b1eb8e94 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f +https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b +https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 +https://conda.anaconda.org/conda-forge/linux-64/python-3.10.5-h582c2e5_0_cpython.tar.bz2#ccbed83043b9b7b5693164591317f327 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb +https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d +https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 +https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b +https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 +https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 +https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 +https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_0.tar.bz2#42d593cd1b23a43d7ac7edd9a2e84fa9 +https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 +https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff +https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 +https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 +https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.1-pyhd8ed1ab_0.tar.bz2#d821b295c4bd18ad27e1e19543a5784a +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 +https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 +https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 +https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c +https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 +https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 +https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py310h0fdd8cc_0.tar.bz2#7b7366be82277a5a210e48cc6d25ce26 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.3-py310hbf28c38_0.tar.bz2#93b57192dd2e93b6d5ed00be96e8ae55 +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_1.tar.bz2#54eaedc01b1aee271e4f150d03ffc8b0 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py310h53a5b5f_0.tar.bz2#16493af3907dbd772904e15bf24948d8 +https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 +https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.1.1-py310he619898_1.tar.bz2#d7052b5cef119518ca362a2ff633a36d +https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 +https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 +https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.tar.bz2#378f2260e871f3ea46c6fa58d9f05277 +https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf +https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py310hff52083_0.tar.bz2#7fc5b1d6db9f6a9307330303a3edb04d +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py310h5764c6d_3.tar.bz2#8a5770e6392d29d99c9bc9c3635bba60 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.0-py310hff52083_0.tar.bz2#75742870c37ab7ef5ee61846bcd342a2 +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py310hde88566_1.tar.bz2#e9505227ecb7a00a4a9fffc7a9a98d14 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py310h597c629_0.tar.bz2#7b40622ed00061cc8f803c5ed3c62707 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py310h5764c6d_0.tar.bz2#b2171665e9cd3ba4114d90b8da6815c8 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.0-hf9f4e7c_0.tar.bz2#dbb42a46de29a600e71952dfdabfba7e +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d +https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310h7612f91_0.tar.bz2#14a7ea0620e4c0801bee756171f4dc03 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h7b2ee30_2.tar.bz2#a95baebc52f890fa8bf71f2d3f536ae1 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py310h122e73d_2.tar.bz2#f485cb3efb4c179928a96fb2e02e6f7e +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py310h96516ba_2.tar.bz2#595f4a5a69f91cd0f72e75417c2eeedb +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_0.tar.bz2#b038b2e97ae14fea11159dcb2c5abf0a +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py310hd7ca5b8_101.tar.bz2#39f597922f7db648da47b67829d71280 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py310hd8f1fbe_1.tar.bz2#21ae1ac216cfc85e24dc7c09570ddf31 +https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py310hb408dcc_6.tar.bz2#69abe28e5da19c0bc7efc798ecd86a27 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py310hff52083_0.tar.bz2#bcf63938923fd8c16c684a4e7157b062 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py310h29803b5_1.tar.bz2#d34f0b4c9ad5705d0ffe501d5d8aa26f +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_0.tar.bz2#0b90a9f77544f943264d47677d63ac9e +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.0-pyhd8ed1ab_1.tar.bz2#5db4d14905f98da161e2153b1c9d2bce +https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 +https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba +https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 39a203a241..e563cf9695 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -97,7 +97,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 @@ -206,7 +206,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.3.0-hf9f4e7c_0.tar.bz2#2a9c6660562d7e3fdeda0f0159e1046d +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.0-hf9f4e7c_0.tar.bz2#dbb42a46de29a600e71952dfdabfba7e https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index a32ca66ce5..6b8194e37f 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -98,7 +98,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.8.0-h4de3113_1.tar.bz2#175a746a43d42c053b91aa765fbc197d +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 @@ -207,7 +207,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py39hd97740a https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py39hb9d737c_0.tar.bz2#43f3c538bbcf6ed0da225891e11bf0a8 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.3.0-hf9f4e7c_0.tar.bz2#2a9c6660562d7e3fdeda0f0159e1046d +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.0-hf9f4e7c_0.tar.bz2#dbb42a46de29a600e71952dfdabfba7e https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml new file mode 100644 index 0000000000..02deb5525f --- /dev/null +++ b/requirements/ci/py310.yml @@ -0,0 +1,49 @@ +name: iris-dev + +channels: + - conda-forge + +dependencies: + - python =3.10 + +# Setup dependencies. + - setuptools >=40.8.0 + +# Core dependencies. + - cartopy >=0.20 + - cf-units >=3 + - cftime >=1.5 + - dask-core >=2 + - matplotlib + - netcdf4 + - numpy >=1.19 + - python-xxhash + - pyproj + - scipy + +# Optional dependencies. + - esmpy >=7.0 + - graphviz + - iris-sample-data >=2.4.0 + - mo_pack + - nc-time-axis >=1.4 + - pandas + - pip + - python-stratify + +# Test dependencies. + - filelock + - imagehash >=4.0 + - pre-commit + - psutil + - pytest + - pytest-xdist + - requests + +# Documentation dependencies. + - sphinx + - sphinxcontrib-napoleon + - sphinx-copybutton + - sphinx-gallery + - sphinx-panels + - pydata-sphinx-theme = 0.8.1 From 40e9a9ef73a8860a1aabbfcb09b6f61a799b1361 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 1 Jul 2022 15:17:27 +0100 Subject: [PATCH 160/319] update github app docs (#4844) --- docs/src/developers_guide/github_app.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/src/developers_guide/github_app.rst b/docs/src/developers_guide/github_app.rst index 338166fd76..402cfe0c75 100644 --- a/docs/src/developers_guide/github_app.rst +++ b/docs/src/developers_guide/github_app.rst @@ -8,6 +8,15 @@ Token GitHub App This section of the documentation is applicable only to GitHub `SciTools`_ Organisation **owners** and **administrators**. +.. note:: + + The ``iris-actions`` GitHub App has been rebranded with the more generic + name ``scitools-ci``, as the app can be used for any `SciTools`_ repository, + not just ``iris`` specifically. + + All of the following instructions are still applicable. + + This section describes how to create, configure, install and use our `SciTools`_ GitHub App for generating tokens for use with *GitHub Actions* (GHA). @@ -269,4 +278,4 @@ to generate a token for use with the `create-pull-request`_ GHA: .. _conda-lock: https://github.com/conda-incubator/conda-lock .. _create-pull-request: https://github.com/peter-evans/create-pull-request .. _github-app-token: https://github.com/tibdex/github-app-token -.. _refresh-lockfiles: https://github.com/SciTools/iris/blob/main/.github/workflows/refresh-lockfiles.yml \ No newline at end of file +.. _refresh-lockfiles: https://github.com/SciTools/iris/blob/main/.github/workflows/refresh-lockfiles.yml From 158b0151272836a0a3368ba52a2da81375511481 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 1 Jul 2022 16:43:33 +0100 Subject: [PATCH 161/319] Lockfile update and Gregorian to Standard calendar change (#4847) * sed gregorian * sed CALENDAR_GREGORIAN * rename cml files * revert whatsnew 2.1 and test_cf.py * revert spelling_allow * post rebase sed 'gregorian' * Updated environment lockfiles * cf-units minimum pin * review actions Co-authored-by: Lockfile bot --- .../general/plot_custom_file_loading.py | 4 +- docs/src/techpapers/um_files_loading.rst | 2 +- docs/src/userguide/subsetting_a_cube.rst | 14 +- docs/src/whatsnew/latest.rst | 13 +- lib/iris/cube.py | 6 +- lib/iris/fileformats/name_loaders.py | 4 +- lib/iris/fileformats/nimrod_load_rules.py | 2 +- lib/iris/fileformats/pp.py | 2 +- lib/iris/fileformats/pp_load_rules.py | 4 +- lib/iris/fileformats/pp_save_rules.py | 2 +- lib/iris/pandas.py | 6 +- lib/iris/plot.py | 4 +- .../concatenate/test_concatenate.py | 10 +- .../tests/integration/plot/test_netcdftime.py | 2 +- lib/iris/tests/integration/test_pp.py | 2 +- .../COLPEX/small_colpex_theta_p_alt.cml | 12 +- .../tests/results/FF/air_temperature_1.cml | 4 +- .../tests/results/FF/air_temperature_2.cml | 4 +- .../tests/results/FF/soil_temperature_1.cml | 4 +- .../tests/results/FF/surface_altitude_1.cml | 4 +- lib/iris/tests/results/abf/load.cml | 2 +- .../results/analysis/areaweights_original.cml | 4 +- .../tests/results/analysis/gmean_latitude.cml | 4 +- .../analysis/gmean_latitude_longitude.cml | 4 +- .../gmean_latitude_longitude_1call.cml | 4 +- .../tests/results/analysis/hmean_latitude.cml | 4 +- .../analysis/hmean_latitude_longitude.cml | 4 +- .../hmean_latitude_longitude_1call.cml | 4 +- .../tests/results/analysis/max_latitude.cml | 4 +- .../analysis/max_latitude_longitude.cml | 4 +- .../analysis/max_latitude_longitude_1call.cml | 4 +- .../tests/results/analysis/mean_latitude.cml | 4 +- .../analysis/mean_latitude_longitude.cml | 4 +- .../mean_latitude_longitude_1call.cml | 4 +- .../results/analysis/median_latitude.cml | 4 +- .../analysis/median_latitude_longitude.cml | 4 +- .../median_latitude_longitude_1call.cml | 4 +- .../tests/results/analysis/min_latitude.cml | 4 +- .../analysis/min_latitude_longitude.cml | 4 +- .../analysis/min_latitude_longitude_1call.cml | 4 +- lib/iris/tests/results/analysis/original.cml | 4 +- .../results/analysis/original_common.cml | 4 +- .../tests/results/analysis/original_hmean.cml | 4 +- .../regrid/linear_masked_altitude.cml | 2 +- .../regrid/linear_partial_overlap.cml | 2 +- .../results/analysis/regrid/linear_subset.cml | 2 +- .../regrid/linear_subset_masked_1.cml | 2 +- .../regrid/linear_subset_masked_2.cml | 2 +- .../regrid/nearest_masked_altitude.cml | 2 +- .../regrid/nearest_partial_overlap.cml | 2 +- .../analysis/regrid/nearest_subset.cml | 2 +- .../regrid/nearest_subset_masked_1.cml | 2 +- .../regrid/nearest_subset_masked_2.cml | 2 +- .../results/analysis/regrid/no_overlap.cml | 2 +- .../tests/results/analysis/rms_latitude.cml | 4 +- .../analysis/rms_latitude_longitude.cml | 4 +- .../analysis/rms_latitude_longitude_1call.cml | 4 +- .../results/analysis/std_dev_latitude.cml | 4 +- .../analysis/std_dev_latitude_longitude.cml | 4 +- .../std_dev_latitude_longitude_1call.cml | 4 +- .../tests/results/analysis/sum_latitude.cml | 4 +- .../analysis/sum_latitude_longitude.cml | 4 +- .../analysis/sum_latitude_longitude_1call.cml | 4 +- .../results/analysis/variance_latitude.cml | 4 +- .../analysis/variance_latitude_longitude.cml | 4 +- .../variance_latitude_longitude_1call.cml | 4 +- .../analysis/weighted_mean_original.cml | 4 +- .../results/categorisation/customcheck.cml | 2 +- .../results/categorisation/quickcheck.cml | 2 +- .../tests/results/cdm/extract/lat_eq_10.cml | 4 +- .../tests/results/cdm/extract/lat_gt_10.cml | 4 +- .../cdm/extract/lat_gt_10_and_lon_ge_10.cml | 4 +- lib/iris/tests/results/cdm/masked_cube.cml | 4 +- .../constrained_load/all_10_load_match.cml | 16 +- .../all_ml_10_22_load_match.cml | 16 +- .../constrained_load/attribute_constraint.cml | 4 +- ..._and_theta_level_gt_30_le_3_load_match.cml | 8 +- ...and_theta_level_gt_30_le_3_load_strict.cml | 8 +- .../constrained_load/theta_10_load_match.cml | 4 +- .../constrained_load/theta_10_load_strict.cml | 4 +- .../theta_and_all_10_load_match.cml | 20 +- .../theta_and_theta_10_load_strict.cml | 8 +- .../theta_and_theta_load_match.cml | 8 +- .../theta_and_theta_load_strict.cml | 8 +- .../theta_gt_30_le_3_load_match.cml | 4 +- .../theta_gt_30_le_3_load_strict.cml | 4 +- .../theta_lat_30_load_match.cml | 4 +- .../theta_lat_30_load_strict.cml | 4 +- .../theta_lat_gt_30_load_match.cml | 4 +- .../theta_lat_gt_30_load_strict.cml | 4 +- .../constrained_load/theta_load_match.cml | 4 +- .../constrained_load/theta_load_strict.cml | 4 +- .../coord_api/str_repr/dim_time_str.txt | 2 +- .../latitude_longitude_dual_stage.cml | 2 +- .../latitude_longitude_single_stage.cml | 2 +- ...latitude_model_level_number_dual_stage.cml | 2 +- ...titude_model_level_number_single_stage.cml | 2 +- .../latitude_time_dual_stage.cml | 2 +- .../latitude_time_single_stage.cml | 2 +- .../longitude_latitude_dual_stage.cml | 2 +- .../longitude_latitude_single_stage.cml | 2 +- ...ongitude_model_level_number_dual_stage.cml | 2 +- ...gitude_model_level_number_single_stage.cml | 2 +- .../longitude_time_dual_stage.cml | 2 +- .../longitude_time_single_stage.cml | 2 +- ...model_level_number_latitude_dual_stage.cml | 2 +- ...del_level_number_latitude_single_stage.cml | 2 +- ...odel_level_number_longitude_dual_stage.cml | 2 +- ...el_level_number_longitude_single_stage.cml | 2 +- .../model_level_number_time_dual_stage.cml | 2 +- .../model_level_number_time_single_stage.cml | 2 +- .../tests/results/cube_collapsed/original.cml | 2 +- .../time_latitude_dual_stage.cml | 2 +- .../time_latitude_single_stage.cml | 2 +- .../time_longitude_dual_stage.cml | 2 +- .../time_longitude_single_stage.cml | 2 +- .../time_model_level_number_dual_stage.cml | 2 +- .../time_model_level_number_single_stage.cml | 2 +- .../triple_collapse_lat_ml_pt.cml | 2 +- .../triple_collapse_ml_pt_lon.cml | 2 +- .../results/cube_io/pickling/cubelist.cml | 8 +- .../results/cube_io/pickling/single_cube.cml | 4 +- .../tests/results/cube_io/pickling/theta.cml | 4 +- .../tests/results/cube_io/pp/load/global.cml | 4 +- .../real_data_dual_tuple_indexing1.cml | 4 +- .../real_data_dual_tuple_indexing2.cml | 4 +- .../real_data_dual_tuple_indexing3.cml | 4 +- .../cube_slice/real_empty_data_indexing.cml | 4 +- .../results/cube_to_pp/no_forecast_period.cml | 4 +- .../results/cube_to_pp/no_forecast_time.cml | 2 +- lib/iris/tests/results/derived/column.cml | 2 +- lib/iris/tests/results/derived/no_orog.cml | 2 +- .../results/derived/removed_derived_coord.cml | 2 +- .../tests/results/derived/removed_orog.cml | 2 +- .../tests/results/derived/removed_sigma.cml | 2 +- lib/iris/tests/results/derived/transposed.cml | 2 +- .../orthogonal_cube_with_factory.cml | 2 +- .../const_lat_cross_section.cml | 2 +- .../const_lon_cross_section.cml | 2 +- .../hybridheight.cml | 2 +- .../ugrid/2D_1t_face_half_levels.cml | 2 +- .../ugrid/2D_72t_face_half_levels.cml | 2 +- .../ugrid/3D_1t_face_full_levels.cml | 2 +- .../ugrid/3D_1t_face_half_levels.cml | 2 +- .../ugrid/3D_snow_pseudo_levels.cml | 2 +- .../ugrid/3D_soil_pseudo_levels.cml | 2 +- .../ugrid/3D_tile_pseudo_levels.cml | 2 +- .../ugrid/3D_veg_pseudo_levels.cml | 2 +- .../tests/results/file_load/theta_levels.cml | 152 ++++----- .../tests/results/file_load/u_wind_levels.cml | 152 ++++----- .../tests/results/file_load/v_wind_levels.cml | 152 ++++----- .../tests/results/file_load/wind_levels.cml | 304 +++++++++--------- .../TestClimatology/reference_simpledata.cdl | 2 +- .../netcdf/TestAtmosphereSigma/save.cdl | 2 +- .../netcdf/TestHybridPressure/save.cdl | 2 +- .../TestPackedData/single_packed_manual.cdl | 4 +- .../TestPackedData/single_packed_signed.cdl | 4 +- .../TestPackedData/single_packed_unsigned.cdl | 4 +- .../hybrid_height_and_pressure.cdl | 2 +- .../hybrid_height_cubes.cml | 4 +- lib/iris/tests/results/merge/dec.cml | 16 +- lib/iris/tests/results/merge/theta.cml | 4 +- .../tests/results/merge/theta_two_times.cml | 4 +- lib/iris/tests/results/name/NAMEIII_field.cml | 10 +- .../tests/results/name/NAMEIII_timeseries.cml | 10 +- .../tests/results/name/NAMEIII_trajectory.cml | 68 ++-- .../results/name/NAMEIII_trajectory0.cml | 4 +- .../tests/results/name/NAMEIII_version2.cml | 8 +- lib/iris/tests/results/name/NAMEII_field.cml | 10 +- .../tests/results/name/NAMEII_timeseries.cml | 4 +- .../TestNetCDFSave__ancillaries/aliases.cdl | 2 +- .../TestNetCDFSave__ancillaries/flag.cdl | 2 +- .../TestNetCDFSave__ancillaries/fulldims.cdl | 2 +- .../TestNetCDFSave__ancillaries/multiple.cdl | 2 +- .../partialdims.cdl | 2 +- .../TestNetCDFSave__ancillaries/shared.cdl | 2 +- .../results/netcdf/netcdf_cell_methods.cml | 56 ++-- .../netcdf/netcdf_deferred_index_0.cml | 2 +- .../netcdf/netcdf_deferred_index_1.cml | 2 +- .../netcdf/netcdf_deferred_index_2.cml | 2 +- .../results/netcdf/netcdf_deferred_mix_0.cml | 2 +- .../results/netcdf/netcdf_deferred_mix_1.cml | 2 +- .../netcdf/netcdf_deferred_slice_0.cml | 2 +- .../netcdf/netcdf_deferred_slice_1.cml | 2 +- .../netcdf/netcdf_deferred_slice_2.cml | 2 +- .../netcdf/netcdf_deferred_tuple_0.cml | 2 +- .../netcdf/netcdf_deferred_tuple_1.cml | 2 +- .../netcdf/netcdf_deferred_tuple_2.cml | 2 +- .../netcdf/netcdf_global_xyt_hires.cml | 2 +- .../netcdf/netcdf_global_xyt_total.cml | 2 +- .../netcdf/netcdf_global_xyzt_gems.cml | 4 +- .../netcdf/netcdf_global_xyzt_gems_iter_0.cml | 2 +- .../netcdf/netcdf_global_xyzt_gems_iter_1.cml | 2 +- lib/iris/tests/results/netcdf/netcdf_laea.cml | 4 +- lib/iris/tests/results/netcdf/netcdf_lcc.cml | 2 +- lib/iris/tests/results/netcdf/netcdf_merc.cml | 2 +- .../tests/results/netcdf/netcdf_monotonic.cml | 6 +- .../tests/results/netcdf/netcdf_polar.cml | 2 +- .../netcdf_rotated_xyt_precipitation.cml | 2 +- .../netcdf/netcdf_save_hybrid_height.cdl | 4 +- .../netcdf/netcdf_save_load_hybrid_height.cml | 4 +- .../netcdf_save_load_ndim_auxiliary.cml | 2 +- .../netcdf/netcdf_save_ndim_auxiliary.cdl | 2 +- .../netcdf/netcdf_save_realistic_0d.cdl | 2 +- .../netcdf/netcdf_save_realistic_4d.cdl | 2 +- .../netcdf_save_realistic_4d_no_hybrid.cdl | 2 +- .../results/netcdf/netcdf_save_single.cdl | 4 +- .../tests/results/netcdf/netcdf_stereo.cml | 2 +- .../netcdf/netcdf_tmerc_and_climatology.cml | 2 +- lib/iris/tests/results/nimrod/load_2flds.cml | 4 +- .../results/nimrod/period_of_interest.cml | 2 +- .../results/nimrod/probability_fields.cml | 168 +++++----- .../nimrod/u1096_ng_bmr04_precip_2km.cml | 4 +- .../u1096_ng_bsr05_precip_accum60_2km.cml | 4 +- .../nimrod/u1096_ng_ek00_cloud3d0060_2km.cml | 8 +- .../nimrod/u1096_ng_ek00_cloud_2km.cml | 28 +- .../nimrod/u1096_ng_ek00_convection_2km.cml | 36 +-- .../nimrod/u1096_ng_ek00_convwind_2km.cml | 24 +- .../nimrod/u1096_ng_ek00_frzlev_2km.cml | 32 +- .../nimrod/u1096_ng_ek00_height_2km.cml | 4 +- .../nimrod/u1096_ng_ek00_precip_2km.cml | 12 +- .../nimrod/u1096_ng_ek00_precipaccum_2km.cml | 4 +- .../nimrod/u1096_ng_ek00_preciptype_2km.cml | 36 +-- .../nimrod/u1096_ng_ek00_pressure_2km.cml | 8 +- .../nimrod/u1096_ng_ek00_radiation_2km.cml | 28 +- .../nimrod/u1096_ng_ek00_radiationuv_2km.cml | 12 +- .../results/nimrod/u1096_ng_ek00_refl_2km.cml | 4 +- .../u1096_ng_ek00_relhumidity3d0060_2km.cml | 4 +- .../nimrod/u1096_ng_ek00_relhumidity_2km.cml | 4 +- .../results/nimrod/u1096_ng_ek00_snow_2km.cml | 12 +- .../nimrod/u1096_ng_ek00_soil3d0060_2km.cml | 16 +- .../results/nimrod/u1096_ng_ek00_soil_2km.cml | 12 +- .../nimrod/u1096_ng_ek00_temperature_2km.cml | 16 +- .../nimrod/u1096_ng_ek00_visibility_2km.cml | 20 +- .../results/nimrod/u1096_ng_ek00_wind_2km.cml | 24 +- .../nimrod/u1096_ng_ek00_winduv3d0015_2km.cml | 8 +- .../nimrod/u1096_ng_ek00_winduv_2km.cml | 8 +- .../results/nimrod/u1096_ng_ek01_cape_2km.cml | 24 +- ...u1096_ng_ek07_precip0540_accum180_18km.cml | 4 +- .../results/nimrod/u1096_ng_umqv_fog_2km.cml | 4 +- ...n.cml => data_frame_datetime_standard.cml} | 2 +- ...orian.cml => series_datetime_standard.cml} | 2 +- .../tests/results/pp_load_rules/global.cml | 4 +- .../pp_load_rules/lbproc_mean_max_min.cml | 20 +- .../results/pp_load_rules/rotated_uk.cml | 4 +- lib/iris/tests/results/stock/realistic_4d.cml | 2 +- .../system/supported_filetype_.grib2.cml | 4 +- .../results/system/supported_filetype_.nc.cml | 2 +- .../results/system/supported_filetype_.pp.cml | 4 +- .../results/trajectory/constant_latitude.cml | 4 +- .../results/trajectory/hybrid_height.cml | 4 +- .../tests/results/trajectory/single_point.cml | 4 +- lib/iris/tests/results/trajectory/zigzag.cml | 4 +- .../cartography/project/TestAll/cube.cml | 2 +- .../collapse_all_dims.cml | 2 +- .../collapse_last_dims.cml | 2 +- .../collapse_middle_dim.cml | 2 +- .../collapse_zeroth_dim.cml | 2 +- .../TestBroadcastingDerived/slice.cml | 2 +- .../TestBroadcastingDerived/transposed.cml | 2 +- .../collapse_all_dims.cml | 2 +- .../collapse_last_dims.cml | 2 +- .../collapse_middle_dim.cml | 2 +- .../collapse_zeroth_dim.cml | 2 +- .../TestBroadcastingWithMesh/slice.cml | 2 +- .../TestBroadcastingWithMesh/transposed.cml | 2 +- .../collapse_all_dims.cml | 2 +- .../collapse_last_dims.cml | 2 +- .../collapse_middle_dim.cml | 2 +- .../collapse_zeroth_dim.cml | 2 +- .../slice.cml | 2 +- .../transposed.cml | 2 +- .../TestBroadcasting/collapse_all_dims.cml | 2 +- .../TestBroadcasting/collapse_last_dims.cml | 2 +- .../TestBroadcasting/collapse_middle_dim.cml | 2 +- .../TestBroadcasting/collapse_zeroth_dim.cml | 2 +- .../maths/add/TestBroadcasting/slice.cml | 2 +- .../maths/add/TestBroadcasting/transposed.cml | 2 +- .../TestBroadcasting/collapse_all_dims.cml | 2 +- .../TestBroadcasting/collapse_last_dims.cml | 2 +- .../TestBroadcasting/collapse_middle_dim.cml | 2 +- .../TestBroadcasting/collapse_zeroth_dim.cml | 2 +- .../maths/divide/TestBroadcasting/slice.cml | 2 +- .../divide/TestBroadcasting/transposed.cml | 2 +- .../TestBroadcasting/collapse_all_dims.cml | 2 +- .../TestBroadcasting/collapse_last_dims.cml | 2 +- .../TestBroadcasting/collapse_middle_dim.cml | 2 +- .../TestBroadcasting/collapse_zeroth_dim.cml | 2 +- .../maths/multiply/TestBroadcasting/slice.cml | 2 +- .../multiply/TestBroadcasting/transposed.cml | 2 +- .../TestBroadcasting/collapse_all_dims.cml | 2 +- .../TestBroadcasting/collapse_last_dims.cml | 2 +- .../TestBroadcasting/collapse_middle_dim.cml | 2 +- .../TestBroadcasting/collapse_zeroth_dim.cml | 2 +- .../maths/subtract/TestBroadcasting/slice.cml | 2 +- .../subtract/TestBroadcasting/transposed.cml | 2 +- .../netcdf/Saver/write/with_climatology.cdl | 2 +- ...000.03.236.000128.1990.12.01.00.00.b_0.cml | 4 +- ...000.03.236.004224.1990.12.01.00.00.b_0.cml | 4 +- ...000.03.236.008320.1990.12.01.00.00.b_0.cml | 4 +- ...00000.03.236.000128.1990.12.01.00.00.b.cml | 4 +- ...00000.03.236.004224.1990.12.01.00.00.b.cml | 4 +- ...00000.03.236.008320.1990.12.01.00.00.b.cml | 4 +- ...000.03.236.000128.1990.12.01.00.00.b_0.cdl | 4 +- ...000.03.236.004224.1990.12.01.00.00.b_0.cdl | 4 +- ...000.03.236.008320.1990.12.01.00.00.b_0.cdl | 4 +- .../file_headers/xios_2D_face_half_levels.cdl | 2 +- .../file_headers/xios_3D_face_full_levels.cdl | 2 +- .../file_headers/xios_3D_face_half_levels.cdl | 2 +- lib/iris/tests/system_test.py | 2 +- lib/iris/tests/test_coord_categorisation.py | 2 +- lib/iris/tests/test_pandas.py | 12 +- .../unit/concatenate/test__CubeSignature.py | 2 +- .../unit/concatenate/test_concatenate.py | 4 +- .../coord_categorisation/test_add_hour.py | 2 +- .../unit/coords/test_AncillaryVariable.py | 2 +- lib/iris/tests/unit/coords/test_Coord.py | 8 +- .../unit/coords/test__DimensionalMetadata.py | 14 +- lib/iris/tests/unit/cube/test_CubeList.py | 4 +- .../tests/unit/fileformats/pp/test_PPField.py | 2 +- .../test__convert_time_coords.py | 4 +- .../pp_load_rules/test__epoch_date_hours.py | 4 +- lib/iris/tests/unit/plot/test__fixup_dates.py | 8 +- .../tests/unit/util/test_unify_time_units.py | 4 +- requirements/ci/nox.lock/py310-linux-64.lock | 22 +- requirements/ci/nox.lock/py38-linux-64.lock | 22 +- requirements/ci/nox.lock/py39-linux-64.lock | 22 +- requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.cfg | 2 +- 331 files changed, 1275 insertions(+), 1264 deletions(-) rename lib/iris/tests/results/pandas/as_cube/{data_frame_datetime_gregorian.cml => data_frame_datetime_standard.cml} (90%) rename lib/iris/tests/results/pandas/as_cube/{series_datetime_gregorian.cml => series_datetime_standard.cml} (86%) diff --git a/docs/gallery_code/general/plot_custom_file_loading.py b/docs/gallery_code/general/plot_custom_file_loading.py index 025f395789..4b817aea66 100644 --- a/docs/gallery_code/general/plot_custom_file_loading.py +++ b/docs/gallery_code/general/plot_custom_file_loading.py @@ -57,7 +57,7 @@ import datetime -from cf_units import CALENDAR_GREGORIAN, Unit +from cf_units import CALENDAR_STANDARD, Unit import matplotlib.pyplot as plt import numpy as np @@ -225,7 +225,7 @@ def NAME_to_cube(filenames, callback): # define the time unit and use it to serialise the datetime for the # time coordinate - time_unit = Unit("hours since epoch", calendar=CALENDAR_GREGORIAN) + time_unit = Unit("hours since epoch", calendar=CALENDAR_STANDARD) time_coord = icoords.AuxCoord( time_unit.date2num(field_headings["time"]), standard_name="time", diff --git a/docs/src/techpapers/um_files_loading.rst b/docs/src/techpapers/um_files_loading.rst index 72d34962ce..f8c94cab08 100644 --- a/docs/src/techpapers/um_files_loading.rst +++ b/docs/src/techpapers/um_files_loading.rst @@ -350,7 +350,7 @@ information is contained in the :attr:`~iris.coords.Coord.units` property. always 1st Jan 1970 (times before this are represented as negative values). The units.calendar property of time coordinates is set from the lowest decimal -digit of LBTIM, known as LBTIM.IC. Note that the non-gregorian calendars (e.g. +digit of LBTIM, known as LBTIM.IC. Note that the non-standard calendars (e.g. 360-day 'model' calendar) are defined in CF, not udunits. There are a number of different time encoding methods used in UM data, but the diff --git a/docs/src/userguide/subsetting_a_cube.rst b/docs/src/userguide/subsetting_a_cube.rst index 6523ab1cd7..c4f55490af 100644 --- a/docs/src/userguide/subsetting_a_cube.rst +++ b/docs/src/userguide/subsetting_a_cube.rst @@ -172,7 +172,7 @@ objects for ease of calendar-based testing. >>> cube_all = iris.load_cube(filename, 'air_potential_temperature') >>> print('All times :\n' + str(cube_all.coord('time'))) All times : - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + DimCoord : time / (hours since 1970-01-01 00:00:00, standard calendar) points: [2009-11-19 10:00:00, 2009-11-19 11:00:00, 2009-11-19 12:00:00] shape: (3,) dtype: float64 @@ -182,7 +182,7 @@ objects for ease of calendar-based testing. >>> cube_11 = cube_all.extract(hour_11) >>> print('Selected times :\n' + str(cube_11.coord('time'))) Selected times : - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + DimCoord : time / (hours since 1970-01-01 00:00:00, standard calendar) points: [2009-11-19 11:00:00] shape: (1,) dtype: float64 @@ -210,7 +210,7 @@ The previous constraint example can now be written as: >>> print(iris.load_cube( ... iris.sample_data_path('uk_hires.pp'), ... 'air_potential_temperature' & the_11th_hour).coord('time')) - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + DimCoord : time / (hours since 1970-01-01 00:00:00, standard calendar) points: [2009-11-19 11:00:00] shape: (1,) dtype: float64 @@ -234,7 +234,7 @@ day of every week for many years: :options: +NORMALIZE_WHITESPACE, +ELLIPSIS >>> print(long_ts.coord('time')) - DimCoord : time / (days since 2007-04-09, gregorian calendar) + DimCoord : time / (days since 2007-04-09, standard calendar) points: [ 2007-04-09 00:00:00, 2007-04-16 00:00:00, ..., 2010-02-08 00:00:00, 2010-02-15 00:00:00] @@ -255,7 +255,7 @@ we constrain that coord using :class:`iris.cube.Cube.extract` ... time=lambda cell: d1 <= cell.point < d2) >>> within_st_swithuns_07 = long_ts.extract(st_swithuns_daterange_07) >>> print(within_st_swithuns_07.coord('time')) - DimCoord : time / (days since 2007-04-09, gregorian calendar) + DimCoord : time / (days since 2007-04-09, standard calendar) points: [ 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00] @@ -275,7 +275,7 @@ objects. ... time=lambda cell: pdt1 <= cell.point < pdt2) >>> within_st_swithuns_07 = long_ts.extract(st_swithuns_daterange_07) >>> print(within_st_swithuns_07.coord('time')) - DimCoord : time / (days since 2007-04-09, gregorian calendar) + DimCoord : time / (days since 2007-04-09, standard calendar) points: [ 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00] @@ -296,7 +296,7 @@ PartialDateTime this becomes simple: ... >>> # Note: using summary(max_values) to show more of the points >>> print(within_st_swithuns.coord('time').summary(max_values=100)) - DimCoord : time / (days since 2007-04-09, gregorian calendar) + DimCoord : time / (days since 2007-04-09, standard calendar) points: [ 2007-07-16 00:00:00, 2007-07-23 00:00:00, 2007-07-30 00:00:00, 2007-08-06 00:00:00, 2007-08-13 00:00:00, 2007-08-20 00:00:00, diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 849d44a44c..5c62a27830 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -132,7 +132,13 @@ This document explains the changes made to Iris for this release 💣 Incompatible Changes ======================= -#. N/A +#. `@rcomer`_ and `@bjlittle`_ (reviewer) updated Iris's calendar handling to be + consistent with ``cf-units`` version 3.1. In line with the `Calendar`_ + section in version 1.9 of the CF Conventions, we now use "standard" rather + than the deprecated "gregorian" label for the default calendar. Units may + still be instantiated with ``calendar="gregorian"`` but their calendar + attribute will be silently changed to "standard". This may cause failures in + code that explicitly checks the calendar attribute. (:pull:`4847`) 🚀 Performance Enhancements @@ -173,6 +179,10 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ and `@bjlittle`_ (reviewer) unpinned ``pillow``. (:pull:`4826`) +#. `@rcomer`_ introduced the ``cf-units >=3.1`` minimum pin, reflecting the + alignment of calendar behaviour in the two packages (see Incompatible Changes). + (:pull:`4847`) + 📚 Documentation ================ @@ -248,5 +258,6 @@ This document explains the changes made to Iris for this release .. comment Whatsnew resources in alphabetical order: +.. _Calendar: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#calendar .. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries .. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html diff --git a/lib/iris/cube.py b/lib/iris/cube.py index e3bacf08fc..cdfc656914 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -4431,7 +4431,7 @@ def interpolate(self, sample_points, scheme, collapse_scalar=True): air_potential_temperature / (K) \ (time: 3; model_level_number: 7; grid_latitude: 204; grid_longitude: 187) >>> print(cube.coord('time')) - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + DimCoord : time / (hours since 1970-01-01 00:00:00, standard calendar) points: [2009-11-19 10:00:00, 2009-11-19 11:00:00, 2009-11-19 12:00:00] shape: (3,) dtype: float64 @@ -4444,7 +4444,7 @@ def interpolate(self, sample_points, scheme, collapse_scalar=True): air_potential_temperature / (K) \ (model_level_number: 7; grid_latitude: 204; grid_longitude: 187) >>> print(result.coord('time')) - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + DimCoord : time / (hours since 1970-01-01 00:00:00, standard calendar) points: [2009-11-19 10:30:00] shape: (1,) dtype: float64 @@ -4459,7 +4459,7 @@ def interpolate(self, sample_points, scheme, collapse_scalar=True): air_potential_temperature / (K) \ (model_level_number: 7; grid_latitude: 204; grid_longitude: 187) >>> print(result2.coord('time')) - DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) + DimCoord : time / (hours since 1970-01-01 00:00:00, standard calendar) points: [2009-11-19 10:30:00] shape: (1,) dtype: float64 diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py index 34e88aff80..3aaba3679e 100644 --- a/lib/iris/fileformats/name_loaders.py +++ b/lib/iris/fileformats/name_loaders.py @@ -456,7 +456,7 @@ def _generate_cubes( # Define the time unit and use it to serialise the datetime for # the time coordinate. time_unit = cf_units.Unit( - "hours since epoch", calendar=cf_units.CALENDAR_GREGORIAN + "hours since epoch", calendar=cf_units.CALENDAR_STANDARD ) # Build time, height, latitude and longitude coordinates. @@ -1212,7 +1212,7 @@ def load_NAMEIII_trajectory(filename): """ time_unit = cf_units.Unit( - "hours since epoch", calendar=cf_units.CALENDAR_GREGORIAN + "hours since epoch", calendar=cf_units.CALENDAR_STANDARD ) with open(filename, "r") as infile: diff --git a/lib/iris/fileformats/nimrod_load_rules.py b/lib/iris/fileformats/nimrod_load_rules.py index 5d8bb11c48..fd1ccb0e95 100644 --- a/lib/iris/fileformats/nimrod_load_rules.py +++ b/lib/iris/fileformats/nimrod_load_rules.py @@ -24,7 +24,7 @@ NIMROD_DEFAULT = -32767.0 TIME_UNIT = cf_units.Unit( - "seconds since 1970-01-01 00:00:00", calendar=cf_units.CALENDAR_GREGORIAN + "seconds since 1970-01-01 00:00:00", calendar=cf_units.CALENDAR_STANDARD ) diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index e2958382cb..1fb7d4e178 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -1066,7 +1066,7 @@ def core_data(self): def calendar(self): """Return the calendar of the field.""" # TODO #577 What calendar to return when ibtim.ic in [0, 3] - calendar = cf_units.CALENDAR_GREGORIAN + calendar = cf_units.CALENDAR_STANDARD if self.lbtim.ic == 2: calendar = cf_units.CALENDAR_360_DAY elif self.lbtim.ic == 4: diff --git a/lib/iris/fileformats/pp_load_rules.py b/lib/iris/fileformats/pp_load_rules.py index c23772f235..ebccec47ee 100644 --- a/lib/iris/fileformats/pp_load_rules.py +++ b/lib/iris/fileformats/pp_load_rules.py @@ -548,7 +548,7 @@ def _epoch_date_hours_internals(epoch_hours_unit, datetime): if m == 0: # Add a 'January', by changing month=0 to 1. m = 1 - if calendar == cf_units.CALENDAR_GREGORIAN: + if calendar == cf_units.CALENDAR_STANDARD: days_offset += 31 elif calendar == cf_units.CALENDAR_360_DAY: days_offset += 30 @@ -561,7 +561,7 @@ def _epoch_date_hours_internals(epoch_hours_unit, datetime): if y == 0: # Add a 'Year 0', by changing year=0 to 1. y = 1 - if calendar == cf_units.CALENDAR_GREGORIAN: + if calendar == cf_units.CALENDAR_STANDARD: days_in_year_0 = 366 elif calendar == cf_units.CALENDAR_360_DAY: days_in_year_0 = 360 diff --git a/lib/iris/fileformats/pp_save_rules.py b/lib/iris/fileformats/pp_save_rules.py index ed156b5a05..e6b3748f9b 100644 --- a/lib/iris/fileformats/pp_save_rules.py +++ b/lib/iris/fileformats/pp_save_rules.py @@ -398,7 +398,7 @@ def _calendar_rules(cube, pp): if time_coord is not None: if time_coord.units.calendar == "360_day": pp.lbtim.ic = 2 - elif time_coord.units.calendar == "gregorian": + elif time_coord.units.calendar == "standard": pp.lbtim.ic = 1 elif time_coord.units.calendar == "365_day": pp.lbtim.ic = 4 diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index 4c421792a7..6b35a1d1cd 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -33,12 +33,12 @@ def _add_iris_coord(cube, name, points, dim, calendar=None): """ Add a Coord to a Cube from a Pandas index or columns array. - If no calendar is specified for a time series, Gregorian is assumed. + If no calendar is specified for a time series, Standard is assumed. """ units = Unit("unknown") if calendar is None: - calendar = cf_units.CALENDAR_GREGORIAN + calendar = cf_units.CALENDAR_STANDARD # Convert pandas datetime objects to python datetime obejcts. if isinstance(points, DatetimeIndex): @@ -83,7 +83,7 @@ def as_cube(pandas_array, copy=True, calendars=None): Example usage:: as_cube(series, calendars={0: cf_units.CALENDAR_360_DAY}) - as_cube(data_frame, calendars={1: cf_units.CALENDAR_GREGORIAN}) + as_cube(data_frame, calendars={1: cf_units.CALENDAR_STANDARD}) .. note:: This function will copy your data by default. diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 47c63dc173..2da91e8c67 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -587,14 +587,14 @@ def _fixup_dates(coord, values): # Convert coordinate values into tuples of # (year, month, day, hour, min, sec) dates = [coord.units.num2date(val).timetuple()[0:6] for val in values] - if coord.units.calendar == "gregorian": + if coord.units.calendar == "standard": r = [datetime.datetime(*date) for date in dates] else: try: import nc_time_axis # noqa: F401 except ImportError: msg = ( - "Cannot plot against time in a non-gregorian " + "Cannot plot against time in a non-standard " 'calendar, because "nc_time_axis" is not available : ' "Install the package from " "https://github.com/SciTools/nc-time-axis to enable " diff --git a/lib/iris/tests/integration/concatenate/test_concatenate.py b/lib/iris/tests/integration/concatenate/test_concatenate.py index 4e3f453e0a..091ecd4378 100644 --- a/lib/iris/tests/integration/concatenate/test_concatenate.py +++ b/lib/iris/tests/integration/concatenate/test_concatenate.py @@ -33,7 +33,7 @@ def simple_1d_time_cubes(self, reftimes, coords_points): standard_name="air_temperature", units="K", ) - unit = cf_units.Unit(reftime, calendar="gregorian") + unit = cf_units.Unit(reftime, calendar="standard") coord = iris.coords.DimCoord( points=np.array(coord_points, dtype=np.float32), standard_name="time", @@ -68,7 +68,7 @@ def create_cube(self): ) height = iris.coords.AuxCoord([1.5], standard_name="height", units="m") t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="gregorian" + "hours since 1970-01-01 00:00:00", calendar="standard" ) time = iris.coords.DimCoord([0, 6], standard_name="time", units=t_unit) @@ -113,7 +113,7 @@ def create_cube(self): [1.5], standard_name="height", units="m" ) t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="gregorian" + "hours since 1970-01-01 00:00:00", calendar="standard" ) time = iris.coords.DimCoord([0, 6], standard_name="time", units=t_unit) @@ -156,7 +156,7 @@ def create_cube(self): [1.5], standard_name="height", units="m" ) t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="gregorian" + "hours since 1970-01-01 00:00:00", calendar="standard" ) time = iris.coords.DimCoord([0, 6], standard_name="time", units=t_unit) @@ -196,7 +196,7 @@ def setUp(self): # Time coord t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="gregorian" + "hours since 1970-01-01 00:00:00", calendar="standard" ) t_coord = iris.coords.DimCoord( [0, 6], standard_name="time", units=t_unit diff --git a/lib/iris/tests/integration/plot/test_netcdftime.py b/lib/iris/tests/integration/plot/test_netcdftime.py index 9f0baeda35..d438c09bd5 100644 --- a/lib/iris/tests/integration/plot/test_netcdftime.py +++ b/lib/iris/tests/integration/plot/test_netcdftime.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Test plot of time coord with non-gregorian calendar. +Test plot of time coord with non-standard calendar. """ diff --git a/lib/iris/tests/integration/test_pp.py b/lib/iris/tests/integration/test_pp.py index db2113025d..e654694aa7 100644 --- a/lib/iris/tests/integration/test_pp.py +++ b/lib/iris/tests/integration/test_pp.py @@ -683,7 +683,7 @@ def test_as_pairs(self): class TestSaveLBPROC(tests.IrisTest): def create_cube(self, longitude_coord="longitude"): cube = Cube(np.zeros((2, 3, 4))) - tunit = Unit("days since epoch", calendar="gregorian") + tunit = Unit("days since epoch", calendar="standard") tcoord = DimCoord(np.arange(2), standard_name="time", units=tunit) xcoord = DimCoord( np.arange(3), standard_name=longitude_coord, units="degrees" diff --git a/lib/iris/tests/results/COLPEX/small_colpex_theta_p_alt.cml b/lib/iris/tests/results/COLPEX/small_colpex_theta_p_alt.cml index 5bba278059..da315c36af 100644 --- a/lib/iris/tests/results/COLPEX/small_colpex_theta_p_alt.cml +++ b/lib/iris/tests/results/COLPEX/small_colpex_theta_p_alt.cml @@ -400,7 +400,7 @@ 0.666666666686, 0.833333333314, 1.0]" shape="(6,)" standard_name="forecast_period" units="Unit('hours')" value_type="float64"/> - + + 347926.666667, 347926.833333, 347927.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -923,7 +923,7 @@ 0.666666666686, 0.833333333314, 1.0]" shape="(6,)" standard_name="forecast_period" units="Unit('hours')" value_type="float64"/> - + + 347926.666667, 347926.833333, 347927.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -1057,7 +1057,7 @@ - + + diff --git a/lib/iris/tests/results/FF/air_temperature_1.cml b/lib/iris/tests/results/FF/air_temperature_1.cml index 267aa88d23..043b9acc16 100644 --- a/lib/iris/tests/results/FF/air_temperature_1.cml +++ b/lib/iris/tests/results/FF/air_temperature_1.cml @@ -11,7 +11,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/lib/iris/tests/results/FF/air_temperature_2.cml b/lib/iris/tests/results/FF/air_temperature_2.cml index 307c58fe72..200a80b54a 100644 --- a/lib/iris/tests/results/FF/air_temperature_2.cml +++ b/lib/iris/tests/results/FF/air_temperature_2.cml @@ -11,7 +11,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/lib/iris/tests/results/FF/soil_temperature_1.cml b/lib/iris/tests/results/FF/soil_temperature_1.cml index e555a3f5b9..57303636c1 100644 --- a/lib/iris/tests/results/FF/soil_temperature_1.cml +++ b/lib/iris/tests/results/FF/soil_temperature_1.cml @@ -11,7 +11,7 @@ - + + diff --git a/lib/iris/tests/results/FF/surface_altitude_1.cml b/lib/iris/tests/results/FF/surface_altitude_1.cml index 27cfad3d09..2669624d37 100644 --- a/lib/iris/tests/results/FF/surface_altitude_1.cml +++ b/lib/iris/tests/results/FF/surface_altitude_1.cml @@ -11,7 +11,7 @@ - + + diff --git a/lib/iris/tests/results/abf/load.cml b/lib/iris/tests/results/abf/load.cml index e470cbebf3..e7954ab229 100644 --- a/lib/iris/tests/results/abf/load.cml +++ b/lib/iris/tests/results/abf/load.cml @@ -30,7 +30,7 @@ - + diff --git a/lib/iris/tests/results/analysis/areaweights_original.cml b/lib/iris/tests/results/analysis/areaweights_original.cml index 3c33ef500a..651bb648dd 100644 --- a/lib/iris/tests/results/analysis/areaweights_original.cml +++ b/lib/iris/tests/results/analysis/areaweights_original.cml @@ -10,7 +10,7 @@ - + @@ -26,7 +26,7 @@ - + diff --git a/lib/iris/tests/results/analysis/gmean_latitude.cml b/lib/iris/tests/results/analysis/gmean_latitude.cml index ebe22c54f3..26b7fdc8af 100644 --- a/lib/iris/tests/results/analysis/gmean_latitude.cml +++ b/lib/iris/tests/results/analysis/gmean_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/gmean_latitude_longitude.cml b/lib/iris/tests/results/analysis/gmean_latitude_longitude.cml index 3cd6a93948..94ed36ac88 100644 --- a/lib/iris/tests/results/analysis/gmean_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/gmean_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/gmean_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/gmean_latitude_longitude_1call.cml index cc7b3133e0..1db977312b 100644 --- a/lib/iris/tests/results/analysis/gmean_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/gmean_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/hmean_latitude.cml b/lib/iris/tests/results/analysis/hmean_latitude.cml index d953f0e4d9..70e3fcb540 100644 --- a/lib/iris/tests/results/analysis/hmean_latitude.cml +++ b/lib/iris/tests/results/analysis/hmean_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/hmean_latitude_longitude.cml b/lib/iris/tests/results/analysis/hmean_latitude_longitude.cml index 43700b083c..f762fd643b 100644 --- a/lib/iris/tests/results/analysis/hmean_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/hmean_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/hmean_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/hmean_latitude_longitude_1call.cml index e17383ff64..369dca3203 100644 --- a/lib/iris/tests/results/analysis/hmean_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/hmean_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/max_latitude.cml b/lib/iris/tests/results/analysis/max_latitude.cml index faa54fff8a..89542d27d3 100644 --- a/lib/iris/tests/results/analysis/max_latitude.cml +++ b/lib/iris/tests/results/analysis/max_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/max_latitude_longitude.cml b/lib/iris/tests/results/analysis/max_latitude_longitude.cml index 8437e8f4a1..7d24ca7f14 100644 --- a/lib/iris/tests/results/analysis/max_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/max_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/max_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/max_latitude_longitude_1call.cml index 5b6504dfb1..b4d1e0349c 100644 --- a/lib/iris/tests/results/analysis/max_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/max_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/mean_latitude.cml b/lib/iris/tests/results/analysis/mean_latitude.cml index fcf2ef55be..80921e762d 100644 --- a/lib/iris/tests/results/analysis/mean_latitude.cml +++ b/lib/iris/tests/results/analysis/mean_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/mean_latitude_longitude.cml b/lib/iris/tests/results/analysis/mean_latitude_longitude.cml index 5cb139be1a..6ac9400a3a 100644 --- a/lib/iris/tests/results/analysis/mean_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/mean_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/mean_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/mean_latitude_longitude_1call.cml index 573fa1c694..affcf07c07 100644 --- a/lib/iris/tests/results/analysis/mean_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/mean_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/median_latitude.cml b/lib/iris/tests/results/analysis/median_latitude.cml index 49006c9592..bbf3875688 100644 --- a/lib/iris/tests/results/analysis/median_latitude.cml +++ b/lib/iris/tests/results/analysis/median_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/median_latitude_longitude.cml b/lib/iris/tests/results/analysis/median_latitude_longitude.cml index 49ec42b391..5663f6d65f 100644 --- a/lib/iris/tests/results/analysis/median_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/median_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/median_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/median_latitude_longitude_1call.cml index 036c6bb2f9..c0c0d7c46b 100644 --- a/lib/iris/tests/results/analysis/median_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/median_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/min_latitude.cml b/lib/iris/tests/results/analysis/min_latitude.cml index 34a2dc5548..bf20be30a9 100644 --- a/lib/iris/tests/results/analysis/min_latitude.cml +++ b/lib/iris/tests/results/analysis/min_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/min_latitude_longitude.cml b/lib/iris/tests/results/analysis/min_latitude_longitude.cml index 76c7e96bce..3792645582 100644 --- a/lib/iris/tests/results/analysis/min_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/min_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/min_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/min_latitude_longitude_1call.cml index 6b484eb591..b43231b7e6 100644 --- a/lib/iris/tests/results/analysis/min_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/min_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/original.cml b/lib/iris/tests/results/analysis/original.cml index 23129095b6..414de1b6b5 100644 --- a/lib/iris/tests/results/analysis/original.cml +++ b/lib/iris/tests/results/analysis/original.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/original_common.cml b/lib/iris/tests/results/analysis/original_common.cml index c1759c12bd..bbfa48d7d8 100644 --- a/lib/iris/tests/results/analysis/original_common.cml +++ b/lib/iris/tests/results/analysis/original_common.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/original_hmean.cml b/lib/iris/tests/results/analysis/original_hmean.cml index 952cede1c2..bdc145022c 100644 --- a/lib/iris/tests/results/analysis/original_hmean.cml +++ b/lib/iris/tests/results/analysis/original_hmean.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/linear_masked_altitude.cml b/lib/iris/tests/results/analysis/regrid/linear_masked_altitude.cml index dc1fee2f2b..1ac69490b4 100644 --- a/lib/iris/tests/results/analysis/regrid/linear_masked_altitude.cml +++ b/lib/iris/tests/results/analysis/regrid/linear_masked_altitude.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/linear_partial_overlap.cml b/lib/iris/tests/results/analysis/regrid/linear_partial_overlap.cml index 6fdbe7df00..eb9adb4aef 100644 --- a/lib/iris/tests/results/analysis/regrid/linear_partial_overlap.cml +++ b/lib/iris/tests/results/analysis/regrid/linear_partial_overlap.cml @@ -99,7 +99,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/linear_subset.cml b/lib/iris/tests/results/analysis/regrid/linear_subset.cml index d9b80dd86b..9bd62287fe 100644 --- a/lib/iris/tests/results/analysis/regrid/linear_subset.cml +++ b/lib/iris/tests/results/analysis/regrid/linear_subset.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/linear_subset_masked_1.cml b/lib/iris/tests/results/analysis/regrid/linear_subset_masked_1.cml index d9b80dd86b..9bd62287fe 100644 --- a/lib/iris/tests/results/analysis/regrid/linear_subset_masked_1.cml +++ b/lib/iris/tests/results/analysis/regrid/linear_subset_masked_1.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/linear_subset_masked_2.cml b/lib/iris/tests/results/analysis/regrid/linear_subset_masked_2.cml index d9b80dd86b..9bd62287fe 100644 --- a/lib/iris/tests/results/analysis/regrid/linear_subset_masked_2.cml +++ b/lib/iris/tests/results/analysis/regrid/linear_subset_masked_2.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/nearest_masked_altitude.cml b/lib/iris/tests/results/analysis/regrid/nearest_masked_altitude.cml index b2aec5e891..a1cff2363e 100644 --- a/lib/iris/tests/results/analysis/regrid/nearest_masked_altitude.cml +++ b/lib/iris/tests/results/analysis/regrid/nearest_masked_altitude.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/nearest_partial_overlap.cml b/lib/iris/tests/results/analysis/regrid/nearest_partial_overlap.cml index f6647aa426..98a0b6b805 100644 --- a/lib/iris/tests/results/analysis/regrid/nearest_partial_overlap.cml +++ b/lib/iris/tests/results/analysis/regrid/nearest_partial_overlap.cml @@ -99,7 +99,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/nearest_subset.cml b/lib/iris/tests/results/analysis/regrid/nearest_subset.cml index 7e12c9be60..a704cbecbb 100644 --- a/lib/iris/tests/results/analysis/regrid/nearest_subset.cml +++ b/lib/iris/tests/results/analysis/regrid/nearest_subset.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_1.cml b/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_1.cml index 7e12c9be60..a704cbecbb 100644 --- a/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_1.cml +++ b/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_1.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_2.cml b/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_2.cml index 7e12c9be60..a704cbecbb 100644 --- a/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_2.cml +++ b/lib/iris/tests/results/analysis/regrid/nearest_subset_masked_2.cml @@ -107,7 +107,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/regrid/no_overlap.cml b/lib/iris/tests/results/analysis/regrid/no_overlap.cml index 6aa4d218f8..da2f03f1ee 100644 --- a/lib/iris/tests/results/analysis/regrid/no_overlap.cml +++ b/lib/iris/tests/results/analysis/regrid/no_overlap.cml @@ -99,7 +99,7 @@ + 347921.666667, 347921.833333]" shape="(5,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/rms_latitude.cml b/lib/iris/tests/results/analysis/rms_latitude.cml index e409daed2d..d4b1428fb2 100644 --- a/lib/iris/tests/results/analysis/rms_latitude.cml +++ b/lib/iris/tests/results/analysis/rms_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/rms_latitude_longitude.cml b/lib/iris/tests/results/analysis/rms_latitude_longitude.cml index 9bdc53fbad..4293087847 100644 --- a/lib/iris/tests/results/analysis/rms_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/rms_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/rms_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/rms_latitude_longitude_1call.cml index 89a593d122..9ca1d23b42 100644 --- a/lib/iris/tests/results/analysis/rms_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/rms_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/std_dev_latitude.cml b/lib/iris/tests/results/analysis/std_dev_latitude.cml index 154d5ef587..a45aefeff4 100644 --- a/lib/iris/tests/results/analysis/std_dev_latitude.cml +++ b/lib/iris/tests/results/analysis/std_dev_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/std_dev_latitude_longitude.cml b/lib/iris/tests/results/analysis/std_dev_latitude_longitude.cml index 770ef9a35a..95e8e3694d 100644 --- a/lib/iris/tests/results/analysis/std_dev_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/std_dev_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/std_dev_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/std_dev_latitude_longitude_1call.cml index a5ce049ca5..f91f6005b7 100644 --- a/lib/iris/tests/results/analysis/std_dev_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/std_dev_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/sum_latitude.cml b/lib/iris/tests/results/analysis/sum_latitude.cml index 943aa9312f..fbb8460fd8 100644 --- a/lib/iris/tests/results/analysis/sum_latitude.cml +++ b/lib/iris/tests/results/analysis/sum_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/sum_latitude_longitude.cml b/lib/iris/tests/results/analysis/sum_latitude_longitude.cml index 2eff41339b..cb992f3b9d 100644 --- a/lib/iris/tests/results/analysis/sum_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/sum_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/sum_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/sum_latitude_longitude_1call.cml index a2a46d2ba8..6171dc516b 100644 --- a/lib/iris/tests/results/analysis/sum_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/sum_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/variance_latitude.cml b/lib/iris/tests/results/analysis/variance_latitude.cml index 437587b00d..5b55731396 100644 --- a/lib/iris/tests/results/analysis/variance_latitude.cml +++ b/lib/iris/tests/results/analysis/variance_latitude.cml @@ -11,7 +11,7 @@ - + @@ -27,7 +27,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/variance_latitude_longitude.cml b/lib/iris/tests/results/analysis/variance_latitude_longitude.cml index 391ab8834e..359e40ef8a 100644 --- a/lib/iris/tests/results/analysis/variance_latitude_longitude.cml +++ b/lib/iris/tests/results/analysis/variance_latitude_longitude.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/variance_latitude_longitude_1call.cml b/lib/iris/tests/results/analysis/variance_latitude_longitude_1call.cml index 535468acfc..0345eac77b 100644 --- a/lib/iris/tests/results/analysis/variance_latitude_longitude_1call.cml +++ b/lib/iris/tests/results/analysis/variance_latitude_longitude_1call.cml @@ -11,7 +11,7 @@ - + @@ -26,7 +26,7 @@ + 319544.0, 319545.0]" shape="(10,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/analysis/weighted_mean_original.cml b/lib/iris/tests/results/analysis/weighted_mean_original.cml index 2df84a8606..a69e633e26 100644 --- a/lib/iris/tests/results/analysis/weighted_mean_original.cml +++ b/lib/iris/tests/results/analysis/weighted_mean_original.cml @@ -10,7 +10,7 @@ - + - + diff --git a/lib/iris/tests/results/categorisation/customcheck.cml b/lib/iris/tests/results/categorisation/customcheck.cml index d6dcc7179d..476a1c56ef 100644 --- a/lib/iris/tests/results/categorisation/customcheck.cml +++ b/lib/iris/tests/results/categorisation/customcheck.cml @@ -19,7 +19,7 @@ + 513, 540, 567, 594]" shape="(23,)" standard_name="time" units="Unit('days since 1970-01-01 00:00:00', calendar='standard')" value_type="int32"/> diff --git a/lib/iris/tests/results/categorisation/quickcheck.cml b/lib/iris/tests/results/categorisation/quickcheck.cml index f64c70350f..b8f3904ad1 100644 --- a/lib/iris/tests/results/categorisation/quickcheck.cml +++ b/lib/iris/tests/results/categorisation/quickcheck.cml @@ -68,7 +68,7 @@ + 513, 540, 567, 594]" shape="(23,)" standard_name="time" units="Unit('days since 1970-01-01 00:00:00', calendar='standard')" value_type="int32"/> diff --git a/lib/iris/tests/results/cdm/extract/lat_eq_10.cml b/lib/iris/tests/results/cdm/extract/lat_eq_10.cml index f10c0be37c..e7213fc7bd 100644 --- a/lib/iris/tests/results/cdm/extract/lat_eq_10.cml +++ b/lib/iris/tests/results/cdm/extract/lat_eq_10.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cdm/extract/lat_gt_10.cml b/lib/iris/tests/results/cdm/extract/lat_gt_10.cml index e0d138f327..3ffbbf89e5 100644 --- a/lib/iris/tests/results/cdm/extract/lat_gt_10.cml +++ b/lib/iris/tests/results/cdm/extract/lat_gt_10.cml @@ -11,7 +11,7 @@ - + - + diff --git a/lib/iris/tests/results/cdm/extract/lat_gt_10_and_lon_ge_10.cml b/lib/iris/tests/results/cdm/extract/lat_gt_10_and_lon_ge_10.cml index 3b435e9ceb..7091aee748 100644 --- a/lib/iris/tests/results/cdm/extract/lat_gt_10_and_lon_ge_10.cml +++ b/lib/iris/tests/results/cdm/extract/lat_gt_10_and_lon_ge_10.cml @@ -11,7 +11,7 @@ - + - + diff --git a/lib/iris/tests/results/cdm/masked_cube.cml b/lib/iris/tests/results/cdm/masked_cube.cml index a38340913e..dcfa8c062f 100644 --- a/lib/iris/tests/results/cdm/masked_cube.cml +++ b/lib/iris/tests/results/cdm/masked_cube.cml @@ -10,7 +10,7 @@ - + + 1000.0, 1006.0]" shape="(8,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/constrained_load/all_10_load_match.cml b/lib/iris/tests/results/constrained_load/all_10_load_match.cml index 6a582f9d67..0712af20fa 100644 --- a/lib/iris/tests/results/constrained_load/all_10_load_match.cml +++ b/lib/iris/tests/results/constrained_load/all_10_load_match.cml @@ -11,7 +11,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -144,7 +144,7 @@ - + @@ -165,7 +165,7 @@ - + - + diff --git a/lib/iris/tests/results/constrained_load/all_ml_10_22_load_match.cml b/lib/iris/tests/results/constrained_load/all_ml_10_22_load_match.cml index 458474f98a..20971021ac 100644 --- a/lib/iris/tests/results/constrained_load/all_ml_10_22_load_match.cml +++ b/lib/iris/tests/results/constrained_load/all_ml_10_22_load_match.cml @@ -11,7 +11,7 @@ - + @@ -43,7 +43,7 @@ [0.222443, 0.177555]]" id="a5c170db" long_name="sigma" points="[0.784571, 0.199878]" shape="(2,)" units="Unit('1')" value_type="float32"/> - + @@ -64,7 +64,7 @@ - + @@ -96,7 +96,7 @@ [0.222443, 0.177555]]" id="a5c170db" long_name="sigma" points="[0.784571, 0.199878]" shape="(2,)" units="Unit('1')" value_type="float32"/> - + @@ -117,7 +117,7 @@ - + @@ -150,7 +150,7 @@ [0.246215, 0.199878]]" id="a5c170db" long_name="sigma" points="[0.803914, 0.222443]" shape="(2,)" units="Unit('1')" value_type="float32"/> - + @@ -171,7 +171,7 @@ - + - + diff --git a/lib/iris/tests/results/constrained_load/attribute_constraint.cml b/lib/iris/tests/results/constrained_load/attribute_constraint.cml index 31714035fa..664dc943bc 100644 --- a/lib/iris/tests/results/constrained_load/attribute_constraint.cml +++ b/lib/iris/tests/results/constrained_load/attribute_constraint.cml @@ -12,7 +12,7 @@ - + @@ -131,7 +131,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_match.cml b/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_match.cml index bbafc31987..44e7d077df 100644 --- a/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_match.cml @@ -11,7 +11,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -115,7 +115,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(11,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_strict.cml index bbafc31987..44e7d077df 100644 --- a/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_10_and_theta_level_gt_30_le_3_load_strict.cml @@ -11,7 +11,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -115,7 +115,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(11,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_10_load_match.cml b/lib/iris/tests/results/constrained_load/theta_10_load_match.cml index 2e5005d042..e2852d0151 100644 --- a/lib/iris/tests/results/constrained_load/theta_10_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_10_load_match.cml @@ -11,7 +11,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/lib/iris/tests/results/constrained_load/theta_10_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_10_load_strict.cml index 2e5005d042..e2852d0151 100644 --- a/lib/iris/tests/results/constrained_load/theta_10_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_10_load_strict.cml @@ -11,7 +11,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/lib/iris/tests/results/constrained_load/theta_and_all_10_load_match.cml b/lib/iris/tests/results/constrained_load/theta_and_all_10_load_match.cml index 40bb37f3ab..772929b0da 100644 --- a/lib/iris/tests/results/constrained_load/theta_and_all_10_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_and_all_10_load_match.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + @@ -151,7 +151,7 @@ - + @@ -181,7 +181,7 @@ - + @@ -202,7 +202,7 @@ - + @@ -232,7 +232,7 @@ - + @@ -253,7 +253,7 @@ - + @@ -284,7 +284,7 @@ - + @@ -305,7 +305,7 @@ - + - + diff --git a/lib/iris/tests/results/constrained_load/theta_and_theta_10_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_and_theta_10_load_strict.cml index 03fed4e61b..0e23de090c 100644 --- a/lib/iris/tests/results/constrained_load/theta_and_theta_10_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_and_theta_10_load_strict.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + @@ -151,7 +151,7 @@ - + @@ -181,7 +181,7 @@ - + diff --git a/lib/iris/tests/results/constrained_load/theta_and_theta_load_match.cml b/lib/iris/tests/results/constrained_load/theta_and_theta_load_match.cml index eadbe8f365..a175652c30 100644 --- a/lib/iris/tests/results/constrained_load/theta_and_theta_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_and_theta_load_match.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + @@ -151,7 +151,7 @@ - + @@ -270,7 +270,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_and_theta_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_and_theta_load_strict.cml index eadbe8f365..a175652c30 100644 --- a/lib/iris/tests/results/constrained_load/theta_and_theta_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_and_theta_load_strict.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + @@ -151,7 +151,7 @@ - + @@ -270,7 +270,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_match.cml b/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_match.cml index 77534b9b55..0048a742a6 100644 --- a/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_match.cml @@ -11,7 +11,7 @@ - + @@ -64,7 +64,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(11,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_strict.cml index 77534b9b55..0048a742a6 100644 --- a/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_gt_30_le_3_load_strict.cml @@ -11,7 +11,7 @@ - + @@ -64,7 +64,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(11,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_lat_30_load_match.cml b/lib/iris/tests/results/constrained_load/theta_lat_30_load_match.cml index f6727427a1..e24937854d 100644 --- a/lib/iris/tests/results/constrained_load/theta_lat_30_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_lat_30_load_match.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_lat_30_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_lat_30_load_strict.cml index f6727427a1..e24937854d 100644 --- a/lib/iris/tests/results/constrained_load/theta_lat_30_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_lat_30_load_strict.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_match.cml b/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_match.cml index daef7ba9dc..218bdd6b1c 100644 --- a/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_match.cml @@ -11,7 +11,7 @@ - + - + diff --git a/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_strict.cml index daef7ba9dc..218bdd6b1c 100644 --- a/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_lat_gt_30_load_strict.cml @@ -11,7 +11,7 @@ - + - + diff --git a/lib/iris/tests/results/constrained_load/theta_load_match.cml b/lib/iris/tests/results/constrained_load/theta_load_match.cml index 293e40cc3a..0e5b02be51 100644 --- a/lib/iris/tests/results/constrained_load/theta_load_match.cml +++ b/lib/iris/tests/results/constrained_load/theta_load_match.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/constrained_load/theta_load_strict.cml b/lib/iris/tests/results/constrained_load/theta_load_strict.cml index 293e40cc3a..0e5b02be51 100644 --- a/lib/iris/tests/results/constrained_load/theta_load_strict.cml +++ b/lib/iris/tests/results/constrained_load/theta_load_strict.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/coord_api/str_repr/dim_time_str.txt b/lib/iris/tests/results/coord_api/str_repr/dim_time_str.txt index 6b95b57215..410da3613a 100644 --- a/lib/iris/tests/results/coord_api/str_repr/dim_time_str.txt +++ b/lib/iris/tests/results/coord_api/str_repr/dim_time_str.txt @@ -1,4 +1,4 @@ -DimCoord : time / (hours since 1970-01-01 00:00:00, gregorian calendar) +DimCoord : time / (hours since 1970-01-01 00:00:00, standard calendar) points: [ 2009-09-09 17:10:00, 2009-09-09 17:20:00, 2009-09-09 17:30:00, 2009-09-09 17:40:00, 2009-09-09 17:50:00, 2009-09-09 18:00:00] diff --git a/lib/iris/tests/results/cube_collapsed/latitude_longitude_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/latitude_longitude_dual_stage.cml index 458b9bf908..463339e5bc 100644 --- a/lib/iris/tests/results/cube_collapsed/latitude_longitude_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/latitude_longitude_dual_stage.cml @@ -82,7 +82,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/latitude_longitude_single_stage.cml b/lib/iris/tests/results/cube_collapsed/latitude_longitude_single_stage.cml index a2f12b0b27..a91ea4ce5c 100644 --- a/lib/iris/tests/results/cube_collapsed/latitude_longitude_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/latitude_longitude_single_stage.cml @@ -82,7 +82,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_dual_stage.cml index 60539d5960..f963658910 100644 --- a/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_dual_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_single_stage.cml b/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_single_stage.cml index 466d0dd8cd..195757a417 100644 --- a/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/latitude_model_level_number_single_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/latitude_time_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/latitude_time_dual_stage.cml index 12bf9270d1..c63c260d25 100644 --- a/lib/iris/tests/results/cube_collapsed/latitude_time_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/latitude_time_dual_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/latitude_time_single_stage.cml b/lib/iris/tests/results/cube_collapsed/latitude_time_single_stage.cml index 9d1070140b..d6cc708aa1 100644 --- a/lib/iris/tests/results/cube_collapsed/latitude_time_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/latitude_time_single_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/longitude_latitude_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/longitude_latitude_dual_stage.cml index 4cd9da34f0..23739a1ac5 100644 --- a/lib/iris/tests/results/cube_collapsed/longitude_latitude_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/longitude_latitude_dual_stage.cml @@ -82,7 +82,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/longitude_latitude_single_stage.cml b/lib/iris/tests/results/cube_collapsed/longitude_latitude_single_stage.cml index dd87dc175b..817b855512 100644 --- a/lib/iris/tests/results/cube_collapsed/longitude_latitude_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/longitude_latitude_single_stage.cml @@ -82,7 +82,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_dual_stage.cml index 16ea40c33e..29d59ce111 100644 --- a/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_dual_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_single_stage.cml b/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_single_stage.cml index b01ede7936..e99d57b816 100644 --- a/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/longitude_model_level_number_single_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/longitude_time_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/longitude_time_dual_stage.cml index 8d38bb748c..8e57ec7258 100644 --- a/lib/iris/tests/results/cube_collapsed/longitude_time_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/longitude_time_dual_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/longitude_time_single_stage.cml b/lib/iris/tests/results/cube_collapsed/longitude_time_single_stage.cml index f4589831a8..67b706e0ae 100644 --- a/lib/iris/tests/results/cube_collapsed/longitude_time_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/longitude_time_single_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_dual_stage.cml index 138e0207c7..d9c1b2a35c 100644 --- a/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_dual_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_single_stage.cml b/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_single_stage.cml index 0e2cf8ef23..ceafb3fc67 100644 --- a/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/model_level_number_latitude_single_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_dual_stage.cml index bbc8272c65..e5090a3572 100644 --- a/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_dual_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_single_stage.cml b/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_single_stage.cml index ba5cd7a171..9e8bdebd4a 100644 --- a/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/model_level_number_longitude_single_stage.cml @@ -44,7 +44,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/model_level_number_time_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/model_level_number_time_dual_stage.cml index b835be4057..a4e0cc1445 100644 --- a/lib/iris/tests/results/cube_collapsed/model_level_number_time_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/model_level_number_time_dual_stage.cml @@ -50,7 +50,7 @@ - + diff --git a/lib/iris/tests/results/cube_collapsed/model_level_number_time_single_stage.cml b/lib/iris/tests/results/cube_collapsed/model_level_number_time_single_stage.cml index 93196268e7..d442637062 100644 --- a/lib/iris/tests/results/cube_collapsed/model_level_number_time_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/model_level_number_time_single_stage.cml @@ -50,7 +50,7 @@ - + diff --git a/lib/iris/tests/results/cube_collapsed/original.cml b/lib/iris/tests/results/cube_collapsed/original.cml index 10a81f21d2..4bc6553dba 100644 --- a/lib/iris/tests/results/cube_collapsed/original.cml +++ b/lib/iris/tests/results/cube_collapsed/original.cml @@ -96,7 +96,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_collapsed/time_latitude_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/time_latitude_dual_stage.cml index a4f2cc6084..788d0d8029 100644 --- a/lib/iris/tests/results/cube_collapsed/time_latitude_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/time_latitude_dual_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/time_latitude_single_stage.cml b/lib/iris/tests/results/cube_collapsed/time_latitude_single_stage.cml index 885328a856..b9b74c6b6d 100644 --- a/lib/iris/tests/results/cube_collapsed/time_latitude_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/time_latitude_single_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/time_longitude_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/time_longitude_dual_stage.cml index 273ad909d9..84b4fea150 100644 --- a/lib/iris/tests/results/cube_collapsed/time_longitude_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/time_longitude_dual_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/time_longitude_single_stage.cml b/lib/iris/tests/results/cube_collapsed/time_longitude_single_stage.cml index c2e2993874..128d29a281 100644 --- a/lib/iris/tests/results/cube_collapsed/time_longitude_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/time_longitude_single_stage.cml @@ -88,7 +88,7 @@ 0.0, 0.0]" shape="(70,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_collapsed/time_model_level_number_dual_stage.cml b/lib/iris/tests/results/cube_collapsed/time_model_level_number_dual_stage.cml index 4d6e85f8a8..8c206fe840 100644 --- a/lib/iris/tests/results/cube_collapsed/time_model_level_number_dual_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/time_model_level_number_dual_stage.cml @@ -50,7 +50,7 @@ - + diff --git a/lib/iris/tests/results/cube_collapsed/time_model_level_number_single_stage.cml b/lib/iris/tests/results/cube_collapsed/time_model_level_number_single_stage.cml index 8f7ccf9b8a..08dc52fca2 100644 --- a/lib/iris/tests/results/cube_collapsed/time_model_level_number_single_stage.cml +++ b/lib/iris/tests/results/cube_collapsed/time_model_level_number_single_stage.cml @@ -50,7 +50,7 @@ - + diff --git a/lib/iris/tests/results/cube_collapsed/triple_collapse_lat_ml_pt.cml b/lib/iris/tests/results/cube_collapsed/triple_collapse_lat_ml_pt.cml index 33b35b7eaa..5fae922867 100644 --- a/lib/iris/tests/results/cube_collapsed/triple_collapse_lat_ml_pt.cml +++ b/lib/iris/tests/results/cube_collapsed/triple_collapse_lat_ml_pt.cml @@ -43,7 +43,7 @@ - + diff --git a/lib/iris/tests/results/cube_collapsed/triple_collapse_ml_pt_lon.cml b/lib/iris/tests/results/cube_collapsed/triple_collapse_ml_pt_lon.cml index c3db78bd9e..454bd29a18 100644 --- a/lib/iris/tests/results/cube_collapsed/triple_collapse_ml_pt_lon.cml +++ b/lib/iris/tests/results/cube_collapsed/triple_collapse_ml_pt_lon.cml @@ -43,7 +43,7 @@ - + diff --git a/lib/iris/tests/results/cube_io/pickling/cubelist.cml b/lib/iris/tests/results/cube_io/pickling/cubelist.cml index 6cebe384aa..eb839e36e4 100644 --- a/lib/iris/tests/results/cube_io/pickling/cubelist.cml +++ b/lib/iris/tests/results/cube_io/pickling/cubelist.cml @@ -400,7 +400,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="forecast_reference_time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -528,7 +528,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="forecast_reference_time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_io/pickling/single_cube.cml b/lib/iris/tests/results/cube_io/pickling/single_cube.cml index 2cd3dbb3cb..a025713766 100644 --- a/lib/iris/tests/results/cube_io/pickling/single_cube.cml +++ b/lib/iris/tests/results/cube_io/pickling/single_cube.cml @@ -400,7 +400,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="forecast_reference_time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/cube_io/pickling/theta.cml b/lib/iris/tests/results/cube_io/pickling/theta.cml index 39ee6aecfd..6c69f6ed54 100644 --- a/lib/iris/tests/results/cube_io/pickling/theta.cml +++ b/lib/iris/tests/results/cube_io/pickling/theta.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/cube_io/pp/load/global.cml b/lib/iris/tests/results/cube_io/pp/load/global.cml index 2df84a8606..a69e633e26 100644 --- a/lib/iris/tests/results/cube_io/pp/load/global.cml +++ b/lib/iris/tests/results/cube_io/pp/load/global.cml @@ -10,7 +10,7 @@ - + - + diff --git a/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing1.cml b/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing1.cml index fc387bb663..f674c30121 100644 --- a/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing1.cml +++ b/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing1.cml @@ -11,7 +11,7 @@ - + @@ -24,7 +24,7 @@ - + diff --git a/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing2.cml b/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing2.cml index 9e5b5a57db..b1bf424a93 100644 --- a/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing2.cml +++ b/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing2.cml @@ -11,7 +11,7 @@ - + @@ -24,7 +24,7 @@ - + diff --git a/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing3.cml b/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing3.cml index 061255bbe4..50fd683cb3 100644 --- a/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing3.cml +++ b/lib/iris/tests/results/cube_slice/real_data_dual_tuple_indexing3.cml @@ -11,7 +11,7 @@ - + @@ -24,7 +24,7 @@ - + diff --git a/lib/iris/tests/results/cube_slice/real_empty_data_indexing.cml b/lib/iris/tests/results/cube_slice/real_empty_data_indexing.cml index 2f899b333e..1563dce74d 100644 --- a/lib/iris/tests/results/cube_slice/real_empty_data_indexing.cml +++ b/lib/iris/tests/results/cube_slice/real_empty_data_indexing.cml @@ -11,7 +11,7 @@ - + @@ -25,7 +25,7 @@ - + diff --git a/lib/iris/tests/results/cube_to_pp/no_forecast_period.cml b/lib/iris/tests/results/cube_to_pp/no_forecast_period.cml index 1c1e58c02b..5b7d800716 100644 --- a/lib/iris/tests/results/cube_to_pp/no_forecast_period.cml +++ b/lib/iris/tests/results/cube_to_pp/no_forecast_period.cml @@ -3,7 +3,7 @@ - + @@ -16,7 +16,7 @@ - + diff --git a/lib/iris/tests/results/cube_to_pp/no_forecast_time.cml b/lib/iris/tests/results/cube_to_pp/no_forecast_time.cml index 02d380a097..edf4392d30 100644 --- a/lib/iris/tests/results/cube_to_pp/no_forecast_time.cml +++ b/lib/iris/tests/results/cube_to_pp/no_forecast_time.cml @@ -13,7 +13,7 @@ - + diff --git a/lib/iris/tests/results/derived/column.cml b/lib/iris/tests/results/derived/column.cml index e4402b4e4d..827214dafa 100644 --- a/lib/iris/tests/results/derived/column.cml +++ b/lib/iris/tests/results/derived/column.cml @@ -111,7 +111,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/derived/no_orog.cml b/lib/iris/tests/results/derived/no_orog.cml index ec0ffdd5ff..844373675e 100644 --- a/lib/iris/tests/results/derived/no_orog.cml +++ b/lib/iris/tests/results/derived/no_orog.cml @@ -136,7 +136,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/derived/removed_derived_coord.cml b/lib/iris/tests/results/derived/removed_derived_coord.cml index 12feb2b643..5175d88875 100644 --- a/lib/iris/tests/results/derived/removed_derived_coord.cml +++ b/lib/iris/tests/results/derived/removed_derived_coord.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/derived/removed_orog.cml b/lib/iris/tests/results/derived/removed_orog.cml index 4c30ec69bc..982e38fd1e 100644 --- a/lib/iris/tests/results/derived/removed_orog.cml +++ b/lib/iris/tests/results/derived/removed_orog.cml @@ -122,7 +122,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/derived/removed_sigma.cml b/lib/iris/tests/results/derived/removed_sigma.cml index ea34680b7d..3908c22188 100644 --- a/lib/iris/tests/results/derived/removed_sigma.cml +++ b/lib/iris/tests/results/derived/removed_sigma.cml @@ -462,7 +462,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/derived/transposed.cml b/lib/iris/tests/results/derived/transposed.cml index eef077d774..c44857bd61 100644 --- a/lib/iris/tests/results/derived/transposed.cml +++ b/lib/iris/tests/results/derived/transposed.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/experimental/analysis/interpolate/LinearInterpolator/orthogonal_cube_with_factory.cml b/lib/iris/tests/results/experimental/analysis/interpolate/LinearInterpolator/orthogonal_cube_with_factory.cml index 1bb899c558..c7200d6106 100644 --- a/lib/iris/tests/results/experimental/analysis/interpolate/LinearInterpolator/orthogonal_cube_with_factory.cml +++ b/lib/iris/tests/results/experimental/analysis/interpolate/LinearInterpolator/orthogonal_cube_with_factory.cml @@ -31,7 +31,7 @@ - + diff --git a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml index 585657b642..b41c0e48c7 100644 --- a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml +++ b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml @@ -66,7 +66,7 @@ [0.989272, 0.984692]]" id="a5c170db" long_name="sigma" points="[0.999424, 0.997504, 0.99482, 0.991375, 0.987171]" shape="(5,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml index 4e928851fe..8617be9372 100644 --- a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml +++ b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml @@ -60,7 +60,7 @@ [0.989272, 0.984692]]" id="a5c170db" long_name="sigma" points="[0.999424, 0.997504, 0.99482, 0.991375, 0.987171]" shape="(5,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/hybridheight.cml b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/hybridheight.cml index 31a753c059..70df0e198d 100644 --- a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/hybridheight.cml +++ b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/hybridheight.cml @@ -429,7 +429,7 @@ 218.732, 216.367]]" shape="(16, 21)" standard_name="surface_altitude" units="Unit('m')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml index be79f3ff57..b863adcf55 100644 --- a/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml @@ -34,7 +34,7 @@ -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml index 568c835e97..b46908a648 100644 --- a/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml @@ -53,7 +53,7 @@ 16800.0, 17100.0, 17400.0, 17700.0, 18000.0, 18300.0, 18600.0, 18900.0, 19200.0, 19500.0, 19800.0, 20100.0, 20400.0, 20700.0, 21000.0, - 21300.0, 21600.0]" shape="(72,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='gregorian')" value_type="float64" var_name="time_instant"> + 21300.0, 21600.0]" shape="(72,)" standard_name="time" units="Unit('seconds since 2016-01-01 15:00:00', calendar='standard')" value_type="float64" var_name="time_instant"> diff --git a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml index 6d7873daaa..57209e4ba7 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_full_levels.cml @@ -45,7 +45,7 @@ -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml index b664e3cf6f..c260587921 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml @@ -45,7 +45,7 @@ -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml index b30d443495..e545e05fdc 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml @@ -34,7 +34,7 @@ -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml index 157755298d..4eedfc21b3 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml @@ -34,7 +34,7 @@ -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml index a9eba1a80d..55155047bb 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml @@ -34,7 +34,7 @@ -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml index e90c048803..fc52fce0b3 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml @@ -34,7 +34,7 @@ -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/file_load/theta_levels.cml b/lib/iris/tests/results/file_load/theta_levels.cml index b4ae2a4b35..fc708b7949 100644 --- a/lib/iris/tests/results/file_load/theta_levels.cml +++ b/lib/iris/tests/results/file_load/theta_levels.cml @@ -11,7 +11,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -62,7 +62,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -143,7 +143,7 @@ - + @@ -164,7 +164,7 @@ - + @@ -194,7 +194,7 @@ - + @@ -215,7 +215,7 @@ - + @@ -245,7 +245,7 @@ - + @@ -266,7 +266,7 @@ - + @@ -296,7 +296,7 @@ - + @@ -317,7 +317,7 @@ - + @@ -347,7 +347,7 @@ - + @@ -368,7 +368,7 @@ - + @@ -398,7 +398,7 @@ - + @@ -419,7 +419,7 @@ - + @@ -449,7 +449,7 @@ - + @@ -470,7 +470,7 @@ - + @@ -500,7 +500,7 @@ - + @@ -521,7 +521,7 @@ - + @@ -551,7 +551,7 @@ - + @@ -572,7 +572,7 @@ - + @@ -602,7 +602,7 @@ - + @@ -623,7 +623,7 @@ - + @@ -653,7 +653,7 @@ - + @@ -674,7 +674,7 @@ - + @@ -704,7 +704,7 @@ - + @@ -725,7 +725,7 @@ - + @@ -755,7 +755,7 @@ - + @@ -776,7 +776,7 @@ - + @@ -806,7 +806,7 @@ - + @@ -827,7 +827,7 @@ - + @@ -857,7 +857,7 @@ - + @@ -878,7 +878,7 @@ - + @@ -908,7 +908,7 @@ - + @@ -929,7 +929,7 @@ - + @@ -959,7 +959,7 @@ - + @@ -980,7 +980,7 @@ - + @@ -1010,7 +1010,7 @@ - + @@ -1031,7 +1031,7 @@ - + @@ -1061,7 +1061,7 @@ - + @@ -1082,7 +1082,7 @@ - + @@ -1112,7 +1112,7 @@ - + @@ -1133,7 +1133,7 @@ - + @@ -1163,7 +1163,7 @@ - + @@ -1184,7 +1184,7 @@ - + @@ -1214,7 +1214,7 @@ - + @@ -1235,7 +1235,7 @@ - + @@ -1265,7 +1265,7 @@ - + @@ -1286,7 +1286,7 @@ - + @@ -1316,7 +1316,7 @@ - + @@ -1337,7 +1337,7 @@ - + @@ -1367,7 +1367,7 @@ - + @@ -1388,7 +1388,7 @@ - + @@ -1418,7 +1418,7 @@ - + @@ -1439,7 +1439,7 @@ - + @@ -1469,7 +1469,7 @@ - + @@ -1490,7 +1490,7 @@ - + @@ -1520,7 +1520,7 @@ - + @@ -1541,7 +1541,7 @@ - + @@ -1571,7 +1571,7 @@ - + @@ -1592,7 +1592,7 @@ - + @@ -1622,7 +1622,7 @@ - + @@ -1643,7 +1643,7 @@ - + @@ -1673,7 +1673,7 @@ - + @@ -1694,7 +1694,7 @@ - + @@ -1724,7 +1724,7 @@ - + @@ -1745,7 +1745,7 @@ - + @@ -1775,7 +1775,7 @@ - + @@ -1796,7 +1796,7 @@ - + @@ -1826,7 +1826,7 @@ - + @@ -1847,7 +1847,7 @@ - + @@ -1877,7 +1877,7 @@ - + @@ -1898,7 +1898,7 @@ - + @@ -1928,7 +1928,7 @@ - + diff --git a/lib/iris/tests/results/file_load/u_wind_levels.cml b/lib/iris/tests/results/file_load/u_wind_levels.cml index 68a3b45f07..5d1af58f6c 100644 --- a/lib/iris/tests/results/file_load/u_wind_levels.cml +++ b/lib/iris/tests/results/file_load/u_wind_levels.cml @@ -11,7 +11,7 @@ - + @@ -42,7 +42,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -115,7 +115,7 @@ - + @@ -146,7 +146,7 @@ - + @@ -167,7 +167,7 @@ - + @@ -198,7 +198,7 @@ - + @@ -219,7 +219,7 @@ - + @@ -250,7 +250,7 @@ - + @@ -271,7 +271,7 @@ - + @@ -302,7 +302,7 @@ - + @@ -323,7 +323,7 @@ - + @@ -354,7 +354,7 @@ - + @@ -375,7 +375,7 @@ - + @@ -406,7 +406,7 @@ - + @@ -427,7 +427,7 @@ - + @@ -458,7 +458,7 @@ - + @@ -479,7 +479,7 @@ - + @@ -510,7 +510,7 @@ - + @@ -531,7 +531,7 @@ - + @@ -562,7 +562,7 @@ - + @@ -583,7 +583,7 @@ - + @@ -614,7 +614,7 @@ - + @@ -635,7 +635,7 @@ - + @@ -666,7 +666,7 @@ - + @@ -687,7 +687,7 @@ - + @@ -718,7 +718,7 @@ - + @@ -739,7 +739,7 @@ - + @@ -770,7 +770,7 @@ - + @@ -791,7 +791,7 @@ - + @@ -822,7 +822,7 @@ - + @@ -843,7 +843,7 @@ - + @@ -874,7 +874,7 @@ - + @@ -895,7 +895,7 @@ - + @@ -926,7 +926,7 @@ - + @@ -947,7 +947,7 @@ - + @@ -978,7 +978,7 @@ - + @@ -999,7 +999,7 @@ - + @@ -1030,7 +1030,7 @@ - + @@ -1051,7 +1051,7 @@ - + @@ -1082,7 +1082,7 @@ - + @@ -1103,7 +1103,7 @@ - + @@ -1134,7 +1134,7 @@ - + @@ -1155,7 +1155,7 @@ - + @@ -1186,7 +1186,7 @@ - + @@ -1207,7 +1207,7 @@ - + @@ -1238,7 +1238,7 @@ - + @@ -1259,7 +1259,7 @@ - + @@ -1290,7 +1290,7 @@ - + @@ -1311,7 +1311,7 @@ - + @@ -1342,7 +1342,7 @@ - + @@ -1363,7 +1363,7 @@ - + @@ -1394,7 +1394,7 @@ - + @@ -1415,7 +1415,7 @@ - + @@ -1446,7 +1446,7 @@ - + @@ -1467,7 +1467,7 @@ - + @@ -1498,7 +1498,7 @@ - + @@ -1519,7 +1519,7 @@ - + @@ -1550,7 +1550,7 @@ - + @@ -1571,7 +1571,7 @@ - + @@ -1602,7 +1602,7 @@ - + @@ -1623,7 +1623,7 @@ - + @@ -1654,7 +1654,7 @@ - + @@ -1675,7 +1675,7 @@ - + @@ -1706,7 +1706,7 @@ - + @@ -1727,7 +1727,7 @@ - + @@ -1758,7 +1758,7 @@ - + @@ -1779,7 +1779,7 @@ - + @@ -1810,7 +1810,7 @@ - + @@ -1831,7 +1831,7 @@ - + @@ -1862,7 +1862,7 @@ - + @@ -1883,7 +1883,7 @@ - + @@ -1914,7 +1914,7 @@ - + @@ -1935,7 +1935,7 @@ - + @@ -1966,7 +1966,7 @@ - + diff --git a/lib/iris/tests/results/file_load/v_wind_levels.cml b/lib/iris/tests/results/file_load/v_wind_levels.cml index 9ccdade1bd..c7145a7e9e 100644 --- a/lib/iris/tests/results/file_load/v_wind_levels.cml +++ b/lib/iris/tests/results/file_load/v_wind_levels.cml @@ -11,7 +11,7 @@ - + - + @@ -63,7 +63,7 @@ - + - + @@ -115,7 +115,7 @@ - + - + @@ -167,7 +167,7 @@ - + - + @@ -219,7 +219,7 @@ - + - + @@ -271,7 +271,7 @@ - + - + @@ -323,7 +323,7 @@ - + - + @@ -375,7 +375,7 @@ - + - + @@ -427,7 +427,7 @@ - + - + @@ -479,7 +479,7 @@ - + - + @@ -531,7 +531,7 @@ - + - + @@ -583,7 +583,7 @@ - + - + @@ -635,7 +635,7 @@ - + - + @@ -687,7 +687,7 @@ - + - + @@ -739,7 +739,7 @@ - + - + @@ -791,7 +791,7 @@ - + - + @@ -843,7 +843,7 @@ - + - + @@ -895,7 +895,7 @@ - + - + @@ -947,7 +947,7 @@ - + - + @@ -999,7 +999,7 @@ - + - + @@ -1051,7 +1051,7 @@ - + - + @@ -1103,7 +1103,7 @@ - + - + @@ -1155,7 +1155,7 @@ - + - + @@ -1207,7 +1207,7 @@ - + - + @@ -1259,7 +1259,7 @@ - + - + @@ -1311,7 +1311,7 @@ - + - + @@ -1363,7 +1363,7 @@ - + - + @@ -1415,7 +1415,7 @@ - + - + @@ -1467,7 +1467,7 @@ - + - + @@ -1519,7 +1519,7 @@ - + - + @@ -1571,7 +1571,7 @@ - + - + @@ -1623,7 +1623,7 @@ - + - + @@ -1675,7 +1675,7 @@ - + - + @@ -1727,7 +1727,7 @@ - + - + @@ -1779,7 +1779,7 @@ - + - + @@ -1831,7 +1831,7 @@ - + - + @@ -1883,7 +1883,7 @@ - + - + @@ -1935,7 +1935,7 @@ - + - + diff --git a/lib/iris/tests/results/file_load/wind_levels.cml b/lib/iris/tests/results/file_load/wind_levels.cml index 96d821fc1c..33584deec6 100644 --- a/lib/iris/tests/results/file_load/wind_levels.cml +++ b/lib/iris/tests/results/file_load/wind_levels.cml @@ -11,7 +11,7 @@ - + @@ -42,7 +42,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -115,7 +115,7 @@ - + @@ -146,7 +146,7 @@ - + @@ -167,7 +167,7 @@ - + @@ -198,7 +198,7 @@ - + @@ -219,7 +219,7 @@ - + @@ -250,7 +250,7 @@ - + @@ -271,7 +271,7 @@ - + @@ -302,7 +302,7 @@ - + @@ -323,7 +323,7 @@ - + @@ -354,7 +354,7 @@ - + @@ -375,7 +375,7 @@ - + @@ -406,7 +406,7 @@ - + @@ -427,7 +427,7 @@ - + @@ -458,7 +458,7 @@ - + @@ -479,7 +479,7 @@ - + @@ -510,7 +510,7 @@ - + @@ -531,7 +531,7 @@ - + @@ -562,7 +562,7 @@ - + @@ -583,7 +583,7 @@ - + @@ -614,7 +614,7 @@ - + @@ -635,7 +635,7 @@ - + @@ -666,7 +666,7 @@ - + @@ -687,7 +687,7 @@ - + @@ -718,7 +718,7 @@ - + @@ -739,7 +739,7 @@ - + @@ -770,7 +770,7 @@ - + @@ -791,7 +791,7 @@ - + @@ -822,7 +822,7 @@ - + @@ -843,7 +843,7 @@ - + @@ -874,7 +874,7 @@ - + @@ -895,7 +895,7 @@ - + @@ -926,7 +926,7 @@ - + @@ -947,7 +947,7 @@ - + @@ -978,7 +978,7 @@ - + @@ -999,7 +999,7 @@ - + @@ -1030,7 +1030,7 @@ - + @@ -1051,7 +1051,7 @@ - + @@ -1082,7 +1082,7 @@ - + @@ -1103,7 +1103,7 @@ - + @@ -1134,7 +1134,7 @@ - + @@ -1155,7 +1155,7 @@ - + @@ -1186,7 +1186,7 @@ - + @@ -1207,7 +1207,7 @@ - + @@ -1238,7 +1238,7 @@ - + @@ -1259,7 +1259,7 @@ - + @@ -1290,7 +1290,7 @@ - + @@ -1311,7 +1311,7 @@ - + @@ -1342,7 +1342,7 @@ - + @@ -1363,7 +1363,7 @@ - + @@ -1394,7 +1394,7 @@ - + @@ -1415,7 +1415,7 @@ - + @@ -1446,7 +1446,7 @@ - + @@ -1467,7 +1467,7 @@ - + @@ -1498,7 +1498,7 @@ - + @@ -1519,7 +1519,7 @@ - + @@ -1550,7 +1550,7 @@ - + @@ -1571,7 +1571,7 @@ - + @@ -1602,7 +1602,7 @@ - + @@ -1623,7 +1623,7 @@ - + @@ -1654,7 +1654,7 @@ - + @@ -1675,7 +1675,7 @@ - + @@ -1706,7 +1706,7 @@ - + @@ -1727,7 +1727,7 @@ - + @@ -1758,7 +1758,7 @@ - + @@ -1779,7 +1779,7 @@ - + @@ -1810,7 +1810,7 @@ - + @@ -1831,7 +1831,7 @@ - + @@ -1862,7 +1862,7 @@ - + @@ -1883,7 +1883,7 @@ - + @@ -1914,7 +1914,7 @@ - + @@ -1935,7 +1935,7 @@ - + @@ -1966,7 +1966,7 @@ - + @@ -1987,7 +1987,7 @@ - + - + @@ -2039,7 +2039,7 @@ - + - + @@ -2091,7 +2091,7 @@ - + - + @@ -2143,7 +2143,7 @@ - + - + @@ -2195,7 +2195,7 @@ - + - + @@ -2247,7 +2247,7 @@ - + - + @@ -2299,7 +2299,7 @@ - + - + @@ -2351,7 +2351,7 @@ - + - + @@ -2403,7 +2403,7 @@ - + - + @@ -2455,7 +2455,7 @@ - + - + @@ -2507,7 +2507,7 @@ - + - + @@ -2559,7 +2559,7 @@ - + - + @@ -2611,7 +2611,7 @@ - + - + @@ -2663,7 +2663,7 @@ - + - + @@ -2715,7 +2715,7 @@ - + - + @@ -2767,7 +2767,7 @@ - + - + @@ -2819,7 +2819,7 @@ - + - + @@ -2871,7 +2871,7 @@ - + - + @@ -2923,7 +2923,7 @@ - + - + @@ -2975,7 +2975,7 @@ - + - + @@ -3027,7 +3027,7 @@ - + - + @@ -3079,7 +3079,7 @@ - + - + @@ -3131,7 +3131,7 @@ - + - + @@ -3183,7 +3183,7 @@ - + - + @@ -3235,7 +3235,7 @@ - + - + @@ -3287,7 +3287,7 @@ - + - + @@ -3339,7 +3339,7 @@ - + - + @@ -3391,7 +3391,7 @@ - + - + @@ -3443,7 +3443,7 @@ - + - + @@ -3495,7 +3495,7 @@ - + - + @@ -3547,7 +3547,7 @@ - + - + @@ -3599,7 +3599,7 @@ - + - + @@ -3651,7 +3651,7 @@ - + - + @@ -3703,7 +3703,7 @@ - + - + @@ -3755,7 +3755,7 @@ - + - + @@ -3807,7 +3807,7 @@ - + - + @@ -3859,7 +3859,7 @@ - + - + @@ -3911,7 +3911,7 @@ - + - + diff --git a/lib/iris/tests/results/integration/climatology/TestClimatology/reference_simpledata.cdl b/lib/iris/tests/results/integration/climatology/TestClimatology/reference_simpledata.cdl index 1f6bc36832..2873f68205 100644 --- a/lib/iris/tests/results/integration/climatology/TestClimatology/reference_simpledata.cdl +++ b/lib/iris/tests/results/integration/climatology/TestClimatology/reference_simpledata.cdl @@ -13,7 +13,7 @@ variables: time:climatology = "time_climatology" ; time:units = "days since 1970-01-01 00:00:00-00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_climatology(time, bnds) ; double latitude(latitude) ; latitude:axis = "Y" ; diff --git a/lib/iris/tests/results/integration/netcdf/TestAtmosphereSigma/save.cdl b/lib/iris/tests/results/integration/netcdf/TestAtmosphereSigma/save.cdl index cfb3143050..762226192c 100644 --- a/lib/iris/tests/results/integration/netcdf/TestAtmosphereSigma/save.cdl +++ b/lib/iris/tests/results/integration/netcdf/TestAtmosphereSigma/save.cdl @@ -21,7 +21,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; int model_level_number(model_level_number) ; model_level_number:axis = "Z" ; model_level_number:units = "1" ; diff --git a/lib/iris/tests/results/integration/netcdf/TestHybridPressure/save.cdl b/lib/iris/tests/results/integration/netcdf/TestHybridPressure/save.cdl index 88c5fc18fe..6fed33430a 100644 --- a/lib/iris/tests/results/integration/netcdf/TestHybridPressure/save.cdl +++ b/lib/iris/tests/results/integration/netcdf/TestHybridPressure/save.cdl @@ -21,7 +21,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; int model_level_number(model_level_number) ; model_level_number:axis = "Z" ; model_level_number:units = "1" ; diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_manual.cdl b/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_manual.cdl index 65da679ad0..fece18b1f3 100644 --- a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_manual.cdl +++ b/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_manual.cdl @@ -32,7 +32,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; double height ; height:units = "m" ; height:standard_name = "height" ; @@ -41,7 +41,7 @@ variables: time:bounds = "time_bnds" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_bnds(bnds) ; // global attributes: diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_signed.cdl b/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_signed.cdl index 65da679ad0..fece18b1f3 100644 --- a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_signed.cdl +++ b/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_signed.cdl @@ -32,7 +32,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; double height ; height:units = "m" ; height:standard_name = "height" ; @@ -41,7 +41,7 @@ variables: time:bounds = "time_bnds" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_bnds(bnds) ; // global attributes: diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_unsigned.cdl b/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_unsigned.cdl index d7a39d72de..c85ba6aadd 100644 --- a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_unsigned.cdl +++ b/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_unsigned.cdl @@ -32,7 +32,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; double height ; height:units = "m" ; height:standard_name = "height" ; @@ -41,7 +41,7 @@ variables: time:bounds = "time_bnds" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_bnds(bnds) ; // global attributes: diff --git a/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl b/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl index 5ff22a679b..d813ab98dc 100644 --- a/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl +++ b/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl @@ -21,7 +21,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; int model_level_number(model_level_number) ; model_level_number:axis = "Z" ; model_level_number:units = "1" ; diff --git a/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml b/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml index 4d37f856ad..09d54a1b19 100644 --- a/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml +++ b/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml @@ -58,7 +58,7 @@ [124, 125, 126, 127, 128, 129]]" shape="(5, 6)" standard_name="surface_altitude" units="Unit('m')" value_type="int64" var_name="surface_altitude"/> - + @@ -122,7 +122,7 @@ [1240, 1250, 1260, 1270, 1280, 1290]]" shape="(5, 6)" units="Unit('m')" value_type="int64" var_name="surface_altitude_0"/> - + diff --git a/lib/iris/tests/results/merge/dec.cml b/lib/iris/tests/results/merge/dec.cml index ea72b506f0..4efd40910f 100644 --- a/lib/iris/tests/results/merge/dec.cml +++ b/lib/iris/tests/results/merge/dec.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + @@ -151,7 +151,7 @@ - + @@ -270,7 +270,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + @@ -291,7 +291,7 @@ - + @@ -411,7 +411,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + @@ -432,7 +432,7 @@ - + - + diff --git a/lib/iris/tests/results/merge/theta.cml b/lib/iris/tests/results/merge/theta.cml index 293e40cc3a..0e5b02be51 100644 --- a/lib/iris/tests/results/merge/theta.cml +++ b/lib/iris/tests/results/merge/theta.cml @@ -11,7 +11,7 @@ - + @@ -130,7 +130,7 @@ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]" shape="(38,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/merge/theta_two_times.cml b/lib/iris/tests/results/merge/theta_two_times.cml index 0dd396e337..d1c9f59ace 100644 --- a/lib/iris/tests/results/merge/theta_two_times.cml +++ b/lib/iris/tests/results/merge/theta_two_times.cml @@ -399,7 +399,7 @@ - + - + diff --git a/lib/iris/tests/results/name/NAMEIII_field.cml b/lib/iris/tests/results/name/NAMEIII_field.cml index 97b3189bba..c419a2760d 100644 --- a/lib/iris/tests/results/name/NAMEIII_field.cml +++ b/lib/iris/tests/results/name/NAMEIII_field.cml @@ -48,7 +48,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -177,7 +177,7 @@ - + @@ -241,7 +241,7 @@ - + @@ -305,7 +305,7 @@ - + diff --git a/lib/iris/tests/results/name/NAMEIII_timeseries.cml b/lib/iris/tests/results/name/NAMEIII_timeseries.cml index c4e70590a2..3776bfc27f 100644 --- a/lib/iris/tests/results/name/NAMEIII_timeseries.cml +++ b/lib/iris/tests/results/name/NAMEIII_timeseries.cml @@ -58,7 +58,7 @@ 358342.0, 358343.0, 358344.0, 358345.0, 358346.0, 358347.0, 358348.0, 358349.0, 358350.0, 358351.0, 358352.0, 358353.0, - 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -129,7 +129,7 @@ 358342.0, 358343.0, 358344.0, 358345.0, 358346.0, 358347.0, 358348.0, 358349.0, 358350.0, 358351.0, 358352.0, 358353.0, - 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -199,7 +199,7 @@ 358342.0, 358343.0, 358344.0, 358345.0, 358346.0, 358347.0, 358348.0, 358349.0, 358350.0, 358351.0, 358352.0, 358353.0, - 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -269,7 +269,7 @@ 358342.0, 358343.0, 358344.0, 358345.0, 358346.0, 358347.0, 358348.0, 358349.0, 358350.0, 358351.0, 358352.0, 358353.0, - 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -339,7 +339,7 @@ 358342.0, 358343.0, 358344.0, 358345.0, 358346.0, 358347.0, 358348.0, 358349.0, 358350.0, 358351.0, 358352.0, 358353.0, - 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 358354.0, 358355.0, 358356.0, 358357.0]" shape="(72,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/name/NAMEIII_trajectory.cml b/lib/iris/tests/results/name/NAMEIII_trajectory.cml index c514d589ca..20a0ec3b82 100644 --- a/lib/iris/tests/results/name/NAMEIII_trajectory.cml +++ b/lib/iris/tests/results/name/NAMEIII_trajectory.cml @@ -16,7 +16,7 @@ - + @@ -39,7 +39,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -106,7 +106,7 @@ - + @@ -129,7 +129,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -151,7 +151,7 @@ - + @@ -174,7 +174,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -196,7 +196,7 @@ - + @@ -219,7 +219,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -241,7 +241,7 @@ - + @@ -264,7 +264,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -286,7 +286,7 @@ - + @@ -309,7 +309,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -331,7 +331,7 @@ - + @@ -354,7 +354,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -376,7 +376,7 @@ - + @@ -399,7 +399,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -421,7 +421,7 @@ - + @@ -444,7 +444,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -466,7 +466,7 @@ - + @@ -489,7 +489,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -511,7 +511,7 @@ - + @@ -534,7 +534,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -556,7 +556,7 @@ - + @@ -579,7 +579,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -601,7 +601,7 @@ - + @@ -624,7 +624,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -646,7 +646,7 @@ - + @@ -669,7 +669,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -691,7 +691,7 @@ - + @@ -714,7 +714,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -736,7 +736,7 @@ - + @@ -759,7 +759,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/name/NAMEIII_trajectory0.cml b/lib/iris/tests/results/name/NAMEIII_trajectory0.cml index 5f10016f39..d337ca9454 100644 --- a/lib/iris/tests/results/name/NAMEIII_trajectory0.cml +++ b/lib/iris/tests/results/name/NAMEIII_trajectory0.cml @@ -16,7 +16,7 @@ - + @@ -39,7 +39,7 @@ + 366886.75, 366887.0]" shape="(836,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/name/NAMEIII_version2.cml b/lib/iris/tests/results/name/NAMEIII_version2.cml index 95b9db7d5b..0ad0c883a2 100644 --- a/lib/iris/tests/results/name/NAMEIII_version2.cml +++ b/lib/iris/tests/results/name/NAMEIII_version2.cml @@ -76,7 +76,7 @@ 402921.0, 402922.0, 402923.0, 402924.0, 402925.0, 402926.0, 402927.0, 402928.0, 402929.0, 402930.0, 402931.0, 402932.0, - 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -158,7 +158,7 @@ 402921.0, 402922.0, 402923.0, 402924.0, 402925.0, 402926.0, 402927.0, 402928.0, 402929.0, 402930.0, 402931.0, 402932.0, - 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -240,7 +240,7 @@ 402921.0, 402922.0, 402923.0, 402924.0, 402925.0, 402926.0, 402927.0, 402928.0, 402929.0, 402930.0, 402931.0, 402932.0, - 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -322,7 +322,7 @@ 402921.0, 402922.0, 402923.0, 402924.0, 402925.0, 402926.0, 402927.0, 402928.0, 402929.0, 402930.0, 402931.0, 402932.0, - 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 402933.0, 402934.0, 402935.0, 402936.0]" shape="(24,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/name/NAMEII_field.cml b/lib/iris/tests/results/name/NAMEII_field.cml index 664669ef62..7d88c06eff 100644 --- a/lib/iris/tests/results/name/NAMEII_field.cml +++ b/lib/iris/tests/results/name/NAMEII_field.cml @@ -51,7 +51,7 @@ - + @@ -112,7 +112,7 @@ - + @@ -166,7 +166,7 @@ - + @@ -227,7 +227,7 @@ - + @@ -288,7 +288,7 @@ - + diff --git a/lib/iris/tests/results/name/NAMEII_timeseries.cml b/lib/iris/tests/results/name/NAMEII_timeseries.cml index 52aaa8b809..39af8a6288 100644 --- a/lib/iris/tests/results/name/NAMEII_timeseries.cml +++ b/lib/iris/tests/results/name/NAMEII_timeseries.cml @@ -36,7 +36,7 @@ [370473.5, 370474.5], [370474.5, 370475.5], [370475.5, 370476.5]]" id="cb784457" points="[370345.0, 370346.0, 370347.0, ..., 370474.0, - 370475.0, 370476.0]" shape="(132,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 370475.0, 370476.0]" shape="(132,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -85,7 +85,7 @@ [370473.5, 370474.5], [370474.5, 370475.5], [370475.5, 370476.5]]" id="cb784457" points="[370345.0, 370346.0, 370347.0, ..., 370474.0, - 370475.0, 370476.0]" shape="(132,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='gregorian')" value_type="float64"/> + 370475.0, 370476.0]" shape="(132,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/aliases.cdl b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/aliases.cdl index e6a18dd2e4..da0d1d10db 100644 --- a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/aliases.cdl +++ b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/aliases.cdl @@ -20,7 +20,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double grid_latitude(grid_latitude) ; grid_latitude:axis = "Y" ; grid_latitude:units = "degrees" ; diff --git a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/flag.cdl b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/flag.cdl index 22ee23e2f6..ef1ef973e2 100644 --- a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/flag.cdl +++ b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/flag.cdl @@ -20,7 +20,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double grid_latitude(grid_latitude) ; grid_latitude:axis = "Y" ; grid_latitude:units = "degrees" ; diff --git a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/fulldims.cdl b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/fulldims.cdl index 50ebd1abc9..1d33942464 100644 --- a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/fulldims.cdl +++ b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/fulldims.cdl @@ -20,7 +20,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double grid_latitude(grid_latitude) ; grid_latitude:axis = "Y" ; grid_latitude:units = "degrees" ; diff --git a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/multiple.cdl b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/multiple.cdl index 9ae68a1112..5a0edc7528 100644 --- a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/multiple.cdl +++ b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/multiple.cdl @@ -20,7 +20,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double grid_latitude(grid_latitude) ; grid_latitude:axis = "Y" ; grid_latitude:units = "degrees" ; diff --git a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/partialdims.cdl b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/partialdims.cdl index 4d54fe36f0..81d32bf80c 100644 --- a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/partialdims.cdl +++ b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/partialdims.cdl @@ -20,7 +20,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double grid_latitude(grid_latitude) ; grid_latitude:axis = "Y" ; grid_latitude:units = "degrees" ; diff --git a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/shared.cdl b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/shared.cdl index 84516e186f..c6b29c5bda 100644 --- a/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/shared.cdl +++ b/lib/iris/tests/results/netcdf/TestNetCDFSave__ancillaries/shared.cdl @@ -20,7 +20,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double grid_latitude(grid_latitude) ; grid_latitude:axis = "Y" ; grid_latitude:units = "degrees" ; diff --git a/lib/iris/tests/results/netcdf/netcdf_cell_methods.cml b/lib/iris/tests/results/netcdf/netcdf_cell_methods.cml index ca4a0eb017..c748853c5c 100644 --- a/lib/iris/tests/results/netcdf/netcdf_cell_methods.cml +++ b/lib/iris/tests/results/netcdf/netcdf_cell_methods.cml @@ -9,7 +9,7 @@ - + @@ -29,7 +29,7 @@ - + @@ -50,7 +50,7 @@ - + @@ -75,7 +75,7 @@ - + @@ -98,7 +98,7 @@ - + @@ -121,7 +121,7 @@ - + @@ -140,7 +140,7 @@ - + @@ -159,7 +159,7 @@ - + @@ -179,7 +179,7 @@ - + @@ -199,7 +199,7 @@ - + @@ -222,7 +222,7 @@ - + @@ -241,7 +241,7 @@ - + @@ -261,7 +261,7 @@ - + @@ -281,7 +281,7 @@ - + @@ -304,7 +304,7 @@ - + @@ -323,7 +323,7 @@ - + @@ -336,7 +336,7 @@ - + @@ -349,7 +349,7 @@ - + @@ -362,7 +362,7 @@ - + @@ -375,7 +375,7 @@ - + @@ -394,7 +394,7 @@ - + @@ -413,7 +413,7 @@ - + @@ -433,7 +433,7 @@ - + @@ -450,7 +450,7 @@ - + @@ -463,7 +463,7 @@ - + @@ -476,7 +476,7 @@ - + @@ -489,7 +489,7 @@ - + @@ -502,7 +502,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_index_0.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_index_0.cml index a11d593684..3847d5a417 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_index_0.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_index_0.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_index_1.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_index_1.cml index 30e6844591..89ee5ac195 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_index_1.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_index_1.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_index_2.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_index_2.cml index 6f9446582a..b3c7709dae 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_index_2.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_index_2.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_mix_0.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_mix_0.cml index 12def7cea4..ea5e42150e 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_mix_0.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_mix_0.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_mix_1.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_mix_1.cml index b20281c53e..b028ee6cf8 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_mix_1.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_mix_1.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_slice_0.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_slice_0.cml index 0d126109cf..76f66e1bc4 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_slice_0.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_slice_0.cml @@ -17,7 +17,7 @@ + 929298, 929304]" shape="(20,)" standard_name="time" units="Unit('hours since 1900-01-01 00:00:0.0', calendar='standard')" value_type="int32" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_slice_1.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_slice_1.cml index 8cfb4a0b5f..133cc4f659 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_slice_1.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_slice_1.cml @@ -15,7 +15,7 @@ + 929226, 929232, 929238, 929244]" shape="(10,)" standard_name="time" units="Unit('hours since 1900-01-01 00:00:0.0', calendar='standard')" value_type="int32" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_slice_2.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_slice_2.cml index 9259a07563..1d7025751e 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_slice_2.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_slice_2.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_0.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_0.cml index 6bc1a094e3..1f5a990bd4 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_0.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_0.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_1.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_1.cml index 0535339c7e..9c32197e56 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_1.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_1.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_2.cml b/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_2.cml index 6a0f9a90bf..100ab1257c 100644 --- a/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_2.cml +++ b/lib/iris/tests/results/netcdf/netcdf_deferred_tuple_2.cml @@ -14,7 +14,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_global_xyt_hires.cml b/lib/iris/tests/results/netcdf/netcdf_global_xyt_hires.cml index bda7f9ed9f..22a4ff1989 100644 --- a/lib/iris/tests/results/netcdf/netcdf_global_xyt_hires.cml +++ b/lib/iris/tests/results/netcdf/netcdf_global_xyt_hires.cml @@ -82,7 +82,7 @@ 71603.5, 71604.5, 71605.5, 71606.5, 71607.5, 71608.5, 71609.5, 71610.5, 71611.5, 71612.5, 71613.5, 71614.5, 71615.5, 71616.5, 71617.5, - 71618.5]" shape="(31,)" standard_name="time" units="Unit('days since 1850-01-01', calendar='gregorian')" value_type="float64" var_name="time"/> + 71618.5]" shape="(31,)" standard_name="time" units="Unit('days since 1850-01-01', calendar='standard')" value_type="float64" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_global_xyt_total.cml b/lib/iris/tests/results/netcdf/netcdf_global_xyt_total.cml index 1204fd0d39..fc6772e5f0 100644 --- a/lib/iris/tests/results/netcdf/netcdf_global_xyt_total.cml +++ b/lib/iris/tests/results/netcdf/netcdf_global_xyt_total.cml @@ -19,7 +19,7 @@ 929262, 929268, 929274, 929280, 929286, 929292, 929298, 929304, 929310, 929316, 929322, 929328, 929334, 929340, 929346, 929352, 929358, 929364, - 929370]" shape="(31,)" standard_name="time" units="Unit('hours since 1900-01-01 00:00:0.0', calendar='gregorian')" value_type="int32" var_name="time"/> + 929370]" shape="(31,)" standard_name="time" units="Unit('hours since 1900-01-01 00:00:0.0', calendar='standard')" value_type="int32" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems.cml b/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems.cml index ac41f4a8b8..9d6b3c1e43 100644 --- a/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems.cml +++ b/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems.cml @@ -20,7 +20,7 @@ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]" shape="(60,)" units="Unit('unknown')" value_type="int32" var_name="levelist"/> - + @@ -46,7 +46,7 @@ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]" shape="(60,)" units="Unit('unknown')" value_type="int32" var_name="levelist"/> - + diff --git a/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_0.cml b/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_0.cml index 4234b5cc84..15ab300757 100644 --- a/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_0.cml +++ b/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_0.cml @@ -20,7 +20,7 @@ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]" shape="(60,)" units="Unit('unknown')" value_type="int32" var_name="levelist"/> - + diff --git a/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_1.cml b/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_1.cml index 17d87a0190..29ff3b9bd9 100644 --- a/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_1.cml +++ b/lib/iris/tests/results/netcdf/netcdf_global_xyzt_gems_iter_1.cml @@ -20,7 +20,7 @@ 51, 52, 53, 54, 55, 56, 57, 58, 59, 60]" shape="(60,)" units="Unit('unknown')" value_type="int32" var_name="levelist"/> - + diff --git a/lib/iris/tests/results/netcdf/netcdf_laea.cml b/lib/iris/tests/results/netcdf/netcdf_laea.cml index ad23114038..799f40522b 100644 --- a/lib/iris/tests/results/netcdf/netcdf_laea.cml +++ b/lib/iris/tests/results/netcdf/netcdf_laea.cml @@ -11,7 +11,7 @@ - + @@ -63,7 +63,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_lcc.cml b/lib/iris/tests/results/netcdf/netcdf_lcc.cml index 7ea53e6600..592c33d534 100644 --- a/lib/iris/tests/results/netcdf/netcdf_lcc.cml +++ b/lib/iris/tests/results/netcdf/netcdf_lcc.cml @@ -88,7 +88,7 @@ [273.0, 303.0], [304.0, 333.0], [334.0, 364.0]]" id="1c4a69ce" long_name="time" points="[15.0, 44.5, 74.0, 104.5, 135.0, 165.5, 196.0, - 227.0, 257.5, 288.0, 318.5, 349.0]" shape="(12,)" standard_name="time" units="Unit('days since 2010-01-01 12:00:00', calendar='gregorian')" value_type="float64" var_name="time"/> + 227.0, 257.5, 288.0, 318.5, 349.0]" shape="(12,)" standard_name="time" units="Unit('days since 2010-01-01 12:00:00', calendar='standard')" value_type="float64" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_merc.cml b/lib/iris/tests/results/netcdf/netcdf_merc.cml index 831a8fdaa7..c06a2efe88 100644 --- a/lib/iris/tests/results/netcdf/netcdf_merc.cml +++ b/lib/iris/tests/results/netcdf/netcdf_merc.cml @@ -65,7 +65,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_monotonic.cml b/lib/iris/tests/results/netcdf/netcdf_monotonic.cml index 578b2b6d96..3385ecd6fe 100644 --- a/lib/iris/tests/results/netcdf/netcdf_monotonic.cml +++ b/lib/iris/tests/results/netcdf/netcdf_monotonic.cml @@ -12,7 +12,7 @@ - + @@ -30,7 +30,7 @@ - + @@ -48,7 +48,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_polar.cml b/lib/iris/tests/results/netcdf/netcdf_polar.cml index ef76a61699..15c1a90da9 100644 --- a/lib/iris/tests/results/netcdf/netcdf_polar.cml +++ b/lib/iris/tests/results/netcdf/netcdf_polar.cml @@ -36,7 +36,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_rotated_xyt_precipitation.cml b/lib/iris/tests/results/netcdf/netcdf_rotated_xyt_precipitation.cml index b236a3677d..05e5fe475d 100644 --- a/lib/iris/tests/results/netcdf/netcdf_rotated_xyt_precipitation.cml +++ b/lib/iris/tests/results/netcdf/netcdf_rotated_xyt_precipitation.cml @@ -54,7 +54,7 @@ + [2925.5, 2926.5]]" id="2306ff47" long_name="Julian Day" points="[2922.5, 2923.5, 2924.5, 2925.5]" shape="(4,)" standard_name="time" units="Unit('days since 1950-01-01 00:00:00.0', calendar='standard')" value_type="float32" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_save_hybrid_height.cdl b/lib/iris/tests/results/netcdf/netcdf_save_hybrid_height.cdl index 1863d1ee7d..74a83c9714 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_hybrid_height.cdl +++ b/lib/iris/tests/results/netcdf/netcdf_save_hybrid_height.cdl @@ -22,7 +22,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; int model_level_number(model_level_number) ; model_level_number:axis = "Z" ; model_level_number:units = "1" ; @@ -46,7 +46,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; float level_height(model_level_number) ; level_height:bounds = "level_height_bnds" ; level_height:units = "m" ; diff --git a/lib/iris/tests/results/netcdf/netcdf_save_load_hybrid_height.cml b/lib/iris/tests/results/netcdf/netcdf_save_load_hybrid_height.cml index 8e4a005d44..fbecdf97d3 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_load_hybrid_height.cml +++ b/lib/iris/tests/results/netcdf/netcdf_save_load_hybrid_height.cml @@ -418,7 +418,7 @@ 0.666666666686, 0.833333333314, 1.0]" shape="(6,)" standard_name="forecast_period" units="Unit('hours')" value_type="float64" var_name="forecast_period"/> - + + 347926.666667, 347926.833333, 347927.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_save_load_ndim_auxiliary.cml b/lib/iris/tests/results/netcdf/netcdf_save_load_ndim_auxiliary.cml index 13582b3106..54bcc8a686 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_load_ndim_auxiliary.cml +++ b/lib/iris/tests/results/netcdf/netcdf_save_load_ndim_auxiliary.cml @@ -54,7 +54,7 @@ + [2925.5, 2926.5]]" id="2306ff47" long_name="Julian Day" points="[2922.5, 2923.5, 2924.5, 2925.5]" shape="(4,)" standard_name="time" units="Unit('days since 1950-01-01 00:00:00.0', calendar='standard')" value_type="float32" var_name="time"/> diff --git a/lib/iris/tests/results/netcdf/netcdf_save_ndim_auxiliary.cdl b/lib/iris/tests/results/netcdf/netcdf_save_ndim_auxiliary.cdl index 32d4163d01..f8180d4ea8 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_ndim_auxiliary.cdl +++ b/lib/iris/tests/results/netcdf/netcdf_save_ndim_auxiliary.cdl @@ -22,7 +22,7 @@ variables: time:units = "days since 1950-01-01 00:00:00.0" ; time:standard_name = "time" ; time:long_name = "Julian Day" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; float time_bnds(time, bnds) ; float rlat(rlat) ; rlat:axis = "Y" ; diff --git a/lib/iris/tests/results/netcdf/netcdf_save_realistic_0d.cdl b/lib/iris/tests/results/netcdf/netcdf_save_realistic_0d.cdl index 0e3ae7e715..642e46a905 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_realistic_0d.cdl +++ b/lib/iris/tests/results/netcdf/netcdf_save_realistic_0d.cdl @@ -50,7 +50,7 @@ variables: double time ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; // global attributes: :source = "Iris test case" ; diff --git a/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d.cdl b/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d.cdl index 601ea11719..d49e775024 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d.cdl +++ b/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d.cdl @@ -21,7 +21,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; int model_level_number(model_level_number) ; model_level_number:axis = "Z" ; model_level_number:units = "1" ; diff --git a/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d_no_hybrid.cdl b/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d_no_hybrid.cdl index b86a77aa62..8353df60e9 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d_no_hybrid.cdl +++ b/lib/iris/tests/results/netcdf/netcdf_save_realistic_4d_no_hybrid.cdl @@ -21,7 +21,7 @@ variables: time:axis = "T" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; int model_level_number(model_level_number) ; model_level_number:axis = "Z" ; model_level_number:units = "1" ; diff --git a/lib/iris/tests/results/netcdf/netcdf_save_single.cdl b/lib/iris/tests/results/netcdf/netcdf_save_single.cdl index e45496521c..9847532001 100644 --- a/lib/iris/tests/results/netcdf/netcdf_save_single.cdl +++ b/lib/iris/tests/results/netcdf/netcdf_save_single.cdl @@ -30,7 +30,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; double height ; height:units = "m" ; height:standard_name = "height" ; @@ -39,7 +39,7 @@ variables: time:bounds = "time_bnds" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_bnds(bnds) ; // global attributes: diff --git a/lib/iris/tests/results/netcdf/netcdf_stereo.cml b/lib/iris/tests/results/netcdf/netcdf_stereo.cml index 092cf337b6..fae7ff027b 100644 --- a/lib/iris/tests/results/netcdf/netcdf_stereo.cml +++ b/lib/iris/tests/results/netcdf/netcdf_stereo.cml @@ -66,7 +66,7 @@ - + diff --git a/lib/iris/tests/results/netcdf/netcdf_tmerc_and_climatology.cml b/lib/iris/tests/results/netcdf/netcdf_tmerc_and_climatology.cml index 2d909ba57e..0575c684a9 100644 --- a/lib/iris/tests/results/netcdf/netcdf_tmerc_and_climatology.cml +++ b/lib/iris/tests/results/netcdf/netcdf_tmerc_and_climatology.cml @@ -62,7 +62,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/load_2flds.cml b/lib/iris/tests/results/nimrod/load_2flds.cml index b068657d40..41e92dd48b 100644 --- a/lib/iris/tests/results/nimrod/load_2flds.cml +++ b/lib/iris/tests/results/nimrod/load_2flds.cml @@ -14,7 +14,7 @@ - + - + diff --git a/lib/iris/tests/results/nimrod/period_of_interest.cml b/lib/iris/tests/results/nimrod/period_of_interest.cml index 258e5bcbbc..4c495b212a 100644 --- a/lib/iris/tests/results/nimrod/period_of_interest.cml +++ b/lib/iris/tests/results/nimrod/period_of_interest.cml @@ -3,7 +3,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/probability_fields.cml b/lib/iris/tests/results/nimrod/probability_fields.cml index 7add3e75a4..184d205132 100644 --- a/lib/iris/tests/results/nimrod/probability_fields.cml +++ b/lib/iris/tests/results/nimrod/probability_fields.cml @@ -17,7 +17,7 @@ - + + @@ -62,7 +62,7 @@ - + + @@ -111,7 +111,7 @@ - + @@ -131,7 +131,7 @@ - + @@ -158,7 +158,7 @@ - + @@ -186,7 +186,7 @@ - + @@ -210,7 +210,7 @@ - + @@ -237,7 +237,7 @@ - + @@ -271,7 +271,7 @@ - + @@ -291,7 +291,7 @@ - + @@ -315,7 +315,7 @@ - + @@ -331,7 +331,7 @@ - + @@ -358,7 +358,7 @@ - + @@ -378,7 +378,7 @@ - + @@ -402,7 +402,7 @@ - + @@ -422,7 +422,7 @@ - + @@ -448,7 +448,7 @@ - + @@ -461,7 +461,7 @@ - + @@ -484,7 +484,7 @@ - + @@ -497,7 +497,7 @@ - + @@ -521,7 +521,7 @@ - + @@ -537,7 +537,7 @@ - + @@ -560,7 +560,7 @@ - + @@ -573,7 +573,7 @@ - + @@ -600,7 +600,7 @@ - + @@ -620,7 +620,7 @@ - + @@ -644,7 +644,7 @@ - + @@ -664,7 +664,7 @@ - + @@ -688,7 +688,7 @@ - + @@ -701,7 +701,7 @@ - + @@ -726,7 +726,7 @@ - + @@ -742,7 +742,7 @@ - + @@ -766,7 +766,7 @@ - + @@ -779,7 +779,7 @@ - + @@ -807,7 +807,7 @@ - + @@ -828,7 +828,7 @@ - + @@ -853,7 +853,7 @@ - + @@ -873,7 +873,7 @@ - + @@ -901,7 +901,7 @@ - + @@ -922,7 +922,7 @@ - + @@ -949,7 +949,7 @@ - + @@ -969,7 +969,7 @@ - + @@ -992,7 +992,7 @@ - + @@ -1005,7 +1005,7 @@ - + @@ -1029,7 +1029,7 @@ - + @@ -1042,7 +1042,7 @@ - + @@ -1067,7 +1067,7 @@ - + @@ -1083,7 +1083,7 @@ - + @@ -1110,7 +1110,7 @@ - + @@ -1130,7 +1130,7 @@ - + @@ -1153,7 +1153,7 @@ - + @@ -1166,7 +1166,7 @@ - + @@ -1190,7 +1190,7 @@ - + @@ -1213,7 +1213,7 @@ - + @@ -1236,7 +1236,7 @@ - + @@ -1256,7 +1256,7 @@ - + @@ -1280,7 +1280,7 @@ - + @@ -1303,7 +1303,7 @@ - + @@ -1326,7 +1326,7 @@ - + @@ -1346,7 +1346,7 @@ - + @@ -1369,7 +1369,7 @@ - + @@ -1389,7 +1389,7 @@ - + @@ -1417,7 +1417,7 @@ - + @@ -1444,7 +1444,7 @@ - + @@ -1468,7 +1468,7 @@ - + @@ -1495,7 +1495,7 @@ - + @@ -1518,7 +1518,7 @@ - + @@ -1538,7 +1538,7 @@ - + @@ -1562,7 +1562,7 @@ - + @@ -1585,7 +1585,7 @@ - + @@ -1608,7 +1608,7 @@ - + @@ -1628,7 +1628,7 @@ - + @@ -1656,7 +1656,7 @@ - + @@ -1683,7 +1683,7 @@ - + @@ -1707,7 +1707,7 @@ - + @@ -1734,7 +1734,7 @@ - + @@ -1757,7 +1757,7 @@ - + @@ -1777,7 +1777,7 @@ - + @@ -1800,7 +1800,7 @@ - + @@ -1820,7 +1820,7 @@ - + @@ -1844,7 +1844,7 @@ - + @@ -1867,7 +1867,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_bmr04_precip_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_bmr04_precip_2km.cml index 31518dd321..a6ed9068ca 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_bmr04_precip_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_bmr04_precip_2km.cml @@ -19,7 +19,7 @@ [6300, 7200]]" id="b40ecfd3" points="[7200, 7200]" shape="(2,)" standard_name="forecast_period" units="Unit('second')" value_type="int32"/> - + @@ -36,7 +36,7 @@ + [1580193900, 1580194800]]" id="90a3bd1c" points="[1580194800, 1580194800]" shape="(2,)" standard_name="time" units="Unit('seconds since 1970-01-01 00:00:00', calendar='standard')" value_type="int64"/> diff --git a/lib/iris/tests/results/nimrod/u1096_ng_bsr05_precip_accum60_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_bsr05_precip_accum60_2km.cml index 80cb1834c0..cf3232d548 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_bsr05_precip_accum60_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_bsr05_precip_accum60_2km.cml @@ -18,7 +18,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud3d0060_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud3d0060_2km.cml index 68ec95555c..2aa1576fad 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud3d0060_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud3d0060_2km.cml @@ -17,7 +17,7 @@ - + - + @@ -73,7 +73,7 @@ - + - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud_2km.cml index c6bc6f0419..3dc62cc8e9 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_cloud_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -96,7 +96,7 @@ - + @@ -112,7 +112,7 @@ - + @@ -136,7 +136,7 @@ - + @@ -152,7 +152,7 @@ - + @@ -175,7 +175,7 @@ - + - + @@ -226,7 +226,7 @@ - + @@ -242,7 +242,7 @@ - + @@ -268,7 +268,7 @@ - + @@ -284,7 +284,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_convection_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_convection_2km.cml index e6c99f9e50..9be61d489c 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_convection_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_convection_2km.cml @@ -24,7 +24,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -79,7 +79,7 @@ - + @@ -102,7 +102,7 @@ - + @@ -118,7 +118,7 @@ - + @@ -141,7 +141,7 @@ - + @@ -157,7 +157,7 @@ - + @@ -185,7 +185,7 @@ - + @@ -208,7 +208,7 @@ - + @@ -231,7 +231,7 @@ - + @@ -247,7 +247,7 @@ - + @@ -270,7 +270,7 @@ - + @@ -286,7 +286,7 @@ - + @@ -309,7 +309,7 @@ - + @@ -328,7 +328,7 @@ - + @@ -351,7 +351,7 @@ - + @@ -370,7 +370,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_convwind_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_convwind_2km.cml index 2f52a93277..734beb7f47 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_convwind_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_convwind_2km.cml @@ -24,7 +24,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -116,7 +116,7 @@ - + @@ -132,7 +132,7 @@ - + @@ -162,7 +162,7 @@ - + @@ -178,7 +178,7 @@ - + @@ -208,7 +208,7 @@ - + @@ -224,7 +224,7 @@ - + @@ -247,7 +247,7 @@ - + @@ -270,7 +270,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_frzlev_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_frzlev_2km.cml index b2b47715a2..56bfecc1b4 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_frzlev_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_frzlev_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -111,7 +111,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -151,7 +151,7 @@ - + @@ -175,7 +175,7 @@ - + @@ -191,7 +191,7 @@ - + @@ -214,7 +214,7 @@ - + @@ -230,7 +230,7 @@ - + @@ -254,7 +254,7 @@ - + @@ -270,7 +270,7 @@ - + @@ -294,7 +294,7 @@ - + @@ -310,7 +310,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_height_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_height_2km.cml index 4fb1371250..2eb83d787b 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_height_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_height_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_precip_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_precip_2km.cml index 59776b5b74..4f4c986a39 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_precip_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_precip_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -57,7 +57,7 @@ - + @@ -73,7 +73,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -113,7 +113,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_precipaccum_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_precipaccum_2km.cml index 0fa98e3bb6..dd6102ea7f 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_precipaccum_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_precipaccum_2km.cml @@ -19,7 +19,7 @@ [6300, 7200]]" id="b40ecfd3" points="[7200, 7200]" shape="(2,)" standard_name="forecast_period" units="Unit('second')" value_type="int32"/> - + @@ -36,7 +36,7 @@ + [1580186700, 1580187600]]" id="90a3bd1c" points="[1580187600, 1580187600]" shape="(2,)" standard_name="time" units="Unit('seconds since 1970-01-01 00:00:00', calendar='standard')" value_type="int64"/> diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_preciptype_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_preciptype_2km.cml index 3fdf646e70..be1e89a53d 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_preciptype_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_preciptype_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -57,7 +57,7 @@ - + @@ -73,7 +73,7 @@ - + @@ -97,7 +97,7 @@ - + @@ -113,7 +113,7 @@ - + @@ -136,7 +136,7 @@ - + @@ -152,7 +152,7 @@ - + @@ -176,7 +176,7 @@ - + @@ -192,7 +192,7 @@ - + @@ -216,7 +216,7 @@ - + @@ -232,7 +232,7 @@ - + @@ -255,7 +255,7 @@ - + @@ -271,7 +271,7 @@ - + @@ -295,7 +295,7 @@ - + @@ -311,7 +311,7 @@ - + @@ -335,7 +335,7 @@ - + @@ -351,7 +351,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_pressure_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_pressure_2km.cml index edb0862676..9a3ff88df8 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_pressure_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_pressure_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiation_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiation_2km.cml index 38f076f232..00bc65f236 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiation_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiation_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -111,7 +111,7 @@ - + @@ -134,7 +134,7 @@ - + @@ -150,7 +150,7 @@ - + @@ -173,7 +173,7 @@ - + @@ -189,7 +189,7 @@ - + @@ -212,7 +212,7 @@ - + @@ -228,7 +228,7 @@ - + @@ -251,7 +251,7 @@ - + @@ -267,7 +267,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiationuv_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiationuv_2km.cml index 35bed38591..b2cf624214 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiationuv_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_radiationuv_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -111,7 +111,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_refl_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_refl_2km.cml index 4411ff9dd5..aaed20394f 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_refl_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_refl_2km.cml @@ -26,7 +26,7 @@ - + @@ -42,7 +42,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity3d0060_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity3d0060_2km.cml index 8759dac5c7..3a25dc86fc 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity3d0060_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity3d0060_2km.cml @@ -17,7 +17,7 @@ - + - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity_2km.cml index 9b7e7582d0..fa4ab30a58 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_relhumidity_2km.cml @@ -17,7 +17,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_snow_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_snow_2km.cml index ce549ab3cd..918a0c7ae5 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_snow_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_snow_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -57,7 +57,7 @@ - + @@ -73,7 +73,7 @@ - + @@ -96,7 +96,7 @@ - + @@ -112,7 +112,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil3d0060_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil3d0060_2km.cml index 9385bfc9ae..3a6c3bf53c 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil3d0060_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil3d0060_2km.cml @@ -24,7 +24,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -116,7 +116,7 @@ - + @@ -132,7 +132,7 @@ - + @@ -162,7 +162,7 @@ - + @@ -178,7 +178,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil_2km.cml index a76971a1ed..eab889a8af 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_soil_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -77,7 +77,7 @@ urban_roof, water]" shape="(10,)" standard_name="soil_type" units="Unit('unknown')" value_type="string"/> - + @@ -100,7 +100,7 @@ - + @@ -121,7 +121,7 @@ urban_roof, water]" shape="(10,)" standard_name="soil_type" units="Unit('unknown')" value_type="string"/> - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_temperature_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_temperature_2km.cml index 09677ff57a..6ff6359046 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_temperature_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_temperature_2km.cml @@ -18,7 +18,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -65,7 +65,7 @@ - + @@ -88,7 +88,7 @@ - + @@ -111,7 +111,7 @@ - + @@ -134,7 +134,7 @@ - + @@ -157,7 +157,7 @@ - + @@ -180,7 +180,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_visibility_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_visibility_2km.cml index 8a0f50700c..037cb5c2b6 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_visibility_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_visibility_2km.cml @@ -17,7 +17,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -109,7 +109,7 @@ - + @@ -132,7 +132,7 @@ - + @@ -155,7 +155,7 @@ - + @@ -178,7 +178,7 @@ - + @@ -201,7 +201,7 @@ - + @@ -224,7 +224,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_wind_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_wind_2km.cml index df2054e8af..5ca9920172 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_wind_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_wind_2km.cml @@ -18,7 +18,7 @@ - + @@ -41,7 +41,7 @@ - + @@ -64,7 +64,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -110,7 +110,7 @@ - + @@ -133,7 +133,7 @@ - + @@ -156,7 +156,7 @@ - + @@ -179,7 +179,7 @@ - + @@ -202,7 +202,7 @@ - + @@ -225,7 +225,7 @@ - + @@ -249,7 +249,7 @@ - + @@ -272,7 +272,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv3d0015_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv3d0015_2km.cml index 331ff59c74..91c40ea6d0 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv3d0015_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv3d0015_2km.cml @@ -17,7 +17,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv_2km.cml index aa14346e2f..3252dbf047 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek00_winduv_2km.cml @@ -17,7 +17,7 @@ - + @@ -40,7 +40,7 @@ - + @@ -63,7 +63,7 @@ - + @@ -86,7 +86,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek01_cape_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek01_cape_2km.cml index 1756ac0205..d39fa0e367 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek01_cape_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek01_cape_2km.cml @@ -17,7 +17,7 @@ - + @@ -33,7 +33,7 @@ - + @@ -56,7 +56,7 @@ - + @@ -72,7 +72,7 @@ - + @@ -98,7 +98,7 @@ - + @@ -114,7 +114,7 @@ - + @@ -137,7 +137,7 @@ - + @@ -153,7 +153,7 @@ - + @@ -176,7 +176,7 @@ - + @@ -192,7 +192,7 @@ - + @@ -215,7 +215,7 @@ - + @@ -231,7 +231,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_ek07_precip0540_accum180_18km.cml b/lib/iris/tests/results/nimrod/u1096_ng_ek07_precip0540_accum180_18km.cml index f4710dd36d..4a5783ecb3 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_ek07_precip0540_accum180_18km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_ek07_precip0540_accum180_18km.cml @@ -18,7 +18,7 @@ - + @@ -34,7 +34,7 @@ - + diff --git a/lib/iris/tests/results/nimrod/u1096_ng_umqv_fog_2km.cml b/lib/iris/tests/results/nimrod/u1096_ng_umqv_fog_2km.cml index 57756ccc1d..d2c7e72848 100644 --- a/lib/iris/tests/results/nimrod/u1096_ng_umqv_fog_2km.cml +++ b/lib/iris/tests/results/nimrod/u1096_ng_umqv_fog_2km.cml @@ -17,7 +17,7 @@ - + @@ -37,7 +37,7 @@ - + - + diff --git a/lib/iris/tests/results/pandas/as_cube/series_datetime_gregorian.cml b/lib/iris/tests/results/pandas/as_cube/series_datetime_standard.cml similarity index 86% rename from lib/iris/tests/results/pandas/as_cube/series_datetime_gregorian.cml rename to lib/iris/tests/results/pandas/as_cube/series_datetime_standard.cml index 7e2e6f4166..5cb621d5f3 100644 --- a/lib/iris/tests/results/pandas/as_cube/series_datetime_gregorian.cml +++ b/lib/iris/tests/results/pandas/as_cube/series_datetime_standard.cml @@ -4,7 +4,7 @@ + 300292.067778, 309797.084722]" shape="(5,)" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/pp_load_rules/global.cml b/lib/iris/tests/results/pp_load_rules/global.cml index 2df84a8606..a69e633e26 100644 --- a/lib/iris/tests/results/pp_load_rules/global.cml +++ b/lib/iris/tests/results/pp_load_rules/global.cml @@ -10,7 +10,7 @@ - + - + diff --git a/lib/iris/tests/results/pp_load_rules/lbproc_mean_max_min.cml b/lib/iris/tests/results/pp_load_rules/lbproc_mean_max_min.cml index 9e4b6d31f5..ecf51190c7 100644 --- a/lib/iris/tests/results/pp_load_rules/lbproc_mean_max_min.cml +++ b/lib/iris/tests/results/pp_load_rules/lbproc_mean_max_min.cml @@ -11,7 +11,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -48,7 +48,7 @@ - + @@ -68,7 +68,7 @@ - + @@ -89,7 +89,7 @@ - + @@ -109,7 +109,7 @@ - + @@ -130,7 +130,7 @@ - + @@ -150,7 +150,7 @@ - + @@ -171,7 +171,7 @@ - + @@ -190,7 +190,7 @@ 850.0, 925.0, 950.0, 1000.0]" shape="(28,)" units="Unit('hPa')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/pp_load_rules/rotated_uk.cml b/lib/iris/tests/results/pp_load_rules/rotated_uk.cml index 51b4682ebf..ece399df4e 100644 --- a/lib/iris/tests/results/pp_load_rules/rotated_uk.cml +++ b/lib/iris/tests/results/pp_load_rules/rotated_uk.cml @@ -11,7 +11,7 @@ - + @@ -35,7 +35,7 @@ - + diff --git a/lib/iris/tests/results/stock/realistic_4d.cml b/lib/iris/tests/results/stock/realistic_4d.cml index 88adbc43de..6640c54360 100644 --- a/lib/iris/tests/results/stock/realistic_4d.cml +++ b/lib/iris/tests/results/stock/realistic_4d.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/system/supported_filetype_.grib2.cml b/lib/iris/tests/results/system/supported_filetype_.grib2.cml index f334b13863..5376af2fe1 100644 --- a/lib/iris/tests/results/system/supported_filetype_.grib2.cml +++ b/lib/iris/tests/results/system/supported_filetype_.grib2.cml @@ -9,7 +9,7 @@ - + - + diff --git a/lib/iris/tests/results/system/supported_filetype_.nc.cml b/lib/iris/tests/results/system/supported_filetype_.nc.cml index 595cd287ae..6ad0a3b176 100644 --- a/lib/iris/tests/results/system/supported_filetype_.nc.cml +++ b/lib/iris/tests/results/system/supported_filetype_.nc.cml @@ -36,7 +36,7 @@ - + diff --git a/lib/iris/tests/results/system/supported_filetype_.pp.cml b/lib/iris/tests/results/system/supported_filetype_.pp.cml index 838b9fad50..e457b2921e 100644 --- a/lib/iris/tests/results/system/supported_filetype_.pp.cml +++ b/lib/iris/tests/results/system/supported_filetype_.pp.cml @@ -6,7 +6,7 @@ - + - + diff --git a/lib/iris/tests/results/trajectory/constant_latitude.cml b/lib/iris/tests/results/trajectory/constant_latitude.cml index f07f1a131f..38c208b825 100644 --- a/lib/iris/tests/results/trajectory/constant_latitude.cml +++ b/lib/iris/tests/results/trajectory/constant_latitude.cml @@ -12,7 +12,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="forecast_reference_time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/trajectory/hybrid_height.cml b/lib/iris/tests/results/trajectory/hybrid_height.cml index 63de9366dc..972fa7b330 100644 --- a/lib/iris/tests/results/trajectory/hybrid_height.cml +++ b/lib/iris/tests/results/trajectory/hybrid_height.cml @@ -54,7 +54,7 @@ [124, 125, 126, 127, 128, 129]]" shape="(5, 6)" units="Unit('m')" value_type="int64"/> - + @@ -91,7 +91,7 @@ - + diff --git a/lib/iris/tests/results/trajectory/single_point.cml b/lib/iris/tests/results/trajectory/single_point.cml index a1cf83a03b..64c71e0394 100644 --- a/lib/iris/tests/results/trajectory/single_point.cml +++ b/lib/iris/tests/results/trajectory/single_point.cml @@ -12,7 +12,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="forecast_reference_time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> @@ -88,7 +88,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/trajectory/zigzag.cml b/lib/iris/tests/results/trajectory/zigzag.cml index 3bdb3e9013..8a578c4ab4 100644 --- a/lib/iris/tests/results/trajectory/zigzag.cml +++ b/lib/iris/tests/results/trajectory/zigzag.cml @@ -11,7 +11,7 @@ - + - + diff --git a/lib/iris/tests/results/unit/analysis/cartography/project/TestAll/cube.cml b/lib/iris/tests/results/unit/analysis/cartography/project/TestAll/cube.cml index e2a1ef2ea6..2592307cda 100644 --- a/lib/iris/tests/results/unit/analysis/cartography/project/TestAll/cube.cml +++ b/lib/iris/tests/results/unit/analysis/cartography/project/TestAll/cube.cml @@ -79,7 +79,7 @@ [0.996162, 0.993097]]" id="a5c170db" long_name="sigma" points="[0.999424, 0.997504, 0.99482]" shape="(3,)" units="Unit('1')" value_type="float32"/> - + diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml index 9a522e5167..1e74c9bc9c 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_all_dims.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml index 9a522e5167..1e74c9bc9c 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_last_dims.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml index 9a522e5167..1e74c9bc9c 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_middle_dim.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml index 9a522e5167..1e74c9bc9c 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/collapse_zeroth_dim.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml index 9a522e5167..1e74c9bc9c 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/slice.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml index 9a522e5167..1e74c9bc9c 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords/TestBroadcastingDerived/transposed.cml @@ -498,7 +498,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml index 2db0cc598f..9fc80a0e4d 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml @@ -113,7 +113,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml index 2db0cc598f..9fc80a0e4d 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_last_dims.cml @@ -113,7 +113,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_middle_dim.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_zeroth_dim.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/slice.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/transposed.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml index 2db0cc598f..9fc80a0e4d 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_all_dims.cml @@ -113,7 +113,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml index 2db0cc598f..9fc80a0e4d 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_last_dims.cml @@ -113,7 +113,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_middle_dim.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/collapse_zeroth_dim.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/slice.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml index 359656f25a..82b7e25aa3 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMeshAndDerived/transposed.cml @@ -102,7 +102,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_all_dims.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_all_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_last_dims.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_last_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_last_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_middle_dim.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_middle_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_middle_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_zeroth_dim.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_zeroth_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/collapse_zeroth_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/slice.cml b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/slice.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/slice.cml +++ b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/slice.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/transposed.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/transposed.cml +++ b/lib/iris/tests/results/unit/analysis/maths/add/TestBroadcasting/transposed.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_all_dims.cml index d4a90d37ac..86d7855b1b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_all_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_last_dims.cml index d4a90d37ac..86d7855b1b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_last_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_last_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_middle_dim.cml index d4a90d37ac..86d7855b1b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_middle_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_middle_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_zeroth_dim.cml index d4a90d37ac..86d7855b1b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_zeroth_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/collapse_zeroth_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/slice.cml b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/slice.cml index d4a90d37ac..86d7855b1b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/slice.cml +++ b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/slice.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/transposed.cml index d4a90d37ac..86d7855b1b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/transposed.cml +++ b/lib/iris/tests/results/unit/analysis/maths/divide/TestBroadcasting/transposed.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_all_dims.cml index 7ae36e51c3..73d6073a4b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_all_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_last_dims.cml index 7ae36e51c3..73d6073a4b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_last_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_last_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_middle_dim.cml index 7ae36e51c3..73d6073a4b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_middle_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_middle_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_zeroth_dim.cml index 7ae36e51c3..73d6073a4b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_zeroth_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/collapse_zeroth_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/slice.cml b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/slice.cml index 7ae36e51c3..73d6073a4b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/slice.cml +++ b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/slice.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/transposed.cml index 7ae36e51c3..73d6073a4b 100644 --- a/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/transposed.cml +++ b/lib/iris/tests/results/unit/analysis/maths/multiply/TestBroadcasting/transposed.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_all_dims.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_all_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_last_dims.cml b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_last_dims.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_last_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_last_dims.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_middle_dim.cml b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_middle_dim.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_middle_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_middle_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_zeroth_dim.cml b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_zeroth_dim.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_zeroth_dim.cml +++ b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/collapse_zeroth_dim.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/slice.cml b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/slice.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/slice.cml +++ b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/slice.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/transposed.cml b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/transposed.cml index bea6795b38..8467544d44 100644 --- a/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/transposed.cml +++ b/lib/iris/tests/results/unit/analysis/maths/subtract/TestBroadcasting/transposed.cml @@ -110,7 +110,7 @@ + 347921.666667, 347921.833333, 347922.0]" shape="(6,)" standard_name="time" units="Unit('hours since 1970-01-01 00:00:00', calendar='standard')" value_type="float64"/> diff --git a/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/with_climatology.cdl b/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/with_climatology.cdl index 3c1033c17e..2159123553 100644 --- a/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/with_climatology.cdl +++ b/lib/iris/tests/results/unit/fileformats/netcdf/Saver/write/with_climatology.cdl @@ -13,7 +13,7 @@ variables: time:climatology = "time_climatology" ; time:units = "days since 1970-01-01 00:00:00-00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_climatology(time, bnds) ; double latitude(latitude) ; latitude:axis = "Y" ; diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cml b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cml index 1f9dfb0a14..e7c799f397 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cml +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cml @@ -11,7 +11,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cml b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cml index 06c192f8a4..66cbc7206b 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cml +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cml @@ -11,7 +11,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cml b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cml index 9b654f6c6e..af298945f0 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cml +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cml @@ -12,7 +12,7 @@ - + @@ -41,7 +41,7 @@ - + diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.000128.1990.12.01.00.00.b.cml b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.000128.1990.12.01.00.00.b.cml index d5d05f15fd..44999e85b7 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.000128.1990.12.01.00.00.b.cml +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.000128.1990.12.01.00.00.b.cml @@ -10,7 +10,7 @@ - + @@ -39,7 +39,7 @@ - + diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.004224.1990.12.01.00.00.b.cml b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.004224.1990.12.01.00.00.b.cml index 1f4d8a4b2c..990fa0d7fe 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.004224.1990.12.01.00.00.b.cml +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.004224.1990.12.01.00.00.b.cml @@ -10,7 +10,7 @@ - + @@ -39,7 +39,7 @@ - + diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.008320.1990.12.01.00.00.b.cml b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.008320.1990.12.01.00.00.b.cml index 359cba997f..43789498c1 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.008320.1990.12.01.00.00.b.cml +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/from_pp/000003000000.03.236.008320.1990.12.01.00.00.b.cml @@ -11,7 +11,7 @@ - + @@ -40,7 +40,7 @@ - + diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cdl b/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cdl index 429da0807b..ddbbee5d34 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cdl +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.000128.1990.12.01.00.00.b_0.cdl @@ -31,7 +31,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; double height ; height:units = "m" ; height:standard_name = "height" ; @@ -40,7 +40,7 @@ variables: time:bounds = "time_bnds" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_bnds(bnds) ; // global attributes: diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cdl b/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cdl index 429da0807b..ddbbee5d34 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cdl +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.004224.1990.12.01.00.00.b_0.cdl @@ -31,7 +31,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; double height ; height:units = "m" ; height:standard_name = "height" ; @@ -40,7 +40,7 @@ variables: time:bounds = "time_bnds" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_bnds(bnds) ; // global attributes: diff --git a/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cdl b/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cdl index f1c94dc834..cb026fd7ae 100644 --- a/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cdl +++ b/lib/iris/tests/results/usecases/pp_to_cf_conversion/to_netcdf/000003000000.03.236.008320.1990.12.01.00.00.b_0.cdl @@ -31,7 +31,7 @@ variables: double forecast_reference_time ; forecast_reference_time:units = "hours since 1970-01-01 00:00:00" ; forecast_reference_time:standard_name = "forecast_reference_time" ; - forecast_reference_time:calendar = "gregorian" ; + forecast_reference_time:calendar = "standard" ; double height ; height:units = "m" ; height:standard_name = "height" ; @@ -40,7 +40,7 @@ variables: time:bounds = "time_bnds" ; time:units = "hours since 1970-01-01 00:00:00" ; time:standard_name = "time" ; - time:calendar = "gregorian" ; + time:calendar = "standard" ; double time_bnds(bnds) ; // global attributes: diff --git a/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl b/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl index b135546f2d..1e5522854e 100644 --- a/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_2D_face_half_levels.cdl @@ -39,7 +39,7 @@ variables: double time_instant(time_counter) ; time_instant:standard_name = "time" ; time_instant:long_name = "Time axis" ; - time_instant:calendar = "gregorian" ; + time_instant:calendar = "standard" ; time_instant:units = "seconds since 2016-01-01 15:00:00" ; time_instant:time_origin = "2016-01-01 15:00:00" ; time_instant:bounds = "time_instant_bounds" ; diff --git a/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl b/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl index e4f32de7b7..9159bf6e46 100644 --- a/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_3D_face_full_levels.cdl @@ -42,7 +42,7 @@ variables: double time_instant(time_counter) ; time_instant:standard_name = "time" ; time_instant:long_name = "Time axis" ; - time_instant:calendar = "gregorian" ; + time_instant:calendar = "standard" ; time_instant:units = "seconds since 2016-01-01 15:00:00" ; time_instant:time_origin = "2016-01-01 15:00:00" ; time_instant:bounds = "time_instant_bounds" ; diff --git a/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl b/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl index a193dbe451..f79ae0bdaf 100644 --- a/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl +++ b/lib/iris/tests/stock/file_headers/xios_3D_face_half_levels.cdl @@ -42,7 +42,7 @@ variables: double time_instant(time_counter) ; time_instant:standard_name = "time" ; time_instant:long_name = "Time axis" ; - time_instant:calendar = "gregorian" ; + time_instant:calendar = "standard" ; time_instant:units = "seconds since 2016-01-01 15:00:00" ; time_instant:time_origin = "2016-01-01 15:00:00" ; time_instant:bounds = "time_instant_bounds" ; diff --git a/lib/iris/tests/system_test.py b/lib/iris/tests/system_test.py index 00cb541e1c..745163b485 100644 --- a/lib/iris/tests/system_test.py +++ b/lib/iris/tests/system_test.py @@ -51,7 +51,7 @@ def horiz_cs(): ) ) hours_since_epoch = cf_units.Unit( - "hours since epoch", cf_units.CALENDAR_GREGORIAN + "hours since epoch", cf_units.CALENDAR_STANDARD ) cm.add_aux_coord( iris.coords.AuxCoord( diff --git a/lib/iris/tests/test_coord_categorisation.py b/lib/iris/tests/test_coord_categorisation.py index 616da882f5..0206ba66a5 100644 --- a/lib/iris/tests/test_coord_categorisation.py +++ b/lib/iris/tests/test_coord_categorisation.py @@ -52,7 +52,7 @@ def setUp(self): time_coord = iris.coords.DimCoord( day_numbers, standard_name="time", - units=cf_units.Unit("days since epoch", "gregorian"), + units=cf_units.Unit("days since epoch", "standard"), ) cube.add_dim_coord(time_coord, 0) diff --git a/lib/iris/tests/test_pandas.py b/lib/iris/tests/test_pandas.py index af62ad23d3..208f7b944e 100644 --- a/lib/iris/tests/test_pandas.py +++ b/lib/iris/tests/test_pandas.py @@ -63,7 +63,7 @@ def test_masked(self): series = iris.pandas.as_series(cube) self.assertArrayEqual(series, cube.data.astype("f").filled(np.nan)) - def test_time_gregorian(self): + def test_time_standard(self): cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") time_coord = DimCoord( [0, 100.1, 200.2, 300.3, 400.4], @@ -210,7 +210,7 @@ def test_masked(self): self.assertArrayEqual(data_frame.index, expected_index) self.assertArrayEqual(data_frame.columns, expected_columns) - def test_time_gregorian(self): + def test_time_standard(self): cube = Cube( np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="ts" ) @@ -348,7 +348,7 @@ def test_series_masked(self): tests.get_result_path(("pandas", "as_cube", "series_masked.cml")), ) - def test_series_datetime_gregorian(self): + def test_series_datetime_standard(self): series = pandas.Series( [0, 1, 2, 3, 4], index=[ @@ -362,7 +362,7 @@ def test_series_datetime_gregorian(self): self.assertCML( iris.pandas.as_cube(series), tests.get_result_path( - ("pandas", "as_cube", "series_datetime_gregorian.cml") + ("pandas", "as_cube", "series_datetime_standard.cml") ), ) @@ -471,7 +471,7 @@ def test_data_frame_cftime_360(self): ), ) - def test_data_frame_datetime_gregorian(self): + def test_data_frame_datetime_standard(self): data_frame = pandas.DataFrame( [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], index=[ @@ -483,7 +483,7 @@ def test_data_frame_datetime_gregorian(self): self.assertCML( iris.pandas.as_cube(data_frame), tests.get_result_path( - ("pandas", "as_cube", "data_frame_datetime_gregorian.cml") + ("pandas", "as_cube", "data_frame_datetime_standard.cml") ), ) diff --git a/lib/iris/tests/unit/concatenate/test__CubeSignature.py b/lib/iris/tests/unit/concatenate/test__CubeSignature.py index b3870a7901..cc20cdfa1f 100644 --- a/lib/iris/tests/unit/concatenate/test__CubeSignature.py +++ b/lib/iris/tests/unit/concatenate/test__CubeSignature.py @@ -24,7 +24,7 @@ def setUp(self): data = np.arange(nt, dtype=np.float32) cube = Cube(data, standard_name="air_temperature", units="K") # Temporal coordinate. - t_units = Unit("hours since 1970-01-01 00:00:00", calendar="gregorian") + t_units = Unit("hours since 1970-01-01 00:00:00", calendar="standard") t_coord = DimCoord( points=np.arange(nt), standard_name="time", units=t_units ) diff --git a/lib/iris/tests/unit/concatenate/test_concatenate.py b/lib/iris/tests/unit/concatenate/test_concatenate.py index 2af568f077..96d13d7d15 100644 --- a/lib/iris/tests/unit/concatenate/test_concatenate.py +++ b/lib/iris/tests/unit/concatenate/test_concatenate.py @@ -30,7 +30,7 @@ def simple_1d_time_cubes(self, reftimes, coords_points): standard_name="air_temperature", units="K", ) - unit = cf_units.Unit(reftime, calendar="gregorian") + unit = cf_units.Unit(reftime, calendar="standard") coord = iris.coords.DimCoord( points=np.array(coord_points, dtype=np.float32), standard_name="time", @@ -58,7 +58,7 @@ def setUp(self): cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") # Time coord t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="gregorian" + "hours since 1970-01-01 00:00:00", calendar="standard" ) t_coord = iris.coords.DimCoord( points=np.arange(2, dtype=np.float32), diff --git a/lib/iris/tests/unit/coord_categorisation/test_add_hour.py b/lib/iris/tests/unit/coord_categorisation/test_add_hour.py index 86230c84b9..418ac72557 100644 --- a/lib/iris/tests/unit/coord_categorisation/test_add_hour.py +++ b/lib/iris/tests/unit/coord_categorisation/test_add_hour.py @@ -32,7 +32,7 @@ def setUp(self): time_coord = iris.coords.DimCoord( hour_numbers, standard_name="time", - units=cf_units.Unit("hours since epoch", "gregorian"), + units=cf_units.Unit("hours since epoch", "standard"), ) cube.add_dim_coord(time_coord, 0) diff --git a/lib/iris/tests/unit/coords/test_AncillaryVariable.py b/lib/iris/tests/unit/coords/test_AncillaryVariable.py index 4d520ac414..75b6250449 100644 --- a/lib/iris/tests/unit/coords/test_AncillaryVariable.py +++ b/lib/iris/tests/unit/coords/test_AncillaryVariable.py @@ -458,7 +458,7 @@ def test_time_values(self): [ ( "AncillaryVariable : time of previous valid detection / " - "(hours since 1970-01-01 01:00, gregorian calendar)" + "(hours since 1970-01-01 01:00, standard calendar)" ), ( " data: [1970-01-01 03:00:00, 1970-01-01 06:00:00, " diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index f8f1ff1d69..08ed8d55e5 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -890,7 +890,7 @@ def test_short_time_interval(self): ) expected = "\n".join( [ - "DimCoord : time / (days since 1970-01-01, gregorian calendar)", + "DimCoord : time / (days since 1970-01-01, standard calendar)", " points: [1970-01-06 00:00:00]", " shape: (1,)", " dtype: int64", @@ -907,7 +907,7 @@ def test_short_time_interval__bounded(self): coord.guess_bounds() expected = "\n".join( [ - "DimCoord : time / (days since 1970-01-01, gregorian calendar)", + "DimCoord : time / (days since 1970-01-01, standard calendar)", " points: [1970-01-06 00:00:00, 1970-01-07 00:00:00]", " bounds: [", " [1970-01-05 12:00:00, 1970-01-06 12:00:00],", @@ -926,7 +926,7 @@ def test_long_time_interval(self): ) expected = "\n".join( [ - "DimCoord : time / (years since 1970-01-01, gregorian calendar)", + "DimCoord : time / (years since 1970-01-01, standard calendar)", " points: [5]", " shape: (1,)", " dtype: int64", @@ -943,7 +943,7 @@ def test_long_time_interval__bounded(self): coord.guess_bounds() expected = "\n".join( [ - "DimCoord : time / (years since 1970-01-01, gregorian calendar)", + "DimCoord : time / (years since 1970-01-01, standard calendar)", " points: [5, 6]", " bounds: [", " [4.5, 5.5],", diff --git a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py index fd10a6f264..f9316ff92c 100644 --- a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py +++ b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py @@ -511,7 +511,7 @@ def test_onepoint_toolong_placeholder(self): result = self.coord_representations(shape=(2,), dates=True) expected = [ "", - "AuxCoord : x / (days since 1970-03-5, gregorian calendar)", + "AuxCoord : x / (days since 1970-03-5, standard calendar)", " points: [1970-03-05 00:00:00, 1970-03-06 00:00:00]", " shape: (2,)", " dtype: float64", @@ -540,7 +540,7 @@ def test_dates_scalar(self): ), ( "AuxCoord : time / (hours since 2025-03-23 01:00:00, " - "gregorian calendar)" + "standard calendar)" ), " points: [2025-03-23 01:00:00]", " shape: (1,)", @@ -553,7 +553,7 @@ def test_dates_bounds(self): result = self.coord_representations(dates=True, bounded=True) expected = [ "", - "AuxCoord : x / (days since 1970-03-5, gregorian calendar)", + "AuxCoord : x / (days since 1970-03-5, standard calendar)", " points: [", " 1970-03-05 00:00:00, 1970-03-06 00:00:00,", " 1970-03-07 00:00:00, 1970-03-08 00:00:00,", @@ -574,7 +574,7 @@ def test_dates_masked(self): result = self.coord_representations(dates=True, masked=True) expected = [ "", - "AuxCoord : x / (days since 1970-03-5, gregorian calendar)", + "AuxCoord : x / (days since 1970-03-5, standard calendar)", " points: [", " 1970-03-05 00:00:00, -- ,", " 1970-03-07 00:00:00, 1970-03-08 00:00:00,", @@ -749,7 +749,7 @@ def test_climatological(self): ), ( "DimCoord : time / (days since 1970-01-01 00:00:00-00, " - "gregorian calendar)" + "standard calendar)" ), " points: [2001-01-10 00:00:00]", " bounds: [[2001-01-10 00:00:00, 2011-01-10 00:00:00]]", @@ -1054,7 +1054,7 @@ def test_convert_dates(self): coord = self.sample_coord(dates=True) result = coord.summary() expected = [ - "AuxCoord : x / (days since 1970-03-5, gregorian calendar)", + "AuxCoord : x / (days since 1970-03-5, standard calendar)", " points: [", ( " 1970-03-05 00:00:00, 1970-03-06 00:00:00, " @@ -1069,7 +1069,7 @@ def test_convert_dates(self): result = coord.summary(convert_dates=False) expected = [ - "AuxCoord : x / (days since 1970-03-5, gregorian calendar)", + "AuxCoord : x / (days since 1970-03-5, standard calendar)", " points: [0., 1., 2., 3., 4.]", " shape: (5,)", " dtype: float64", diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py index 771b214fe4..1ebfe57773 100644 --- a/lib/iris/tests/unit/cube/test_CubeList.py +++ b/lib/iris/tests/unit/cube/test_CubeList.py @@ -48,7 +48,7 @@ def test_fail(self): class Test_concatenate_cube(tests.IrisTest): def setUp(self): self.units = Unit( - "days since 1970-01-01 00:00:00", calendar="gregorian" + "days since 1970-01-01 00:00:00", calendar="standard" ) self.cube1 = Cube([1, 2, 3], "air_temperature", units="K") self.cube1.add_dim_coord( @@ -64,7 +64,7 @@ def test_pass(self): self.assertIsInstance(result, Cube) def test_fail(self): - units = Unit("days since 1970-01-02 00:00:00", calendar="gregorian") + units = Unit("days since 1970-01-02 00:00:00", calendar="standard") cube2 = Cube([1, 2, 3], "air_temperature", units="K") cube2.add_dim_coord(DimCoord([0, 1, 2], "time", units=units), 0) with self.assertRaises(iris.exceptions.ConcatenateError): diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPField.py b/lib/iris/tests/unit/fileformats/pp/test_PPField.py index d1e194aa3c..5e2bbcaa2c 100644 --- a/lib/iris/tests/unit/fileformats/pp/test_PPField.py +++ b/lib/iris/tests/unit/fileformats/pp/test_PPField.py @@ -143,7 +143,7 @@ class Test_calendar(tests.IrisTest): def test_greg(self): field = DummyPPField() field.lbtim = SplittableInt(1, {"ia": 2, "ib": 1, "ic": 0}) - self.assertEqual(field.calendar, "gregorian") + self.assertEqual(field.calendar, "standard") def test_360(self): field = DummyPPField() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py index 2aae32b1ae..cf147e5928 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py @@ -13,7 +13,7 @@ # importing anything else. import iris.tests as tests # isort:skip -from cf_units import CALENDAR_360_DAY, CALENDAR_GREGORIAN, Unit +from cf_units import CALENDAR_360_DAY, CALENDAR_STANDARD, Unit from cftime import datetime as nc_datetime import numpy as np @@ -38,7 +38,7 @@ def _lbcode(value=None, ix=None, iy=None): return result -_EPOCH_HOURS_UNIT = Unit("hours since epoch", calendar=CALENDAR_GREGORIAN) +_EPOCH_HOURS_UNIT = Unit("hours since epoch", calendar=CALENDAR_STANDARD) _HOURS_UNIT = Unit("hours") diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py index 2877d6ea89..2c5d672e14 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py @@ -28,9 +28,9 @@ # -class TestEpochHours__gregorian(tests.IrisTest): +class TestEpochHours__standard(tests.IrisTest): def setUp(self): - self.calendar = cf_units.CALENDAR_GREGORIAN + self.calendar = cf_units.CALENDAR_STANDARD self.hrs_unit = Unit("hours since epoch", calendar=self.calendar) def test_1970_1_1(self): diff --git a/lib/iris/tests/unit/plot/test__fixup_dates.py b/lib/iris/tests/unit/plot/test__fixup_dates.py index 1ad5c87691..0abef01e41 100644 --- a/lib/iris/tests/unit/plot/test__fixup_dates.py +++ b/lib/iris/tests/unit/plot/test__fixup_dates.py @@ -19,8 +19,8 @@ class Test(tests.IrisTest): - def test_gregorian_calendar(self): - unit = Unit("hours since 2000-04-13 00:00:00", calendar="gregorian") + def test_standard_calendar(self): + unit = Unit("hours since 2000-04-13 00:00:00", calendar="standard") coord = AuxCoord([1, 3, 6], "time", units=unit) result = _fixup_dates(coord, coord.points) self.assertIsInstance(result[0], datetime.datetime) @@ -31,8 +31,8 @@ def test_gregorian_calendar(self): ] self.assertArrayEqual(result, expected) - def test_gregorian_calendar_sub_second(self): - unit = Unit("seconds since 2000-04-13 00:00:00", calendar="gregorian") + def test_standard_calendar_sub_second(self): + unit = Unit("seconds since 2000-04-13 00:00:00", calendar="standard") coord = AuxCoord([1, 1.25, 1.5], "time", units=unit) result = _fixup_dates(coord, coord.points) self.assertIsInstance(result[0], datetime.datetime) diff --git a/lib/iris/tests/unit/util/test_unify_time_units.py b/lib/iris/tests/unit/util/test_unify_time_units.py index 16dc7054f3..daf71890b1 100644 --- a/lib/iris/tests/unit/util/test_unify_time_units.py +++ b/lib/iris/tests/unit/util/test_unify_time_units.py @@ -20,7 +20,7 @@ class Test(tests.IrisTest): - def simple_1d_time_cubes(self, calendar="gregorian"): + def simple_1d_time_cubes(self, calendar="standard"): coord_points = [1, 2, 3, 4, 5] data_points = [273, 275, 278, 277, 274] reftimes = [ @@ -92,7 +92,7 @@ def test_time_coord_only_in_some_cubes(self): def test_multiple_time_coords_in_cube(self): cube0, cube1 = self.simple_1d_time_cubes() units = cf_units.Unit( - "days since 1980-05-02 00:00:00", calendar="gregorian" + "days since 1980-05-02 00:00:00", calendar="standard" ) aux_coord = iris.coords.AuxCoord( 72, standard_name="forecast_reference_time", units=units diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 1b1c973e15..07098b8ccf 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -30,7 +30,7 @@ https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c @@ -84,7 +84,7 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_1.tar.bz2#3db63b53bb194dbaa7dc3d8833e98da2 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 @@ -130,7 +130,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 @@ -200,14 +200,14 @@ https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py310hff52083_0.tar.bz2#7fc5b1d6db9f6a9307330303a3edb04d https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py310h5764c6d_3.tar.bz2#8a5770e6392d29d99c9bc9c3635bba60 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.0-py310hff52083_0.tar.bz2#75742870c37ab7ef5ee61846bcd342a2 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py310hff52083_0.tar.bz2#7f6c48710ee99edfa3dfa0b54fa6f020 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py310hde88566_1.tar.bz2#e9505227ecb7a00a4a9fffc7a9a98d14 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py310hde88566_2.tar.bz2#90ebffd27e2d482997d3cdb1a60e993a https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py310h597c629_0.tar.bz2#7b40622ed00061cc8f803c5ed3c62707 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py310h5764c6d_0.tar.bz2#b2171665e9cd3ba4114d90b8da6815c8 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.0-hf9f4e7c_0.tar.bz2#dbb42a46de29a600e71952dfdabfba7e +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 @@ -224,21 +224,21 @@ https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h7b2ee30_2.ta https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py310h122e73d_2.tar.bz2#f485cb3efb4c179928a96fb2e02e6f7e https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py310h96516ba_2.tar.bz2#595f4a5a69f91cd0f72e75417c2eeedb +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py310hde88566_0.tar.bz2#6ff69580c6b0cb8540ff16a3771667ee https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_0.tar.bz2#b038b2e97ae14fea11159dcb2c5abf0a -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py310hd7ca5b8_101.tar.bz2#39f597922f7db648da47b67829d71280 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h947f774_100.tar.bz2#ebde0c4a610be54e818f5407ddc33af7 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py310hd8f1fbe_1.tar.bz2#21ae1ac216cfc85e24dc7c09570ddf31 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py310hb408dcc_6.tar.bz2#69abe28e5da19c0bc7efc798ecd86a27 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310hb408dcc_0.tar.bz2#32c47109680148344fd76448d808ac75 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py310hff52083_0.tar.bz2#bcf63938923fd8c16c684a4e7157b062 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py310h29803b5_1.tar.bz2#d34f0b4c9ad5705d0ffe501d5d8aa26f @@ -246,7 +246,7 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_0.tar.bz2#0b90a9f77544f943264d47677d63ac9e -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.0-pyhd8ed1ab_1.tar.bz2#5db4d14905f98da161e2153b1c9d2bce +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index e563cf9695..b6960b7442 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -29,7 +29,7 @@ https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c @@ -83,7 +83,7 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_1.tar.bz2#3db63b53bb194dbaa7dc3d8833e98da2 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 @@ -129,7 +129,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 @@ -199,14 +199,14 @@ https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py38h578d9bd_0.tar.bz2#4dbffb6d975f26cd71fb27aa20fc4761 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.0-py38h578d9bd_0.tar.bz2#87e1283dc05d80ceaa21ebd8550722ce +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py38h578d9bd_0.tar.bz2#0a703d05848de7738a1466b5e0a99274 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_1.tar.bz2#16d4a68061bf898fa4126cf213ebb14e +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_2.tar.bz2#f0c8c8b7047356710ed0612ea9364890 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30_0.tar.bz2#bcc387154aae535f8b4f84822621b5f7 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.0-hf9f4e7c_0.tar.bz2#dbb42a46de29a600e71952dfdabfba7e +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 @@ -223,21 +223,21 @@ https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h928055b_2.tar https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py38h709712a_2.tar.bz2#8ff0cdb63842c29388a34a6af7b5ce6f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py38h6c62de6_2.tar.bz2#350322b046c129e5802b79358a1343f7 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py38h71d37f0_0.tar.bz2#948a6c68f96103a3fdbc2f21a91145e3 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_0.tar.bz2#107af20136422bcabf9f1195f6262117 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py38h2823cc8_101.tar.bz2#1dfe1cdee4532c72f893955259eb3de9 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38he1ab104_100.tar.bz2#ad4bc1e4ed59a1c1464f7b511f7bf74b https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py38hfa26641_1.tar.bz2#40f4eeb2cb0f0ab25d0640f5f7a34de8 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py38hb3c56ba_6.tar.bz2#9ec1f7a5fe50f16f5103845db2ebd6d8 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38hb3c56ba_0.tar.bz2#d8703766f96665b77519c72beed1f6c4 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py38h7492b6b_1.tar.bz2#7a78a346ffd3297790b81cce35d32083 @@ -245,7 +245,7 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.0-pyhd8ed1ab_1.tar.bz2#5db4d14905f98da161e2153b1c9d2bce +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 6b8194e37f..a49dac9849 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -30,7 +30,7 @@ https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_1.tar.bz2#4828c7f7208321cfbede4880463f4930 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c @@ -84,7 +84,7 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_1.tar.bz2#3db63b53bb194dbaa7dc3d8833e98da2 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 @@ -130,7 +130,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.0.12-pyhd8ed1ab_0.tar.bz2#1f5b32dabae0f1893ae3283dac7f799e +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 @@ -200,14 +200,14 @@ https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py39hf3d152e_0.tar.bz2#ddfb37b6e91b8c41be3976d19af47ade https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py39hb9d737c_3.tar.bz2#5e13a2d214ed4184969df363a1aab420 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.0-py39hf3d152e_0.tar.bz2#baf424316921dd61ac57a1c21ddf2711 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py39hf3d152e_0.tar.bz2#59361d2352ad38bbcec48f9275eff7d5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py39hd257fcd_1.tar.bz2#d7acdd66aea024ddf51a957e006305f0 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py39hd257fcd_2.tar.bz2#ee8fda438f307ee5b858075b2d90d5a7 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py39hd97740a_0.tar.bz2#11780968ae65fdeb1a0bc294d211597d https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py39hb9d737c_0.tar.bz2#43f3c538bbcf6ed0da225891e11bf0a8 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.0-hf9f4e7c_0.tar.bz2#dbb42a46de29a600e71952dfdabfba7e +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 @@ -224,21 +224,21 @@ https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h4fbd0eb_2.tar https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py39he80948d_2.tar.bz2#e93686e0252282dd8ccba5c6cbcd8295 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.0.1-py39hce5d2b2_2.tar.bz2#599cee42b88c9afba10033c01fb40d74 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py39hd257fcd_0.tar.bz2#fafe86e78a01190cc1e6e29cfa4fa0ed https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_0.tar.bz2#ab1bcd0fd24e375f16d662e4cc783cab -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.5.8-nompi_py39h64b754b_101.tar.bz2#75875ec36c7d80400bd7bb0b23fab429 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39hf5a3a3f_100.tar.bz2#e7f0560cfc4fdbd1fb4fc8a305410a44 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py39h5a03fae_1.tar.bz2#bff0f5f688af0dc337d11517c5a46402 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.2-py39ha2ae0e9_6.tar.bz2#34e933f8cd0e61990d50859a3eadca9e +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39ha2ae0e9_0.tar.bz2#eef9ac84cc8162636e1d089649bab5c0 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.3-h7abd40a_0.tar.bz2#02b82b1dc4e876242900dcaff109e697 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py39hf3d152e_0.tar.bz2#dddc330e78d96d59c0cf68bf39dc09b1 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py39h18e9c17_1.tar.bz2#111647767bc84d4795f829bdc07dbb27 @@ -246,7 +246,7 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py39hf3d152e_0.tar.bz2#d65d073d186977a2a9a9d5a68b2b77ef -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.0-pyhd8ed1ab_1.tar.bz2#5db4d14905f98da161e2153b1c9d2bce +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 02deb5525f..feaa593de2 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -11,7 +11,7 @@ dependencies: # Core dependencies. - cartopy >=0.20 - - cf-units >=3 + - cf-units >=3.1 - cftime >=1.5 - dask-core >=2 - matplotlib diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 6f782a831d..c5bb93e064 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -11,7 +11,7 @@ dependencies: # Core dependencies. - cartopy >=0.20 - - cf-units >=3 + - cf-units >=3.1 - cftime >=1.5 - dask-core >=2 - matplotlib diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 3d0dab94f7..a901e85c12 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -11,7 +11,7 @@ dependencies: # Core dependencies. - cartopy >=0.20 - - cf-units >=3 + - cf-units >=3.1 - cftime >=1.5 - dask-core >=2 - matplotlib diff --git a/setup.cfg b/setup.cfg index 116095e64a..fe8cd81e5b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -46,7 +46,7 @@ version = attr: iris.__version__ include_package_data = True install_requires = cartopy>=0.20 - cf-units>=3 + cf-units>=3.1 cftime>=1.5.0 dask[array]>=2 matplotlib From 7187b0e47d1659c3f1e29f9a9d7fec21b6efc249 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Sat, 2 Jul 2022 10:07:20 +0100 Subject: [PATCH 162/319] Updated environment lockfiles (#4850) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 2 +- requirements/ci/nox.lock/py38-linux-64.lock | 2 +- requirements/ci/nox.lock/py39-linux-64.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 07098b8ccf..98c1c65d36 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 6e4ca9b4ac3d61e97093bdf528ea99b9569aafeab377acd24324cf6d63069a97 +# input_hash: 067d55c87f97649f3822a424e6b2d4b8d6a5995536c8f513e02f1d31400bcf72 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index b6960b7442..277371307e 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 1cd97f6f80cce9e7fa719a886fb090579568d5909bfcac6cb42a820ad562dad3 +# input_hash: d8c856206fe77b3d616a60e61babd0c745d9605517e008e7b28536d0c4cf1fb3 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index a49dac9849..4bd0ec5f68 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 8892442b4a37d8bf563ef33883a79ad3f126a997042a926a12309d6a655e8b70 +# input_hash: ad69bb2c81f01f127c33e3ea95acc59af4e280b1777751c6ecf1741ebced8a70 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 From c0c86c2182e3d04f81d242aa524a10c5cb9ace0b Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 4 Jul 2022 10:06:29 +0100 Subject: [PATCH 163/319] avoid setuptools deprecation (#4848) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index fe8cd81e5b..66e865572e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ keywords = ugrid visualisation license = LGPL-3.0-or-later -license_file = COPYING.LESSER +license_files = COPYING.LESSER long_description = file: README.md long_description_content_type = text/markdown name = scitools-iris From 28da78595401438ff5d3fd4ea7144d597d8cbef2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 08:39:04 +0100 Subject: [PATCH 164/319] [pre-commit.ci] pre-commit autoupdate (#4851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) - [github.com/psf/black: 22.3.0 → 22.6.0](https://github.com/psf/black/compare/22.3.0...22.6.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2563497de..855bbe70a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ minimum_pre_commit_version: 1.21.0 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: # Prevent giant files from being committed. - id: check-added-large-files @@ -29,7 +29,7 @@ repos: - id: no-commit-to-branch - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black pass_filenames: false From 7992fb50e95703155aecbb402df007c7a7f67bc3 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 5 Jul 2022 16:59:59 +0100 Subject: [PATCH 165/319] Alternative Cell.__hash__ implementation for NaN support (#4852) * Alternative Cell.__hash__ implementation for NaN support * add whatsnew entry --- docs/src/whatsnew/latest.rst | 3 +++ lib/iris/coords.py | 36 +++++++++++++++++-------- lib/iris/tests/unit/coords/test_Cell.py | 18 +++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 5c62a27830..c3d7c13b3f 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -247,6 +247,9 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ removed some now redundant testing methods. (:pull:`4838`) +#. `@bjlittle`_ extended the GitHub Continuous-Integration to cover testing + on ``py38``, ``py39``, and ``py310``. (:pull:`4840` and :pull:`4852`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 48fabfaf64..3c28fa591f 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -1266,7 +1266,13 @@ def _get_2d_coord_bound_grid(bounds): return result -class Cell(namedtuple("Cell", ["point", "bound"])): +class _Hash: + """Mutable hash property""" + + slots = ("_hash",) + + +class Cell(namedtuple("Cell", ["point", "bound"]), _Hash): """ An immutable representation of a single cell of a coordinate, including the sample point and/or boundary position. @@ -1326,6 +1332,18 @@ 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 @@ -1346,23 +1364,19 @@ def __add__(self, mod): def __hash__(self): # Required for >py39 and >np1.22.x due to changes in Cell behaviour for - # point=np.nan, as calling super().__hash__() returns a different - # hash 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. + # 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 - - try: - point = "nan" if np.isnan(self.point) else self.point - except TypeError: - point = self.point - return hash((point,)) + return self._hash def __eq__(self, other): """ diff --git a/lib/iris/tests/unit/coords/test_Cell.py b/lib/iris/tests/unit/coords/test_Cell.py index a161d7df9a..2910fde9e3 100644 --- a/lib/iris/tests/unit/coords/test_Cell.py +++ b/lib/iris/tests/unit/coords/test_Cell.py @@ -155,6 +155,24 @@ 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() From d9318f5130c07bb3f8fe1576289e9a349bee70df Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 6 Jul 2022 11:49:37 +0100 Subject: [PATCH 166/319] whatsnew update (#4853) --- docs/src/whatsnew/latest.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index c3d7c13b3f..9e99546677 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -233,8 +233,8 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ made :func:`iris.tests.stock.simple_1d` respect the ``with_bounds`` argument. (:pull:`4658`) -#. `@bjlittle`_ migrated to GitHub Actions for Continuous-Integration. - (:pull:`4503`) +#. `@bjlittle`_ and `@trexfeathers`_ (reviewer) migrated to GitHub Actions + for Continuous-Integration. (:pull:`4503`) #. `@pp-mo`_ made tests run certain linux executables from the Python env, specifically ncdump and ncgen. These could otherwise fail when run in IDEs @@ -247,8 +247,9 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ removed some now redundant testing methods. (:pull:`4838`) -#. `@bjlittle`_ extended the GitHub Continuous-Integration to cover testing - on ``py38``, ``py39``, and ``py310``. (:pull:`4840` and :pull:`4852`) +#. `@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`) .. comment From 036c90c4faf0ad51cbf9a421f49b2f11ad7e5c05 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 6 Jul 2022 13:43:41 +0100 Subject: [PATCH 167/319] adopt setuptools-scm for auto versioning (#4841) * adopt setuptools-scm for auto versioning * update py310.yml requirements * update locks * simplify docs version * update dev release docs * update .gitignore for vscode * update locks * add exclude _version.py to MANIFEST.in * review actions * update lock files * update pyproject.toml * what a whatsnew entry --- .git_archival.txt | 4 +++ .gitattributes | 1 + .gitignore | 6 +++++ MANIFEST.in | 1 + docs/src/conf.py | 20 +++------------ docs/src/developers_guide/release.rst | 21 ++++++++------- docs/src/whatsnew/latest.rst | 4 +++ lib/iris/__init__.py | 7 +++-- pyproject.toml | 6 ++++- requirements/ci/nox.lock/py310-linux-64.lock | 27 +++++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 27 +++++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 27 +++++++++++--------- requirements/ci/py310.yml | 3 ++- requirements/ci/py38.yml | 3 ++- requirements/ci/py39.yml | 3 ++- 15 files changed, 91 insertions(+), 69 deletions(-) create mode 100644 .git_archival.txt create mode 100644 .gitattributes diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 0000000000..3994ec0a83 --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..82bf71c1c5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.git_archival.txt export-subst \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1791caf3f3..512fbab231 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.py[co] +# setuptools-scm +_version.py + # Environment file which should be autogenerated *conda_requirements.txt* @@ -55,6 +58,9 @@ lib/iris/tests/results/imagerepo.lock /.idea *.cover +# vscode files +.vscode + # Auto generated documentation files docs/src/_build/* docs/src/generated diff --git a/MANIFEST.in b/MANIFEST.in index 52492b17b2..81d7165199 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -21,3 +21,4 @@ include etc/cf-standard-name-table.xml global-exclude *.pyc global-exclude __pycache__ global-exclude iris_image_test_output +exclude lib/iris/_version.py \ No newline at end of file diff --git a/docs/src/conf.py b/docs/src/conf.py index 4fe2513bb1..4025d8159f 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -20,6 +20,7 @@ # ---------------------------------------------------------------------------- import datetime +from importlib.metadata import version as get_version import ntpath import os from pathlib import Path @@ -27,8 +28,6 @@ import sys import warnings -import iris - # function to write useful output to stdout, prefixing the source. def autolog(message): @@ -85,21 +84,10 @@ def autolog(message): author = "Iris Developers" # The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. - -# The short X.Y version. -if iris.__version__ == "dev": - version = "dev" -else: - # major.minor.patch-dev -> major.minor.patch - version = ".".join(iris.__version__.split("-")[0].split(".")[:3]) - -# The full version, including alpha/beta/rc tags. -release = iris.__version__ +# |version|, also used in various other places throughout the built documents. -autolog("Iris Version = {}".format(version)) -autolog("Iris Release = {}".format(release)) +version = get_version("scitools-iris") +autolog(f"Iris Version = {version}") # -- General configuration --------------------------------------------------- diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index b2de9106a2..182ab482b3 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -100,12 +100,14 @@ Steps to achieve this can be found in the :ref:`iris_development_releases_steps` The Release ----------- -The final steps of the release are to change the version string ``__version__`` -in the source of :literal:`iris.__init__.py` and ensure the release date and details +The final steps of the release are to ensure that the release date and details are correct in the relevant ``whatsnew`` page within the documentation. -Once all checks are complete, the release is cut by the creation of a new tag -in the ``SciTools/iris`` repository. +There is no need to update the ``iris.__version__``, as this is managed +automatically by `setuptools-scm`_. + +Once all checks are complete, the release is published on GitHub by +creating a new tag in the ``SciTools/iris`` repository. Update conda-forge @@ -179,14 +181,14 @@ For further details on how to test Iris, see :ref:`developer_running_tests`. Merge Back ---------- -After the release is cut, the changes from the release branch should be merged +After the release is published, the changes from the release branch should be merged back onto the ``SciTools/iris`` ``main`` branch. To achieve this, first cut a local branch from the latest ``main`` branch, and `git merge` the :literal:`.x` release branch into it. Ensure that the -``iris.__version__``, ``docs/src/whatsnew/index.rst``, -and ``docs/src/whatsnew/latest.rst`` are correct, before committing these changes -and then proposing a pull-request on the ``main`` branch of ``SciTools/iris``. +``docs/src/whatsnew/index.rst`` and ``docs/src/whatsnew/latest.rst`` are +correct, before committing these changes and then proposing a pull-request +on the ``main`` branch of ``SciTools/iris``. Point Releases @@ -274,4 +276,5 @@ Post Release Steps .. _rc_iris: https://anaconda.org/conda-forge/iris/labels .. _Generating Distribution Archives: https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives .. _Packaging Your Project: https://packaging.python.org/guides/distributing-packages-using-setuptools/#packaging-your-project -.. _latest CF standard names: http://cfconventions.org/standard-names.html \ No newline at end of file +.. _latest CF standard names: http://cfconventions.org/standard-names.html +.. _setuptools-scm: https://github.com/pypa/setuptools_scm \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9e99546677..761833ba15 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -251,6 +251,9 @@ This document explains the changes made to Iris for this release the GitHub Continuous-Integration to cover testing on ``py38``, ``py39``, and ``py310``. (:pull:`4840` and :pull:`4852`) +#. `@bjlittle`_ and `@trexfeathers`_ (reviewer) adopted `setuptools-scm`_ for + automated ``iris`` package versioning. (:pull:`4841`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, @@ -265,3 +268,4 @@ This document explains the changes made to Iris for this release .. _Calendar: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#calendar .. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries .. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html +.. _setuptools-scm: https://github.com/pypa/setuptools_scm \ No newline at end of file diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 7442ff1173..7ff0a4260e 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -97,19 +97,18 @@ def callback(cube, field, filename): import threading import iris._constraints -from iris._deprecation import IrisDeprecation, warn_deprecated import iris.config import iris.io +from ._deprecation import IrisDeprecation, warn_deprecated +from ._version import version as __version__ # noqa: F401 + try: import iris_sample_data except ImportError: iris_sample_data = None -# Iris revision. -__version__ = "3.3.dev0" - # Restrict the names imported when using "from iris import *" __all__ = [ "AttributeConstraint", diff --git a/pyproject.toml b/pyproject.toml index d72d9aba3d..a782c34a27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,16 @@ [build-system] # Defined by PEP 518 requires = [ - "setuptools>=40.8.0", + "setuptools>=45", + "setuptools_scm[toml]>=7.0", "wheel", ] # Defined by PEP 517 build-backend = "setuptools.build_meta" +[tool.setuptools_scm] +write_to = "lib/iris/_version.py" +local_scheme = "dirty-tag" [tool.black] line-length = 79 diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 98c1c65d36..689b5c9d6e 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 067d55c87f97649f3822a424e6b2d4b8d6a5995536c8f513e02f1d31400bcf72 +# input_hash: b9e611fba7bc4bd71c8f4e36acf1dde0bd10352159237ea4d2e979ee66214d23 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -26,7 +26,7 @@ https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d11cf000ee8b976b5ce3b425477b5689 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -97,7 +97,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a @@ -143,7 +143,7 @@ https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd -https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea @@ -169,6 +169,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 @@ -176,7 +177,7 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py310h0fdd8cc_0.tar.bz2#7b7366be82277a5a210e48cc6d25ce26 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e @@ -188,7 +189,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_1.tar https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py310h53a5b5f_0.tar.bz2#16493af3907dbd772904e15bf24948d8 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.1.1-py310he619898_1.tar.bz2#d7052b5cef119518ca362a2ff633a36d +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310he619898_0.tar.bz2#5808b13c720854aaa3307c6e04cc1505 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed @@ -197,12 +198,13 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.ta https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py310hff52083_0.tar.bz2#7fc5b1d6db9f6a9307330303a3edb04d -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py310h5764c6d_3.tar.bz2#8a5770e6392d29d99c9bc9c3635bba60 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.1.0-py310hff52083_0.tar.bz2#4957e3a46761a6c7a3a05233c39ed904 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py310hff52083_0.tar.bz2#7f6c48710ee99edfa3dfa0b54fa6f020 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py310hde88566_2.tar.bz2#90ebffd27e2d482997d3cdb1a60e993a +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py310h597c629_0.tar.bz2#7b40622ed00061cc8f803c5ed3c62707 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py310h5764c6d_0.tar.bz2#b2171665e9cd3ba4114d90b8da6815c8 @@ -220,7 +222,8 @@ https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310h7612f91_0.tar.bz2#14a7ea0620e4c0801bee756171f4dc03 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h7b2ee30_2.tar.bz2#a95baebc52f890fa8bf71f2d3f536ae1 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h5e49deb_3.tar.bz2#5305d80a31c7db98765b52ea95521ff6 https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py310h122e73d_2.tar.bz2#f485cb3efb4c179928a96fb2e02e6f7e https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d @@ -230,12 +233,12 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_0.tar.bz2#b038b2e97ae14fea11159dcb2c5abf0a https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h947f774_100.tar.bz2#ebde0c4a610be54e818f5407ddc33af7 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py310hd8f1fbe_1.tar.bz2#21ae1ac216cfc85e24dc7c09570ddf31 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310hb408dcc_0.tar.bz2#32c47109680148344fd76448d808ac75 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_1.tar.bz2#a06eff87c5bbd8ece667e155854fec2b https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 277371307e..736d3e12df 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: d8c856206fe77b3d616a60e61babd0c745d9605517e008e7b28536d0c4cf1fb3 +# input_hash: e4bb2cf171924c5e31a2295669bea2f19b5a76f0ba3676dd535f044fe801255a @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -25,7 +25,7 @@ https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d11cf000ee8b976b5ce3b425477b5689 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -96,7 +96,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a @@ -142,7 +142,7 @@ https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd -https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea @@ -168,6 +168,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a @@ -175,7 +176,7 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py38h3931269_0.tar.bz2#9c491a90ae11d08ca97326a0ed876f3a +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 @@ -187,7 +188,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar. https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py38h3a7f9d9_0.tar.bz2#bde7c584b811ef5aec0dd2204e502334 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.1.1-py38h0ee0e06_1.tar.bz2#cd653a4a951ca80adb96ff6cd3b36883 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h0ee0e06_0.tar.bz2#7ef61f9084bda76b2f7a668ec5d1499a https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 @@ -196,12 +197,13 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py38h578d9bd_0.tar.bz2#4dbffb6d975f26cd71fb27aa20fc4761 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py38h0a891b7_3.tar.bz2#d9e2836a4a46935f84b858462d54a7c3 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.1.0-py38h578d9bd_0.tar.bz2#5f57352931001b831e0f3702801e6fc8 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py38h578d9bd_0.tar.bz2#0a703d05848de7738a1466b5e0a99274 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py38h71d37f0_2.tar.bz2#f0c8c8b7047356710ed0612ea9364890 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30_0.tar.bz2#bcc387154aae535f8b4f84822621b5f7 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e @@ -219,7 +221,8 @@ https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar. https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38h1ee437e_0.tar.bz2#a0a8bc19d491ec659a534c9a11cf74a0 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h928055b_2.tar.bz2#09810d4eb86812673f88a1f396fe1bdf +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h3b45516_3.tar.bz2#b2415a314858f4232aab437bc27803fc https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py38h709712a_2.tar.bz2#8ff0cdb63842c29388a34a6af7b5ce6f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 @@ -229,12 +232,12 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_0.tar.bz2#107af20136422bcabf9f1195f6262117 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38he1ab104_100.tar.bz2#ad4bc1e4ed59a1c1464f7b511f7bf74b -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py38hfa26641_1.tar.bz2#40f4eeb2cb0f0ab25d0640f5f7a34de8 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38hb3c56ba_0.tar.bz2#d8703766f96665b77519c72beed1f6c4 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_1.tar.bz2#9e07b83f7d5e70bb48d13938ff93faf9 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 4bd0ec5f68..f967125d1c 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: ad69bb2c81f01f127c33e3ea95acc59af4e280b1777751c6ecf1741ebced8a70 +# input_hash: 4e20df906742e42d00c1dc04af27bb5a312df25365015a497178d7ea36cce391 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -26,7 +26,7 @@ https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/geos-3.10.3-h27087fc_0.tar.bz2#d11cf000ee8b976b5ce3b425477b5689 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -97,7 +97,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_0.tar.bz2#7d623237b73d93dd856b5dd0f5fedd6b +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a @@ -143,7 +143,7 @@ https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd -https://conda.anaconda.org/conda-forge/noarch/imagesize-1.3.0-pyhd8ed1ab_0.tar.bz2#be807e7606fff9436e5e700f6bffb7c6 +https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea @@ -169,6 +169,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb @@ -176,7 +177,7 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14 -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.0-py39h4bc2ebd_0.tar.bz2#f6191bf565dee581e77549d63737751c +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb @@ -188,7 +189,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_1.tar. https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py39hba7629e_0.tar.bz2#0e48a6f61637735a88644359d90f5f1e https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.1.1-py39hae2aec6_1.tar.bz2#9039c6b86ddb65d7e8bf464337ae9053 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hae2aec6_0.tar.bz2#ea9760ab3700e63809de6ae71a11664b https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef @@ -197,12 +198,13 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-62.6.0-py39hf3d152e_0.tar.bz2#ddfb37b6e91b8c41be3976d19af47ade -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.1-py39hb9d737c_3.tar.bz2#5e13a2d214ed4184969df363a1aab420 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.1.0-py39hf3d152e_0.tar.bz2#7014afab44a0e69fba02c8bf8d084e7f +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py39hf3d152e_0.tar.bz2#59361d2352ad38bbcec48f9275eff7d5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.0-py39hd257fcd_2.tar.bz2#ee8fda438f307ee5b858075b2d90d5a7 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py39hd97740a_0.tar.bz2#11780968ae65fdeb1a0bc294d211597d https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py39hb9d737c_0.tar.bz2#43f3c538bbcf6ed0da225891e11bf0a8 @@ -220,7 +222,8 @@ https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar. https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39he49c0e8_0.tar.bz2#b1b4cc4216e555168e88d6a2b1914af1 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h4fbd0eb_2.tar.bz2#d4039ba3e744a45777763df7a22250f9 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h68ae834_3.tar.bz2#2800d4dabd586cf203d8f1271a6c9e9d https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py39he80948d_2.tar.bz2#e93686e0252282dd8ccba5c6cbcd8295 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 @@ -230,12 +233,12 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_0.tar.bz2#ab1bcd0fd24e375f16d662e4cc783cab https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39hf5a3a3f_100.tar.bz2#e7f0560cfc4fdbd1fb4fc8a305410a44 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.7-hbd2fdc8_0.tar.bz2#1cff4bab8ed133d59b7c22fe7bf09263 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py39h5a03fae_1.tar.bz2#bff0f5f688af0dc337d11517c5a46402 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39ha2ae0e9_0.tar.bz2#eef9ac84cc8162636e1d089649bab5c0 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_1.tar.bz2#7264fa97520021d1a01f5be9741493c2 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index feaa593de2..19d4e90c22 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -7,7 +7,8 @@ dependencies: - python =3.10 # Setup dependencies. - - setuptools >=40.8.0 + - setuptools >=45 + - setuptools-scm >=7 # Core dependencies. - cartopy >=0.20 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index c5bb93e064..55a64676bf 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -7,7 +7,8 @@ dependencies: - python =3.8 # Setup dependencies. - - setuptools >=40.8.0 + - setuptools >=45 + - setuptools-scm >=7 # Core dependencies. - cartopy >=0.20 diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index a901e85c12..abd11e48a2 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -7,7 +7,8 @@ dependencies: - python =3.9 # Setup dependencies. - - setuptools >=40.8.0 + - setuptools >=45 + - setuptools-scm >=7 # Core dependencies. - cartopy >=0.20 From 573780b80cc0e57e7b50c16fd0ab932992d48092 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 7 Jul 2022 09:45:29 +0100 Subject: [PATCH 168/319] fix rtd version (#4855) --- .readthedocs.yml | 4 ++++ docs/src/conf.py | 2 ++ 2 files changed, 6 insertions(+) diff --git a/.readthedocs.yml b/.readthedocs.yml index 63c4798050..8ec8ab145c 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -4,6 +4,10 @@ build: os: ubuntu-20.04 tools: python: mambaforge-4.10 + jobs: + post_checkout: + - git fetch --unshallow + - git fetch --all conda: environment: requirements/ci/readthedocs.yml diff --git a/docs/src/conf.py b/docs/src/conf.py index 4025d8159f..4f1eae7403 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -87,6 +87,8 @@ def autolog(message): # |version|, also used in various other places throughout the built documents. version = get_version("scitools-iris") +if version.endswith("+dirty"): + version = version[: -len("+dirty")] autolog(f"Iris Version = {version}") # -- General configuration --------------------------------------------------- From 2cbaec747820410cce41db004a41e68d6ff63ee8 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 10:32:10 +0100 Subject: [PATCH 169/319] Updated environment lockfiles (#4858) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 23 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 23 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 23 ++++++++++---------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 689b5c9d6e..44617d9362 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -53,7 +53,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1p-h166bdaf_0.tar.bz2#995e819f901ee0c4411e4f50d9b31a82 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 @@ -94,7 +94,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa @@ -150,7 +150,8 @@ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_0.tar.bz2#42d593cd1b23a43d7ac7edd9a2e84fa9 +https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc @@ -205,9 +206,9 @@ https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py310hff52083_0.tar.bz2#7f6c48710ee99edfa3dfa0b54fa6f020 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py310h597c629_0.tar.bz2#7b40622ed00061cc8f803c5ed3c62707 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py310h5764c6d_0.tar.bz2#b2171665e9cd3ba4114d90b8da6815c8 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py310h5764c6d_0.tar.bz2#c965e9e47e42b19d26994e848d2a40e6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -224,7 +225,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310h7612f91_0.tar.bz2#14a7ea0620e4c0801bee756171f4dc03 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h5e49deb_3.tar.bz2#5305d80a31c7db98765b52ea95521ff6 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py310h122e73d_2.tar.bz2#f485cb3efb4c179928a96fb2e02e6f7e +https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py310hde88566_0.tar.bz2#6ff69580c6b0cb8540ff16a3771667ee @@ -235,7 +236,7 @@ https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h947f774_100.tar.bz2#ebde0c4a610be54e818f5407ddc33af7 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py310hd8f1fbe_1.tar.bz2#21ae1ac216cfc85e24dc7c09570ddf31 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_1.tar.bz2#a06eff87c5bbd8ece667e155854fec2b @@ -244,10 +245,10 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py310hff52083_0.tar.bz2#bcf63938923fd8c16c684a4e7157b062 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py310h29803b5_1.tar.bz2#d34f0b4c9ad5705d0ffe501d5d8aa26f +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_0.tar.bz2#0b90a9f77544f943264d47677d63ac9e https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 736d3e12df..781cef3172 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -52,7 +52,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1p-h166bdaf_0.tar.bz2#995e819f901ee0c4411e4f50d9b31a82 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 @@ -93,7 +93,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa @@ -149,7 +149,8 @@ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_0.tar.bz2#42d593cd1b23a43d7ac7edd9a2e84fa9 +https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc @@ -204,9 +205,9 @@ https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py38h578d9bd_0.tar.bz2#0a703d05848de7738a1466b5e0a99274 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py38h2b5fc30_0.tar.bz2#bcc387154aae535f8b4f84822621b5f7 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py38h0a891b7_0.tar.bz2#fd11badf5b3f7d738cc983cb2c75946e +https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py38h0a891b7_0.tar.bz2#7ac14fa19454e00f50d9a39506bcc3c6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -223,7 +224,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1. https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38h1ee437e_0.tar.bz2#a0a8bc19d491ec659a534c9a11cf74a0 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h3b45516_3.tar.bz2#b2415a314858f4232aab437bc27803fc -https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py38h709712a_2.tar.bz2#8ff0cdb63842c29388a34a6af7b5ce6f +https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py38h71d37f0_0.tar.bz2#948a6c68f96103a3fdbc2f21a91145e3 @@ -234,7 +235,7 @@ https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bf https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38he1ab104_100.tar.bz2#ad4bc1e4ed59a1c1464f7b511f7bf74b https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py38hfa26641_1.tar.bz2#40f4eeb2cb0f0ab25d0640f5f7a34de8 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_1.tar.bz2#9e07b83f7d5e70bb48d13938ff93faf9 @@ -243,10 +244,10 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py38h7492b6b_1.tar.bz2#7a78a346ffd3297790b81cce35d32083 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index f967125d1c..30e2695443 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -53,7 +53,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1p-h166bdaf_0.tar.bz2#995e819f901ee0c4411e4f50d9b31a82 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 @@ -94,7 +94,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h21135ba_2.tar.bz2#b6acf807307d033d4b7e758b4f44b036 +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa @@ -150,7 +150,8 @@ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_0.tar.bz2#42d593cd1b23a43d7ac7edd9a2e84fa9 +https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc @@ -205,9 +206,9 @@ https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py39hf3d152e_0.tar.bz2#59361d2352ad38bbcec48f9275eff7d5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.2-py39hd97740a_0.tar.bz2#11780968ae65fdeb1a0bc294d211597d -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.6.1-pyhd8ed1ab_0.tar.bz2#69655c7e78034d4293130f5a5ecf7421 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.33.3-py39hb9d737c_0.tar.bz2#43f3c538bbcf6ed0da225891e11bf0a8 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py39hb9d737c_0.tar.bz2#7980ace37ccb3399672c3a9840e039ed https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -224,7 +225,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1. https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39he49c0e8_0.tar.bz2#b1b4cc4216e555168e88d6a2b1914af1 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h68ae834_3.tar.bz2#2800d4dabd586cf203d8f1271a6c9e9d -https://conda.anaconda.org/conda-forge/linux-64/sip-6.5.1-py39he80948d_2.tar.bz2#e93686e0252282dd8ccba5c6cbcd8295 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py39hd257fcd_0.tar.bz2#fafe86e78a01190cc1e6e29cfa4fa0ed @@ -235,7 +236,7 @@ https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h70065 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39hf5a3a3f_100.tar.bz2#e7f0560cfc4fdbd1fb4fc8a305410a44 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.9.0-py39h5a03fae_1.tar.bz2#bff0f5f688af0dc337d11517c5a46402 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_1.tar.bz2#7264fa97520021d1a01f5be9741493c2 @@ -244,10 +245,10 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py39hf3d152e_0.tar.bz2#dddc330e78d96d59c0cf68bf39dc09b1 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.4-py39h18e9c17_1.tar.bz2#111647767bc84d4795f829bdc07dbb27 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.9-pyhd8ed1ab_0.tar.bz2#0ea179ee251aa7100807c35bc0252693 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-4.0.0-h5abf519_0.tar.bz2#970a4e3632a3c2f27f1860600f2f5fb5 +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py39hf3d152e_0.tar.bz2#d65d073d186977a2a9a9d5a68b2b77ef https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 From 2e63cdc748c233a9b2a049de38024e846da2ad5b Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 12 Jul 2022 21:23:30 +0100 Subject: [PATCH 170/319] pytest verbosity (#4861) --- lib/iris/tests/runner/_runner.py | 1 - pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/iris/tests/runner/_runner.py b/lib/iris/tests/runner/_runner.py index 34a1af1e06..bfb2cc2402 100644 --- a/lib/iris/tests/runner/_runner.py +++ b/lib/iris/tests/runner/_runner.py @@ -130,7 +130,6 @@ def run(self): args = [ None, - "-v", f"-n={self.num_processors}", ] diff --git a/pyproject.toml b/pyproject.toml index a782c34a27..a45cf3a1fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,5 +43,5 @@ skip_gitignore = "True" verbose = "False" [tool.pytest.ini_options] -addopts = "-ra -q" +addopts = "-ra" testpaths = "lib/iris" From 7b12120f2f45336707778774c85210c0ed32a247 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 18 Jul 2022 10:43:51 +0100 Subject: [PATCH 171/319] Updated environment lockfiles (#4867) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 28 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 28 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 28 ++++++++++---------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 44617d9362..4d43544236 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -48,7 +48,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar. https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_1.tar.bz2#58eaff4f91891978af3625e7bbf958af +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 @@ -83,7 +83,7 @@ https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe @@ -91,7 +91,7 @@ https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a @@ -100,7 +100,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -111,7 +111,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.70.2-h780b84a_4.tar.bz2#c66c6df8ef582a3b78702201b1eb8e94 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 @@ -135,12 +135,12 @@ https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b -https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 @@ -149,7 +149,7 @@ https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 @@ -169,7 +169,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.ta https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 -https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c @@ -187,7 +187,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#e https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_1.tar.bz2#54eaedc01b1aee271e4f150d03ffc8b0 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py310h53a5b5f_0.tar.bz2#16493af3907dbd772904e15bf24948d8 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py310h53a5b5f_0.tar.bz2#9b86a46d908354fe7f91da24f5ea3f36 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310he619898_0.tar.bz2#5808b13c720854aaa3307c6e04cc1505 @@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.ta https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.1.0-py310hff52083_0.tar.bz2#4957e3a46761a6c7a3a05233c39ed904 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py310hff52083_0.tar.bz2#9a7c1d5f93e2b153d5b0b4930f8b6b66 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 @@ -223,12 +223,12 @@ https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310h7612f91_0.tar.bz2#14a7ea0620e4c0801bee756171f4dc03 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h5e49deb_3.tar.bz2#5305d80a31c7db98765b52ea95521ff6 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py310hde88566_0.tar.bz2#6ff69580c6b0cb8540ff16a3771667ee +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 @@ -244,7 +244,7 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c8 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py310hff52083_0.tar.bz2#bcf63938923fd8c16c684a4e7157b062 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 781cef3172..c185089fa6 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -47,7 +47,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar. https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_1.tar.bz2#58eaff4f91891978af3625e7bbf958af +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 @@ -82,7 +82,7 @@ https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe @@ -90,7 +90,7 @@ https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a @@ -99,7 +99,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -110,7 +110,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.70.2-h780b84a_4.tar.bz2#c66c6df8ef582a3b78702201b1eb8e94 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 @@ -134,12 +134,12 @@ https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b -https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 @@ -148,7 +148,7 @@ https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 @@ -168,7 +168,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.ta https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 -https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c @@ -186,7 +186,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#e https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py38h3a7f9d9_0.tar.bz2#bde7c584b811ef5aec0dd2204e502334 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py38h3a7f9d9_0.tar.bz2#90cf44c14b2bfe19ce7b875979b90cb9 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h0ee0e06_0.tar.bz2#7ef61f9084bda76b2f7a668ec5d1499a @@ -198,7 +198,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.1.0-py38h578d9bd_0.tar.bz2#5f57352931001b831e0f3702801e6fc8 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py38h578d9bd_0.tar.bz2#e69405e267e41bb8409e5ec307a6cd3d https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 @@ -222,12 +222,12 @@ https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar. https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38h1ee437e_0.tar.bz2#a0a8bc19d491ec659a534c9a11cf74a0 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h3b45516_3.tar.bz2#b2415a314858f4232aab437bc27803fc https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py38h71d37f0_0.tar.bz2#948a6c68f96103a3fdbc2f21a91145e3 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 @@ -243,7 +243,7 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h91476 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py38h578d9bd_0.tar.bz2#aa6a241a741c27c9560fd3cebb3f43ce +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 30e2695443..2843b1699b 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -48,7 +48,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar. https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_1.tar.bz2#58eaff4f91891978af3625e7bbf958af +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 @@ -83,7 +83,7 @@ https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_1.tar.bz2#e4b67f2b4096807cd7d836227c026a43 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe @@ -91,7 +91,7 @@ https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.70.2-h174f98d_4.tar.bz2#d44314ffae96b17657fbf3f8e47b04fc +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a @@ -100,7 +100,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.0-h4ff8645_0.tar.bz2#ead30581ba8cfd52d69632868b844d4a +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -111,7 +111,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.70.2-h780b84a_4.tar.bz2#c66c6df8ef582a3b78702201b1eb8e94 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 @@ -135,12 +135,12 @@ https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.4-pyhd8ed1ab_0.tar.bz2#7b50d840543d9cdae100e91582c33035 +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b -https://conda.anaconda.org/conda-forge/linux-64/glib-2.70.2-h780b84a_4.tar.bz2#977c857d773389a51442ad3a716c0480 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 @@ -149,7 +149,7 @@ https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.1-pyhd8ed1ab_0.tar.bz2#d5df87964a39f67c46a5448f4e78d9b6 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 @@ -169,7 +169,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.ta https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 -https://conda.anaconda.org/conda-forge/noarch/toolz-0.11.2-pyhd8ed1ab_0.tar.bz2#f348d1590550371edfac5ed3c1d44f7e +https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c @@ -187,7 +187,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#e https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_1.tar.bz2#a0475f4b801777d641058ca5ff08f07f -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.0-py39hba7629e_0.tar.bz2#0e48a6f61637735a88644359d90f5f1e +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py39hba7629e_0.tar.bz2#ee8dff1fb28e0e2c458e845aae9d915a https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hae2aec6_0.tar.bz2#ea9760ab3700e63809de6ae71a11664b @@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.1.0-py39hf3d152e_0.tar.bz2#7014afab44a0e69fba02c8bf8d084e7f +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py39hf3d152e_0.tar.bz2#0a487a44f996e39d13cdf2899855c406 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f @@ -223,12 +223,12 @@ https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar. https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39he49c0e8_0.tar.bz2#b1b4cc4216e555168e88d6a2b1914af1 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.4-pyhd8ed1ab_0.tar.bz2#dff6862ca0b54bbeab8ddf657d032920 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h68ae834_3.tar.bz2#2800d4dabd586cf203d8f1271a6c9e9d https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.0-py39hd257fcd_0.tar.bz2#fafe86e78a01190cc1e6e29cfa4fa0ed +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 @@ -244,7 +244,7 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb45 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.19.0-py39hf3d152e_0.tar.bz2#dddc330e78d96d59c0cf68bf39dc09b1 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec From 78009072d0aafc4d19d1fe43020fe376058dea5a Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 19 Jul 2022 23:10:32 +0100 Subject: [PATCH 172/319] Recommend `pytest` in developer guide (#4866) * recommend pytest * pros and cons * tweaks --- .../contributing_running_tests.rst | 108 +++++++++--------- docs/src/installing.rst | 4 +- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/docs/src/developers_guide/contributing_running_tests.rst b/docs/src/developers_guide/contributing_running_tests.rst index b9b89b3336..f60cedba05 100644 --- a/docs/src/developers_guide/contributing_running_tests.rst +++ b/docs/src/developers_guide/contributing_running_tests.rst @@ -5,13 +5,22 @@ Running the Tests ***************** -Using setuptools for Testing Iris -================================= +There are two options for running the tests: -.. warning:: The `setuptools`_ ``test`` command was deprecated in `v41.5.0`_. See :ref:`using nox`. +* Use an environment you created yourself. This requires more manual steps to + set up, but gives you more flexibility. For example, you can run a subset of + the tests or use ``python`` interactively to investigate any issues. See + :ref:`test manual env`. -A prerequisite of running the tests is to have the Python environment -setup. For more information on this see :ref:`installing_from_source`. +* Use ``nox``. This will automatically generate an environment and run test + sessions consistent with our GitHub continuous integration. See :ref:`using nox`. + +.. _test manual env: + +Testing Iris in a Manually Created Environment +============================================== + +To create a suitable environment for running the tests, see :ref:`installing_from_source`. Many Iris tests will use data that may be defined in the test itself, however this is not always the case as sometimes example files may be used. Due to @@ -32,74 +41,69 @@ The example command below uses ``~/projects`` as the parent directory:: git clone git@github.com:SciTools/iris-test-data.git export OVERRIDE_TEST_DATA_REPOSITORY=~/projects/iris-test-data/test_data -All the Iris tests may be run from the root ``iris`` project directory via:: - - python setup.py test - -You can also run a specific test, the example below runs the tests for -mapping:: - - cd lib/iris/tests - python test_mapping.py - -When running the test directly as above you can view the command line options -using the commands ``python test_mapping.py -h`` or -``python test_mapping.py --help``. - -.. tip:: A useful command line option to use is ``-d``. This will display - matplotlib_ figures as the tests are run. For example:: - - python test_mapping.py -d +All the Iris tests may be run from the root ``iris`` project directory using +``pytest``. For example:: - You can also use the ``-d`` command line option when running all - the tests but this will take a while to run and will require the - manual closing of each of the figures for the tests to continue. + pytest -n 2 -The output from running the tests is verbose as it will run ~5000 separate -tests. Below is a trimmed example of the output:: +will run the tests across two processes. For more options, use the command +``pytest -h``. Below is a trimmed example of the output:: - running test - Running test suite(s): default + ============================= test session starts ============================== + platform linux -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0 + rootdir: /path/to/git/clone/iris, configfile: pyproject.toml, testpaths: lib/iris + plugins: xdist-2.5.0, forked-1.4.0 + gw0 I / gw1 I + gw0 [6361] / gw1 [6361] - Running test discovery on iris.tests with 2 processors. - test_circular_subset (iris.tests.experimental.regrid.test_regrid_area_weighted_rectilinear_src_and_grid.TestAreaWeightedRegrid) ... ok - test_cross_section (iris.tests.experimental.regrid.test_regrid_area_weighted_rectilinear_src_and_grid.TestAreaWeightedRegrid) ... ok - test_different_cs (iris.tests.experimental.regrid.test_regrid_area_weighted_rectilinear_src_and_grid.TestAreaWeightedRegrid) ... ok + ........................................................................ [ 1%] + ........................................................................ [ 2%] + ........................................................................ [ 3%] ... + .......................ssssssssssssssssss............................... [ 99%] + ........................ [100%] + =============================== warnings summary =============================== ... - test_ellipsoid (iris.tests.unit.experimental.raster.test_export_geotiff.TestProjection) ... SKIP: Test requires 'gdal'. - test_no_ellipsoid (iris.tests.unit.experimental.raster.test_export_geotiff.TestProjection) ... SKIP: Test requires 'gdal'. + -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html + =========================== short test summary info ============================ + SKIPPED [1] lib/iris/tests/experimental/test_raster.py:152: Test requires 'gdal'. + SKIPPED [1] lib/iris/tests/experimental/test_raster.py:155: Test requires 'gdal'. ... - ... - test_slice (iris.tests.test_util.TestAsCompatibleShape) ... ok - test_slice_and_transpose (iris.tests.test_util.TestAsCompatibleShape) ... ok - test_transpose (iris.tests.test_util.TestAsCompatibleShape) ... ok - - ---------------------------------------------------------------------- - Ran 4762 tests in 238.649s - - OK (SKIP=22) + ========= 6340 passed, 21 skipped, 1659 warnings in 193.57s (0:03:13) ========== There may be some tests that have been **skipped**. This is due to a Python decorator being present in the test script that will intentionally skip a test if a certain condition is not met. In the example output above there are -**22** skipped tests, at the point in time when this was run this was primarily -due to an experimental dependency not being present. - +**21** skipped tests. At the point in time when this was run this was due to an +experimental dependency not being present. .. tip:: The most common reason for tests to be skipped is when the directory for the ``iris-test-data`` has not been set which would shows output such as:: - test_coord_coord_map (iris.tests.test_plot.Test1dScatter) ... SKIP: Test(s) require external data. - test_coord_coord (iris.tests.test_plot.Test1dScatter) ... SKIP: Test(s) require external data. - test_coord_cube (iris.tests.test_plot.Test1dScatter) ... SKIP: Test(s) require external data. - + SKIPPED [1] lib/iris/tests/unit/fileformats/test_rules.py:157: Test(s) require external data. + SKIPPED [1] lib/iris/tests/unit/fileformats/pp/test__interpret_field.py:97: Test(s) require external data. + SKIPPED [1] lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py:29: Test(s) require external data. + All Python decorators that skip tests will be defined in ``lib/iris/tests/__init__.py`` with a function name with a prefix of ``skip_``. +You can also run a specific test module. The example below runs the tests for +mapping:: + + cd lib/iris/tests + python test_mapping.py + +When running the test directly as above you can view the command line options +using the commands ``python test_mapping.py -h`` or +``python test_mapping.py --help``. + +.. tip:: A useful command line option to use is ``-d``. This will display + matplotlib_ figures as the tests are run. For example:: + + python test_mapping.py -d .. _using nox: diff --git a/docs/src/installing.rst b/docs/src/installing.rst index 33b15610fa..6a2d2f6131 100644 --- a/docs/src/installing.rst +++ b/docs/src/installing.rst @@ -119,9 +119,9 @@ Running the Tests To ensure your setup is configured correctly you can run the test suite using the command:: - python setup.py test + pytest -For more information see :ref:`developer_running_tests`. +For more information see :ref:`test manual env`. Custom Site Configuration From 56a97d66b8404f96ef61fe2d52433eb58373f338 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 19 Jul 2022 23:14:01 +0100 Subject: [PATCH 173/319] add missing skips (#4865) --- lib/iris/tests/test_load.py | 1 + lib/iris/tests/test_plot.py | 2 ++ lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py | 1 + 3 files changed, 4 insertions(+) diff --git a/lib/iris/tests/test_load.py b/lib/iris/tests/test_load.py index d21b40ee26..4749236abc 100644 --- a/lib/iris/tests/test_load.py +++ b/lib/iris/tests/test_load.py @@ -183,6 +183,7 @@ def new_load_http(passed_urls, *args, **kwargs): finally: iris.io.load_http = orig + @tests.skip_data def test_netCDF_Dataset_call(self): # Check that load_http calls netCDF4.Dataset and supplies the expected URL. diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py index 0c47bd6d3a..77aea2b6b6 100644 --- a/lib/iris/tests/test_plot.py +++ b/lib/iris/tests/test_plot.py @@ -342,6 +342,8 @@ def test_circular_changes(self): self.check_graphic() +@tests.skip_data +@tests.skip_plot class Test1dFillBetween(tests.GraphicsTest): def setUp(self): super().setUp() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py index a914dd3314..575c852ece 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py @@ -575,6 +575,7 @@ def test_nonmesh_dim(self): self.assertEqual(data_props["mesh"], mesh_name) self.assertEqual(data_props["location"], "face") + @tests.skip_data def test_nonmesh_hybrid_dim(self): # Check a case with a hybrid non-mesh dimension cube = realistic_4d() From 09fac5b0b8936a8f9a3ed705af422c442124729d Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 25 Jul 2022 08:33:04 +0100 Subject: [PATCH 174/319] Updated environment lockfiles (#4873) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 18 +++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 18 +++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 18 +++++++++--------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 4d43544236..cb6be0db64 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: b9e611fba7bc4bd71c8f4e36acf1dde0bd10352159237ea4d2e979ee66214d23 +# input_hash: 598e5811a7e12a4e68fde1ffdbc292fd650eda66b7c6231c25f3f71482002097 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -47,7 +47,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_2.tar.bz2#99c0160c84e61348aa8eb2949b24e6d3 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 @@ -100,7 +100,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -119,7 +119,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 @@ -160,7 +160,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e -https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.1-pyhd8ed1ab_0.tar.bz2#d821b295c4bd18ad27e1e19543a5784a +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 @@ -182,7 +182,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar. https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.3-py310hbf28c38_0.tar.bz2#93b57192dd2e93b6d5ed00be96e8ae55 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 @@ -207,7 +207,7 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py310hff52083 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py310h5764c6d_0.tar.bz2#c965e9e47e42b19d26994e848d2a40e6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 @@ -216,7 +216,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d -https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2-pyhd8ed1ab_0.tar.bz2#2e92a2c7eb81581410550bec3f28560d https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 @@ -230,7 +230,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_0.tar.bz2#b038b2e97ae14fea11159dcb2c5abf0a https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h947f774_100.tar.bz2#ebde0c4a610be54e818f5407ddc33af7 diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index c185089fa6..ec91d1c2b1 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e4bb2cf171924c5e31a2295669bea2f19b5a76f0ba3676dd535f044fe801255a +# input_hash: 5701e0b5b8d78ba450e4b3497a5e9edbd18b306eac6572cd4f65e550f39d9a4c @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -46,7 +46,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_2.tar.bz2#99c0160c84e61348aa8eb2949b24e6d3 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 @@ -99,7 +99,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -118,7 +118,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 @@ -159,7 +159,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bf https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e -https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.1-pyhd8ed1ab_0.tar.bz2#d821b295c4bd18ad27e1e19543a5784a +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 @@ -181,7 +181,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.3-py38h43d8883_0.tar.bz2#0719de23a2c5aa0b4db25ee34394e8f3 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 @@ -206,7 +206,7 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py38h578d9bd_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py38h0a891b7_0.tar.bz2#7ac14fa19454e00f50d9a39506bcc3c6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 @@ -215,7 +215,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007. https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe -https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2-pyhd8ed1ab_0.tar.bz2#2e92a2c7eb81581410550bec3f28560d https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 @@ -229,7 +229,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_0.tar.bz2#107af20136422bcabf9f1195f6262117 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38he1ab104_100.tar.bz2#ad4bc1e4ed59a1c1464f7b511f7bf74b diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 2843b1699b..433616561b 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 4e20df906742e42d00c1dc04af27bb5a312df25365015a497178d7ea36cce391 +# input_hash: e71685e652ffa68d39b3703490ab0ecbb40a73fdac940ab8c067fd4704286361 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -47,7 +47,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.2-h7f98852_1.tar.bz2#46cf26ecc8775a0aab300ea1821aaa3c +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_2.tar.bz2#99c0160c84e61348aa8eb2949b24e6d3 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 @@ -100,7 +100,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.1-h4ff8645_0.tar.bz2#6acda9d2a3ea84b58637b8f880bbf29b +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -119,7 +119,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.2-h3452ae3_0.tar.bz2#c363665b4aabe56aae4f8981cff5b153 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 @@ -160,7 +160,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e -https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.1-pyhd8ed1ab_0.tar.bz2#d821b295c4bd18ad27e1e19543a5784a +https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 @@ -182,7 +182,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.3-py39hf939315_0.tar.bz2#d8bdd7cb2f0e571665bbf0ec09c94da9 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c @@ -207,7 +207,7 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py39hf3d152e_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.0-pyhd8ed1ab_0.tar.bz2#3b533fc35efb54900c9e4ab06242f8b5 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py39hb9d737c_0.tar.bz2#7980ace37ccb3399672c3a9840e039ed https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 @@ -216,7 +216,7 @@ https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007. https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 -https://conda.anaconda.org/conda-forge/noarch/pip-22.1.2-pyhd8ed1ab_0.tar.bz2#d29185c662a424f8bea1103270b85c96 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2-pyhd8ed1ab_0.tar.bz2#2e92a2c7eb81581410550bec3f28560d https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 @@ -230,7 +230,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.1-pyhd8ed1ab_0.tar.bz2#6f41e3056fcd3061fbc2b49b3309fe0c +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_0.tar.bz2#ab1bcd0fd24e375f16d662e4cc783cab https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39hf5a3a3f_100.tar.bz2#e7f0560cfc4fdbd1fb4fc8a305410a44 From 1f7a4a70ed4ed9404e6457aa6b3a2c9581bd0387 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 27 Jul 2022 14:23:26 +0100 Subject: [PATCH 175/319] Replace testing helper function (#4878) * replace unmasked_data_as_1d_array * whatsnew --- docs/src/whatsnew/latest.rst | 3 ++- lib/iris/tests/__init__.py | 23 +++++------------------ 2 files changed, 7 insertions(+), 19 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 761833ba15..bd24b1c64c 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -245,7 +245,8 @@ This document explains the changes made to Iris for this release #. `@trexfeathers`_ and `@pp-mo`_ improved generation of stock NetCDF files. (:pull:`4827`, :pull:`4836`) -#. `@rcomer`_ removed some now redundant testing methods. (:pull:`4838`) +#. `@rcomer`_ removed some now redundant testing functions. (:pull:`4838`, + :pull:`4878`) #. `@bjlittle`_ and `@jamesp`_ (reviewer) and `@lbdreyer`_ (reviewer) extended the GitHub Continuous-Integration to cover testing on ``py38``, ``py39``, diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py index 75b7882221..5b99885874 100644 --- a/lib/iris/tests/__init__.py +++ b/lib/iris/tests/__init__.py @@ -588,31 +588,18 @@ def assertNoWarningsRegexp(self, expected_regexp=""): self.assertFalse(matches, msg) def _assertMaskedArray(self, assertion, a, b, strict, **kwargs): - # Define helper function to extract unmasked values as a 1d - # array. - def unmasked_data_as_1d_array(array): - array = ma.asarray(array) - if array.ndim == 0: - if array.mask: - data = np.array([]) - else: - data = np.array([array.data]) - else: - data = array.data[~ma.getmaskarray(array)] - return data - - # Compare masks. This will also check that the array shapes - # match, which is not tested when comparing unmasked values if - # strict is False. + # Compare masks. a_mask, b_mask = ma.getmaskarray(a), ma.getmaskarray(b) np.testing.assert_array_equal(a_mask, b_mask) if strict: + # Compare all data values. assertion(a.data, b.data, **kwargs) else: + # Compare only unmasked data values. assertion( - unmasked_data_as_1d_array(a), - unmasked_data_as_1d_array(b), + ma.compressed(a), + ma.compressed(b), **kwargs, ) From 2b3329f6272752722db89ea83fa331b83ee699ee Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 27 Jul 2022 16:58:21 +0100 Subject: [PATCH 176/319] Cube arithmetic array type handling (#3790) * add new tests; fix existing tests * force lhs lazy if rhs lazy * black * add in-place tests * add handling for unmasked, masked real array combo * add tests for false skeleton * rearrange _math_op_common if-loops * revert maths.py to upstream/master * simpler solution * reduce masking arithmetic tests * expand and improve tests * add whatsnew * fix rebase snafu * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update following numpy#21977 * Revert "update following numpy#21977" This reverts commit 7d62ad6b8779129b9f4c2f7d26f070f5402f95e9. * update comment * a before b Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 4 ++ lib/iris/analysis/maths.py | 14 +++++++ lib/iris/tests/test_basic_maths.py | 12 +++--- .../tests/unit/analysis/maths/__init__.py | 42 ++++++++++++++++--- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index bd24b1c64c..5383711072 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -128,6 +128,10 @@ This document explains the changes made to Iris for this release code comments, this was supposedly already the case, but there were several bugs and loopholes. (:issue:`1897`, :pull:`4767`) +#. `@rcomer`_ modified cube arithmetic to handle mismatches in the cube's data + array type. This prevents masks being lost in some cases and therefore + resolves :issue:`2987`. (:pull:`3790`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index 1cbc90cc60..94f01c5140 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -837,6 +837,20 @@ def unary_func(lhs): raise TypeError(emsg) return data + if in_place and not cube.has_lazy_data(): + # In-place arithmetic doesn't work if array type of LHS is less complex + # than RHS. + if iris._lazy_data.is_lazy_data(rhs): + cube.data = cube.lazy_data() + elif ma.is_masked(rhs) and not isinstance(cube.data, ma.MaskedArray): + cube.data = ma.array(cube.data) + + elif isinstance( + cube.core_data(), ma.MaskedArray + ) and iris._lazy_data.is_lazy_data(rhs): + # Workaround for #2987. numpy#15200 discusses the general problem. + cube = cube.copy(cube.lazy_data()) + result = _math_op_common( cube, unary_func, diff --git a/lib/iris/tests/test_basic_maths.py b/lib/iris/tests/test_basic_maths.py index 24f2b89442..6c08dc1f9e 100644 --- a/lib/iris/tests/test_basic_maths.py +++ b/lib/iris/tests/test_basic_maths.py @@ -687,12 +687,12 @@ def setUp(self): self.data_1u = np.array([[9, 9, 9], [8, 8, 8]], dtype=np.uint64) self.data_2u = np.array([[3, 3, 3], [2, 2, 2]], dtype=np.uint64) - self.cube_1f = Cube(self.data_1f) - self.cube_2f = Cube(self.data_2f) - self.cube_1i = Cube(self.data_1i) - self.cube_2i = Cube(self.data_2i) - self.cube_1u = Cube(self.data_1u) - self.cube_2u = Cube(self.data_2u) + self.cube_1f = Cube(self.data_1f.copy()) + self.cube_2f = Cube(self.data_2f.copy()) + self.cube_1i = Cube(self.data_1i.copy()) + self.cube_2i = Cube(self.data_2i.copy()) + self.cube_1u = Cube(self.data_1u.copy()) + self.cube_2u = Cube(self.data_2u.copy()) self.ops = (operator.add, operator.sub, operator.mul, operator.truediv) self.iops = ( diff --git a/lib/iris/tests/unit/analysis/maths/__init__.py b/lib/iris/tests/unit/analysis/maths/__init__.py index 521c65a7eb..311da8a0e6 100644 --- a/lib/iris/tests/unit/analysis/maths/__init__.py +++ b/lib/iris/tests/unit/analysis/maths/__init__.py @@ -12,6 +12,7 @@ from abc import ABCMeta, abstractmethod import operator +import dask.array as da import numpy as np from numpy import ma @@ -201,18 +202,22 @@ def cube_func(self): # I.E. 'iris.analysis.maths.xx'. pass - def _test_partial_mask(self, in_place): + def _test_partial_mask(self, in_place, second_lazy=False): # Helper method for masked data tests. dat_a = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 0, 1, 0]) dat_b = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 1, 0, 0]) + if second_lazy: + cube_b = Cube(da.from_array(dat_b)) + else: + cube_b = Cube(dat_b) + cube_a = Cube(dat_a) - cube_b = Cube(dat_b) - com = self.data_op(dat_b, dat_a) - res = self.cube_func(cube_b, cube_a, in_place=in_place) + com = self.data_op(dat_a, dat_b) + res = self.cube_func(cube_a, cube_b, in_place=in_place) - return com, res, cube_b + return com, res, cube_a def test_partial_mask_in_place(self): # Cube in_place arithmetic operation. @@ -221,13 +226,38 @@ def test_partial_mask_in_place(self): self.assertMaskedArrayEqual(com, res.data, strict=True) self.assertIs(res, orig_cube) + def test_partial_mask_second_lazy_in_place(self): + # Only second cube has lazy data. + com, res, orig_cube = self._test_partial_mask(True, second_lazy=True) + self.assertMaskedArrayEqual(com, res.data, strict=True) + self.assertIs(res, orig_cube) + def test_partial_mask_not_in_place(self): # Cube arithmetic not an in_place operation. com, res, orig_cube = self._test_partial_mask(False) - self.assertMaskedArrayEqual(com, res.data) + self.assertMaskedArrayEqual(com, res.data, strict=True) self.assertIsNot(res, orig_cube) + def test_partial_mask_second_lazy_not_in_place(self): + # Only second cube has lazy data. + com, res, orig_cube = self._test_partial_mask(False, second_lazy=True) + self.assertMaskedArrayEqual(com, res.data, strict=True) + self.assertIsNot(res, orig_cube) + + def test_in_place_introduces_mask(self): + # If second cube is masked, result should also be masked. + data1 = np.arange(4, dtype=np.float) + data2 = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 1, 0, 0]) + cube1 = Cube(data1) + cube2 = Cube(data2) + + com = self.data_op(data1, data2) + res = self.cube_func(cube1, cube2, in_place=True) + + self.assertMaskedArrayEqual(com, res.data, strict=True) + self.assertIs(res, cube1) + class CubeArithmeticCoordsTest(tests.IrisTest): # This class sets up pairs of cubes to test iris' ability to reject From f3a72340fbf439291beb5cdaec94d0ae0c672770 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 29 Jul 2022 11:21:51 +0100 Subject: [PATCH 177/319] automate sdist and wheel build and publish (#4849) * automate sdist and wheel build and publish * update names * tidy package manifest and discovery * add note to dev docs * update MANIFEST.in * Update .github/workflows/ci-wheels.yml Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * add unshallow comment to rtd * reinstate docs release * add pypa build comment * test wheel * added whatnew + tweak nox wheel emsgs * Update docs/src/developers_guide/release.rst Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- .github/workflows/ci-tests.yml | 2 +- .github/workflows/ci-wheels.yml | 166 ++++++++++++++++++++++++++ .readthedocs.yml | 3 + MANIFEST.in | 18 ++- docs/src/conf.py | 2 + docs/src/developers_guide/release.rst | 8 ++ docs/src/whatsnew/latest.rst | 3 + noxfile.py | 30 +++++ setup.cfg | 2 +- 9 files changed, 221 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/ci-wheels.yml diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 4cdc3f5e94..2657e1e42a 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -3,7 +3,7 @@ # - https://github.com/actions/checkout # - https://github.com/marketplace/actions/setup-miniconda -name: ci-tests +name: ci-tests on: push: diff --git a/.github/workflows/ci-wheels.yml b/.github/workflows/ci-wheels.yml new file mode 100644 index 0000000000..265489883f --- /dev/null +++ b/.github/workflows/ci-wheels.yml @@ -0,0 +1,166 @@ +# Reference: +# - https://github.com/actions/checkout +# - https://github.com/actions/download-artifact +# - https://github.com/actions/upload-artifact +# - https://github.com/pypa/build +# - https://github.com/pypa/gh-action-pypi-publish +# - https://test.pypi.org/help/#apitoken + +name: ci-wheels + +on: + pull_request: + + push: + tags: + - "v*" + branches-ignore: + - "auto-update-lockfiles" + - "pre-commit-ci-update-config" + - "dependabot/*" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: "build sdist & wheel" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: "building" + shell: bash + run: | + # require build with explicit --sdist and --wheel in order to + # get correct version associated with sdist and bdist artifacts + pipx run build --sdist --wheel + + - uses: actions/upload-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist/* + + test-wheel: + needs: build + name: "test wheel (py${{ matrix.python-version }})" + runs-on: ubuntu-latest + defaults: + run: + shell: bash -l {0} + strategy: + fail-fast: false + matrix: + python-version: ["3.8", "3.9", "3.10"] + session: ["wheel"] + env: + ENV_NAME: "ci-wheels" + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - name: "environment configure" + env: + # Maximum cache period (in weeks) before forcing a cache refresh. + CACHE_WEEKS: 2 + run: | + echo "CACHE_PERIOD=$(date +%Y).$(expr $(date +%U) / ${CACHE_WEEKS})" >> ${GITHUB_ENV} + echo "LOCK_FILE=requirements/ci/nox.lock/py$(echo ${{ matrix.python-version }} | tr -d '.')-linux-64.lock" >> ${GITHUB_ENV} + + - name: "conda package cache" + uses: ./.github/workflows/composite/conda-pkg-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + + - name: "conda install" + uses: conda-incubator/setup-miniconda@v2 + with: + miniforge-version: latest + channels: conda-forge,defaults + activate-environment: ${{ env.ENV_NAME }} + auto-update-conda: false + use-only-tar-bz2: true + + - name: "conda environment cache" + uses: ./.github/workflows/composite/conda-env-cache + with: + cache_build: 0 + cache_period: ${{ env.CACHE_PERIOD }} + env_name: ${{ env.ENV_NAME }} + install_packages: "nox pip" + + - name: "nox cache" + uses: ./.github/workflows/composite/nox-cache + with: + cache_build: 0 + env_name: ${{ env.ENV_NAME }} + lock_file: ${{ env.LOCK_FILE }} + + - name: "nox install and test wheel" + env: + PY_VER: ${{ matrix.python-version }} + run: | + nox --session ${{ matrix.session }} -- --verbose + + show-artifacts: + needs: build + name: "show artifacts" + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - shell: bash + run: | + ls -l ${{ github.workspace }}/dist + + publish-artifacts-test-pypi: + needs: test-wheel + name: "publish to test.pypi" + runs-on: ubuntu-latest + # upload to Test PyPI for every commit on main branch + if: github.event_name == 'push' && github.event.ref == 'refs/heads/main' + steps: + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + skip_existing: true + print_hash: true + + publish-artifacts-pypi: + needs: test-wheel + name: "publish to pypi" + runs-on: ubuntu-latest + # upload to PyPI for every tag starting with 'v' + if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') + steps: + - uses: actions/download-artifact@v3 + with: + name: pypi-artifacts + path: ${{ github.workspace }}/dist + + - uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_API_TOKEN }} + print_hash: true \ No newline at end of file diff --git a/.readthedocs.yml b/.readthedocs.yml index 8ec8ab145c..58d5b26769 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -6,6 +6,9 @@ build: python: mambaforge-4.10 jobs: post_checkout: + # The SciTools/iris repository is shallow i.e., has a .git/shallow, + # therefore complete the repository with a full history in order + # to allow setuptools-scm to correctly auto-discover the version. - git fetch --unshallow - git fetch --all diff --git a/MANIFEST.in b/MANIFEST.in index 81d7165199..ad28df9c7c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,15 +1,13 @@ # Top-level files include CHANGES COPYING COPYING.LESSER +prune .github +exclude .gitignore -# Files from setup.py package_data that are not automatically added to source distributions -recursive-include lib/iris/tests/results *.cml *.cdl *.txt *.xml *.json -recursive-include lib/iris/etc * -include lib/iris/tests/stock/file_headers/* - +# Files required for conda package management recursive-include requirements * -# File required to build docs -recursive-include docs Makefile *.js *.png *.py *.rst +# Files required to build docs +recursive-include docs * prune docs/src/_build prune docs/src/generated prune docs/gallery_tests @@ -18,7 +16,5 @@ prune docs/gallery_tests include tools/generate_std_names.py include etc/cf-standard-name-table.xml -global-exclude *.pyc -global-exclude __pycache__ -global-exclude iris_image_test_output -exclude lib/iris/_version.py \ No newline at end of file +global-exclude *.py[cod] +global-exclude __pycache__ \ No newline at end of file diff --git a/docs/src/conf.py b/docs/src/conf.py index 4f1eae7403..6b11e83a8c 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -89,7 +89,9 @@ def autolog(message): version = get_version("scitools-iris") if version.endswith("+dirty"): version = version[: -len("+dirty")] +release = version autolog(f"Iris Version = {version}") +autolog(f"Iris Release = {release}") # -- General configuration --------------------------------------------------- diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index 182ab482b3..25a426e20b 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -123,6 +123,14 @@ conda package on the `conda-forge Anaconda channel`_. Update PyPI ----------- +.. note:: + + As part of our Continuous-Integration (CI), the building and publishing of + PyPI artifacts is now automated by a dedicated GitHub Action. + + The following instructions **no longer** require to be performed manually, + but remain part of the documentation for reference purposes only. + Update the `scitools-iris`_ project on PyPI with the latest Iris release. To do this perform the following steps. diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 5383711072..fddf646c01 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -259,6 +259,9 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ and `@trexfeathers`_ (reviewer) adopted `setuptools-scm`_ for automated ``iris`` package versioning. (:pull:`4841`) +#. `@bjlittle`_ and `@trexfeathers`_ (reviewer) added building, testing and + publishing of ``iris`` PyPI ``sdist`` and binary ``wheels`` as part of + our GitHub Continuous-Integration. (:pull`4849`) .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/noxfile.py b/noxfile.py index 2b1df8fb00..8aabf862fb 100755 --- a/noxfile.py +++ b/noxfile.py @@ -271,6 +271,36 @@ def linkcheck(session: nox.sessions.Session): ) +@nox.session(python=PY_VER, venv_backend="conda") +def wheel(session: nox.sessions.Session): + """ + Perform iris local wheel install and import test. + + Parameters + ---------- + session: object + A `nox.sessions.Session` object. + + """ + prepare_venv(session) + session.cd("dist") + fname = list(Path(".").glob("scitools_iris-*.whl")) + if len(fname) == 0: + raise ValueError("Cannot find wheel to install.") + if len(fname) > 1: + emsg = ( + f"Expected to find 1 wheel to install, found {len(fname)} instead." + ) + raise ValueError(emsg) + session.install(fname[0].name) + session.run( + "python", + "-c", + "import iris; print(f'{iris.__version__=}')", + external=True, + ) + + @nox.session @nox.parametrize( "run_type", diff --git a/setup.cfg b/setup.cfg index 66e865572e..a60d107835 100644 --- a/setup.cfg +++ b/setup.cfg @@ -54,7 +54,7 @@ install_requires = numpy>=1.19 scipy xxhash -packages = find: +packages = find_namespace: package_dir = =lib python_requires = From 22854da109b7ab0e29d30edc500a3c6d4303774f Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 29 Jul 2022 15:42:02 +0100 Subject: [PATCH 178/319] Warn users of increased `recombine_submeshes` memory demand. (#4881) * Warn users of increased recombine_submeshes memory demand. * Clearer what's new entry. --- docs/src/whatsnew/latest.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index fddf646c01..3c6391d0ba 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -145,8 +145,8 @@ This document explains the changes made to Iris for this release code that explicitly checks the calendar attribute. (:pull:`4847`) -🚀 Performance Enhancements -=========================== +🚀 Performance +============== #. `@wjbenfold`_ added caching to the calculation of the points array in a :class:`~iris.coords.DimCoord` created using @@ -166,6 +166,10 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ improved the speed of linear interpolation using :meth:`iris.analysis.trajectory.interpolate` (:pull:`4366`) +#. NumPy ``v1.23`` behaviour changes mean that + :func:`iris.experimental.ugrid.utils.recombine_submeshes` now uses ~3x as + much memory; testing shows a ~16-million point mesh will now use ~600MB. + Investigated by `@pp-mo` and `@trexfeathers`. (:issue:`4845`) 🔥 Deprecations From 597be8ab3cc1d6a21103d254b04a0714b192e43b Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 1 Aug 2022 21:30:36 +0100 Subject: [PATCH 179/319] Sphinx reset modules (#4885) * fix sphinx-gallery * change gallery index reference --- docs/src/conf.py | 5 +++++ .../src/developers_guide/contributing_documentation_full.rst | 2 +- requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.cfg | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 6b11e83a8c..082d7eb384 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -368,6 +368,11 @@ def _dotv(version): "ignore_pattern": r"__init__\.py", # force gallery building, unless overridden (see src/Makefile) "plot_gallery": "'True'", + # force re-registering of nc-time-axis with matplotlib for each example, + # required for sphinx-gallery>=0.11.0 + "reset_modules": ( + lambda gallery_conf, fname: sys.modules.pop("nc_time_axis", None), + ), } # ----------------------------------------------------------------------------- diff --git a/docs/src/developers_guide/contributing_documentation_full.rst b/docs/src/developers_guide/contributing_documentation_full.rst index 46f9c563d1..a98ff0a7e9 100755 --- a/docs/src/developers_guide/contributing_documentation_full.rst +++ b/docs/src/developers_guide/contributing_documentation_full.rst @@ -147,7 +147,7 @@ can exclude the module from the API documentation. Add the entry to the Gallery ~~~~~~~ -The Iris :ref:`sphx_glr_generated_gallery` uses a sphinx extension named +The Iris :ref:`gallery_index` uses a sphinx extension named `sphinx-gallery `_ that auto generates reStructuredText (rst) files based upon a gallery source directory that abides directory and filename convention. diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 19d4e90c22..5ba9988c92 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -45,6 +45,6 @@ dependencies: - sphinx - sphinxcontrib-napoleon - sphinx-copybutton - - sphinx-gallery + - sphinx-gallery >=0.11.0 - sphinx-panels - pydata-sphinx-theme = 0.8.1 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 55a64676bf..016a986d9f 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -45,6 +45,6 @@ dependencies: - sphinx - sphinxcontrib-napoleon - sphinx-copybutton - - sphinx-gallery + - sphinx-gallery >=0.11.0 - sphinx-panels - pydata-sphinx-theme = 0.8.1 diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index abd11e48a2..171d2a88e3 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -45,6 +45,6 @@ dependencies: - sphinx - sphinxcontrib-napoleon - sphinx-copybutton - - sphinx-gallery + - sphinx-gallery >=0.11.0 - sphinx-panels - pydata-sphinx-theme = 0.8.1 diff --git a/setup.cfg b/setup.cfg index a60d107835..2fe615e72a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,7 +68,7 @@ where = lib docs = sphinx sphinx-copybutton - sphinx-gallery + sphinx-gallery>=0.11.0 sphinx_rtd_theme sphinxcontrib-napoleon sphinx-panels From caad2566c015936b347c3056fdc8d97f188048e4 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 21:41:59 +0100 Subject: [PATCH 180/319] Updated environment lockfiles (#4884) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 40 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 40 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 40 ++++++++++---------- 3 files changed, 60 insertions(+), 60 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index cb6be0db64..ecf81b28c9 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -32,7 +32,7 @@ https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.t https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 -https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e +https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 @@ -42,7 +42,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_1.tar.bz2#759c6f385ca4110f5fb185d404d306a3 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -96,7 +96,7 @@ https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openb https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 @@ -128,7 +128,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0. https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d +https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_0.tar.bz2#b10c888ee1372e73d6d57a40d3f8e490 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -139,9 +139,9 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 @@ -155,7 +155,7 @@ https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#82 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc -https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 @@ -184,7 +184,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar. https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_1.tar.bz2#54eaedc01b1aee271e4f150d03ffc8b0 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py310h53a5b5f_0.tar.bz2#9b86a46d908354fe7f91da24f5ea3f36 @@ -203,38 +203,38 @@ https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py310hff52083_ https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py310hff52083_0.tar.bz2#7f6c48710ee99edfa3dfa0b54fa6f020 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.2-py310hff52083_0.tar.bz2#54ed6fbd5c15fe0ed64429a5b35e3baa https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py310h5764c6d_0.tar.bz2#c965e9e47e42b19d26994e848d2a40e6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.0.1-hf9f4e7c_0.tar.bz2#e63a748f2f13b2b1e034ff9f5917edaa https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_hd09bd1e_1.tar.bz2#f3fd1799efae42bf8a4a77251f3319a1 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d -https://conda.anaconda.org/conda-forge/noarch/pip-22.2-pyhd8ed1ab_0.tar.bz2#2e92a2c7eb81581410550bec3f28560d +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.1-pyhd8ed1ab_0.tar.bz2#0cbcd1fa7291fe72dc8f848907510498 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310h7612f91_0.tar.bz2#14a7ea0620e4c0801bee756171f4dc03 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310hdfbd76f_2.tar.bz2#0f394bf0f45f1a9b036fa9af374dcdad https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h5e49deb_3.tar.bz2#5305d80a31c7db98765b52ea95521ff6 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h863fc68_101.tar.bz2#97848ee3f2b01f6d99a43b729c1e89ef https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_0.tar.bz2#b038b2e97ae14fea11159dcb2c5abf0a -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h947f774_100.tar.bz2#ebde0c4a610be54e818f5407ddc33af7 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_1.tar.bz2#30705ca3f166305e268ae10d2d703879 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hc4f8a73_1.tar.bz2#302c545886fc6438adaf13bac64e5188 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f @@ -247,12 +247,12 @@ https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_0.tar.bz2#0b90a9f77544f943264d47677d63ac9e +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_1.tar.bz2#ac5726cced21482d6b817921a441d6a2 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index ec91d1c2b1..7afc079278 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -31,7 +31,7 @@ https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.t https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 -https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e +https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 @@ -41,7 +41,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_1.tar.bz2#759c6f385ca4110f5fb185d404d306a3 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -95,7 +95,7 @@ https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openb https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 @@ -127,7 +127,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0. https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d +https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_0.tar.bz2#b10c888ee1372e73d6d57a40d3f8e490 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -138,9 +138,9 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 @@ -154,7 +154,7 @@ https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#82 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc -https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 @@ -183,7 +183,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar. https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py38h3a7f9d9_0.tar.bz2#90cf44c14b2bfe19ce7b875979b90cb9 @@ -202,38 +202,38 @@ https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py38h578d9bd_0 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py38h578d9bd_0.tar.bz2#0a703d05848de7738a1466b5e0a99274 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.2-py38h578d9bd_0.tar.bz2#a01038c7c46c4a52fba1645c17a8c8cb https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py38h0a891b7_0.tar.bz2#7ac14fa19454e00f50d9a39506bcc3c6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.0.1-hf9f4e7c_0.tar.bz2#e63a748f2f13b2b1e034ff9f5917edaa https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_hd09bd1e_1.tar.bz2#f3fd1799efae42bf8a4a77251f3319a1 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe -https://conda.anaconda.org/conda-forge/noarch/pip-22.2-pyhd8ed1ab_0.tar.bz2#2e92a2c7eb81581410550bec3f28560d +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.1-pyhd8ed1ab_0.tar.bz2#0cbcd1fa7291fe72dc8f848907510498 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38h1ee437e_0.tar.bz2#a0a8bc19d491ec659a534c9a11cf74a0 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38hea3f02b_2.tar.bz2#71f2dd6099c193da621ead62efba14a9 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h3b45516_3.tar.bz2#b2415a314858f4232aab437bc27803fc https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h863fc68_101.tar.bz2#97848ee3f2b01f6d99a43b729c1e89ef https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_0.tar.bz2#107af20136422bcabf9f1195f6262117 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38he1ab104_100.tar.bz2#ad4bc1e4ed59a1c1464f7b511f7bf74b -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_1.tar.bz2#2726cea4b977031563cb3d2ae813a695 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h32db9c8_101.tar.bz2#d1451d40c8204594cdcf156363128000 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hc4f8a73_1.tar.bz2#302c545886fc6438adaf13bac64e5188 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f @@ -246,12 +246,12 @@ https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_0.tar.bz2#b15039e7f67b5f91c35f9b6d27c2775c +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_1.tar.bz2#8f00ce2589f003a6c796e23073765602 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 433616561b..dd824baa39 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -32,7 +32,7 @@ https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.t https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 -https://conda.anaconda.org/conda-forge/linux-64/lerc-3.0-h9c3ff4c_0.tar.bz2#7fcefde484980d23f0ec24c11e314d2e +https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 @@ -42,7 +42,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_0.tar.bz2#9b6d0781953c9e353faee494336cc229 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_1.tar.bz2#759c6f385ca4110f5fb185d404d306a3 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -96,7 +96,7 @@ https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openb https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-hc85c160_1.tar.bz2#151f9fae3ab50f039c8735e47770aa2d +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 @@ -128,7 +128,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0. https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/noarch/attrs-21.4.0-pyhd8ed1ab_0.tar.bz2#f70280205d7044c8b8358c8de3190e5d +https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_0.tar.bz2#b10c888ee1372e73d6d57a40d3f8e490 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -139,9 +139,9 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.5.0-pyhd8ed1ab_0.tar.bz2#db4ffc615663c66a9cc0869ce4d1092b +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.1-mpi_mpich_h08b82f9_4.tar.bz2#975d5635b158c1b3c5c795f9d0a430a1 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 @@ -155,7 +155,7 @@ https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#82 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc -https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.0-pyhd8ed1ab_0.tar.bz2#0158f62cae46ad1fb77c522c4e7ecc90 +https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 @@ -184,7 +184,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar. https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcdf9059_2.tar.bz2#7c035ca8a06010c4d9730c428d1a5969 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_1.tar.bz2#a0475f4b801777d641058ca5ff08f07f https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py39hba7629e_0.tar.bz2#ee8dff1fb28e0e2c458e845aae9d915a @@ -203,38 +203,38 @@ https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py39hf3d152e_0 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.15.1-py39hf3d152e_0.tar.bz2#59361d2352ad38bbcec48f9275eff7d5 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.2-py39hf3d152e_0.tar.bz2#6d14433ac4d61370f267d2cf6b583b9c https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py39hb9d737c_0.tar.bz2#7980ace37ccb3399672c3a9840e039ed https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-4.4.1-hf9f4e7c_0.tar.bz2#ac83998fdc71721da8c8f0846a25a503 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.0.1-hf9f4e7c_0.tar.bz2#e63a748f2f13b2b1e034ff9f5917edaa https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_h1364a43_0.tar.bz2#b6ba4f487ef9fd5d353ff277df06d133 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_hd09bd1e_1.tar.bz2#f3fd1799efae42bf8a4a77251f3319a1 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2-pyhd8ed1ab_0.tar.bz2#2e92a2c7eb81581410550bec3f28560d +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.1-pyhd8ed1ab_0.tar.bz2#0cbcd1fa7291fe72dc8f848907510498 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39he49c0e8_0.tar.bz2#b1b4cc4216e555168e88d6a2b1914af1 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39h8ba3f38_2.tar.bz2#a072f694938815973930e93273674231 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h68ae834_3.tar.bz2#2800d4dabd586cf203d8f1271a6c9e9d https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h4975321_100.tar.bz2#56f5c650937b1667ad0a557a0dff3bc4 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h863fc68_101.tar.bz2#97848ee3f2b01f6d99a43b729c1e89ef https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_0.tar.bz2#ab1bcd0fd24e375f16d662e4cc783cab -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39hf5a3a3f_100.tar.bz2#e7f0560cfc4fdbd1fb4fc8a305410a44 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hbd2fdc8_0.tar.bz2#e76dcd4a0efe0c037929f98c2fd87e10 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_1.tar.bz2#b8d1b075536dfca33459870dbb824886 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h71b8e10_101.tar.bz2#91e01aa93a2bcca96c9d64d2ce4f65f0 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hc4f8a73_1.tar.bz2#302c545886fc6438adaf13bac64e5188 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f @@ -247,12 +247,12 @@ https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.ta https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.10-pyhd8ed1ab_0.tar.bz2#14f22c5b9cfd0d93c2806faaa3fe6dec +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py39hf3d152e_0.tar.bz2#d65d073d186977a2a9a9d5a68b2b77ef +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py39hf3d152e_1.tar.bz2#7a3ff2d1e66e674631a65ea32d5cdbbd https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.10.1-pyhd8ed1ab_0.tar.bz2#4918585fe5e5341740f7e63c61743efb +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a From b6b54cff64b19f82b1c12c1af608fd99a01bc977 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 21:42:47 +0100 Subject: [PATCH 181/319] [pre-commit.ci] pre-commit autoupdate (#4886) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 4.0.1 → 5.0.2](https://github.com/PyCQA/flake8/compare/4.0.1...5.0.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 855bbe70a7..014268201f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: args: [--config=./pyproject.toml, .] - repo: https://github.com/PyCQA/flake8 - rev: 4.0.1 + rev: 5.0.2 hooks: - id: flake8 types: [file, python] From 08fe9c1fb18b753ee1293929db0435d58bdd31d3 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 3 Aug 2022 15:18:49 +0100 Subject: [PATCH 182/319] fix percentile docstring (#4888) --- lib/iris/analysis/__init__.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 25697266fa..9d8392cd95 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -1975,31 +1975,29 @@ def interp_order(length): A :class:`~iris.analysis.PercentileAggregator` instance that calculates the percentile over a :class:`~iris.cube.Cube`, as computed by :func:`scipy.stats.mstats.mquantiles` (default) or :func:`numpy.percentile` (if -fast_percentile_method is True). +``fast_percentile_method`` is True). -**Required** kwargs associated with the use of this aggregator: +Parameters +---------- percent : float or sequence of floats Percentile rank/s at which to extract value/s. -Additional kwargs associated with the use of this aggregator: - -alphap : float +alphap : float, default=1 Plotting positions parameter, see :func:`scipy.stats.mstats.mquantiles`. - Defaults to 1. -betap : float +betap : float, default=1 Plotting positions parameter, see :func:`scipy.stats.mstats.mquantiles`. - Defaults to 1. -fast_percentile_method : bool +fast_percentile_method : bool, default=False When set to True, uses :func:`numpy.percentile` method as a faster alternative to the :func:`scipy.stats.mstats.mquantiles` method. An exception is raised if the data are masked and the missing data tolerance - is not 0. Defaults to False. + is not 0. -kwargs : dict, optional +**kwargs : dict, optional Passed to :func:`scipy.stats.mstats.mquantiles` or :func:`numpy.percentile`. -**For example**: +Example +------- To compute the 10th and 90th percentile over *time*:: From 7a5e68d52f7209d9321ebd5db0e63d753898beda Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Aug 2022 21:57:28 +0100 Subject: [PATCH 183/319] [pre-commit.ci] pre-commit autoupdate (#4895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 5.0.2 → 5.0.4](https://github.com/PyCQA/flake8/compare/5.0.2...5.0.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 014268201f..ab7e64b6a0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: args: [--config=./pyproject.toml, .] - repo: https://github.com/PyCQA/flake8 - rev: 5.0.2 + rev: 5.0.4 hooks: - id: flake8 types: [file, python] From 14cf56ab7de3e3fbad1a2cccd5857f4cecf31d5d Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 9 Aug 2022 11:48:52 +0100 Subject: [PATCH 184/319] Upgrade `iris.util.mask_cube` (#4889) * rewrite mask_cube * fix import problems * simplify _unmask_mask and its tests * parametrize not in place 1d _mask_array * pytest in-place _mask_array tests * pytest _mask_array broadcasting tests * remove iris.tests from test__mask_array * attempt to preserve metadata * pull masked array assertions off test class * pytestify test__unmask_mask.py * remove _unmask_mask; tweak test * numpydoc docstring * add CML checks * preserve cube.metadata; add final CML * path handling * get hold of _mask_array result Co-authored-by: Bill Little * more thorough in_place check Co-authored-by: Bill Little * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * review actions * whatsnew Co-authored-by: Bill Little Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 9 +- lib/iris/analysis/maths.py | 11 +- lib/iris/common/metadata.py | 4 +- lib/iris/tests/__init__.py | 133 +++++++------- .../mask_cube_2d_create_new_dim.cml | 23 +++ .../mask_cube/original_cube_full2d_global.cml | 123 +++++++++++++ .../mask_cube/original_cube_simple_1d.cml | 22 +++ .../mask_cube/original_cube_simple_2d.cml | 20 ++ lib/iris/tests/test_analysis.py | 4 +- lib/iris/tests/unit/util/test__mask_array.py | 173 ++++++++++++++++++ lib/iris/tests/unit/util/test_mask_cube.py | 158 +++++++++++++++- lib/iris/util.py | 126 +++++++++++-- 12 files changed, 717 insertions(+), 89 deletions(-) create mode 100644 lib/iris/tests/results/unit/util/mask_cube/TestCubeMask/mask_cube_2d_create_new_dim.cml create mode 100644 lib/iris/tests/results/unit/util/mask_cube/original_cube_full2d_global.cml create mode 100644 lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_1d.cml create mode 100644 lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_2d.cml create mode 100644 lib/iris/tests/unit/util/test__mask_array.py diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 3c6391d0ba..d7febb9e0e 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -76,6 +76,10 @@ This document explains the changes made to Iris for this release :func:`iris.plot.fill_between` and :func:`iris.quickplot.fill_between`. (:issue:`3493`, :pull:`4647`) +#. `@rcomer`_ and `@bjlittle`_ (reviewer) re-wrote :func:`iris.util.mask_cube` + to provide lazy evaluation and greater flexibility with respect to input types. + (:issue:`3936`, :pull:`4889`) + 🐛 Bugs Fixed ============= @@ -132,6 +136,9 @@ This document explains the changes made to Iris for this release array type. This prevents masks being lost in some cases and therefore resolves :issue:`2987`. (:pull:`3790`) +#. `@rcomer`_ and `@bjlittle`_ (reviewer) modified :func:`iris.util.mask_cube` so it + either works in place or returns a new cube (:issue:`3717`, :pull:`4889`) + 💣 Incompatible Changes ======================= @@ -265,7 +272,7 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ and `@trexfeathers`_ (reviewer) added building, testing and publishing of ``iris`` PyPI ``sdist`` and binary ``wheels`` as part of - our GitHub Continuous-Integration. (:pull`4849`) + our GitHub Continuous-Integration. (:pull:`4849`) .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index 94f01c5140..468847bca2 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -774,6 +774,7 @@ def _binary_op_common( new_dtype=None, dim=None, in_place=False, + sanitise_metadata=True, ): """ Function which shares common code between binary operations. @@ -792,6 +793,8 @@ def _binary_op_common( coordinate that is not found in `cube` in_place - whether or not to apply the operation in place to `cube` and `cube.data` + sanitise_metadata - whether or not to remove metadata using + _sanitise_metadata function """ from iris.cube import Cube @@ -858,13 +861,15 @@ def unary_func(lhs): new_dtype=new_dtype, in_place=in_place, skeleton_cube=skeleton_cube, + sanitise_metadata=sanitise_metadata, ) if isinstance(other, Cube): # Insert the resultant data from the maths operation # within the resolved cube. result = resolver.cube(result.core_data(), in_place=in_place) - _sanitise_metadata(result, new_unit) + if sanitise_metadata: + _sanitise_metadata(result, new_unit) return result @@ -946,6 +951,7 @@ def _math_op_common( new_dtype=None, in_place=False, skeleton_cube=False, + sanitise_metadata=True, ): from iris.cube import Cube @@ -979,7 +985,8 @@ def _math_op_common( ): new_cube.data = ma.masked_array(0, 1, dtype=new_dtype) - _sanitise_metadata(new_cube, new_unit) + if sanitise_metadata: + _sanitise_metadata(new_cube, new_unit) return new_cube diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index cb5f53f5f4..8ec39bb4b1 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -44,8 +44,6 @@ # https://www.unidata.ucar.edu/software/netcdf/docs/netcdf_data_set_components.html#object_name -from ..util import guess_coord_axis - _TOKEN_PARSE = re.compile(r"""^[a-zA-Z0-9][\w\.\+\-@]*$""") # Configure the logger. @@ -1413,6 +1411,8 @@ def metadata_filter( to only those that matched the given criteria. """ + from ..util import guess_coord_axis + name = None obj = None diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py index 5b99885874..a556cb6231 100644 --- a/lib/iris/tests/__init__.py +++ b/lib/iris/tests/__init__.py @@ -141,6 +141,73 @@ def main(): unittest.main() +def _assert_masked_array(assertion, a, b, strict, **kwargs): + # Compare masks. + a_mask, b_mask = ma.getmaskarray(a), ma.getmaskarray(b) + np.testing.assert_array_equal(a_mask, b_mask) + + if strict: + # Compare all data values. + assertion(a.data, b.data, **kwargs) + else: + # Compare only unmasked data values. + assertion( + ma.compressed(a), + ma.compressed(b), + **kwargs, + ) + + +def assert_masked_array_equal(a, b, strict=False): + """ + Check that masked arrays are equal. This requires the + unmasked values and masks to be identical. + + Args: + + * a, b (array-like): + Two arrays to compare. + + Kwargs: + + * strict (bool): + If True, perform a complete mask and data array equality check. + If False (default), the data array equality considers only unmasked + elements. + + """ + _assert_masked_array(np.testing.assert_array_equal, a, b, strict) + + +def assert_masked_array_almost_equal(a, b, decimal=6, strict=False): + """ + Check that masked arrays are almost equal. This requires the + masks to be identical, and the unmasked values to be almost + equal. + + Args: + + * a, b (array-like): + Two arrays to compare. + + Kwargs: + + * strict (bool): + If True, perform a complete mask and data array equality check. + If False (default), the data array equality considers only unmasked + elements. + + * decimal (int): + Equality tolerance level for + :meth:`numpy.testing.assert_array_almost_equal`, with the meaning + 'abs(desired-actual) < 0.5 * 10**(-decimal)' + + """ + _assert_masked_array( + np.testing.assert_array_almost_equal, a, b, strict, decimal=decimal + ) + + class IrisTest_nometa(unittest.TestCase): """A subclass of unittest.TestCase which provides Iris specific testing functionality.""" @@ -587,72 +654,14 @@ def assertNoWarningsRegexp(self, expected_regexp=""): msg = msg.format(expected_regexp, matches) self.assertFalse(matches, msg) - def _assertMaskedArray(self, assertion, a, b, strict, **kwargs): - # Compare masks. - a_mask, b_mask = ma.getmaskarray(a), ma.getmaskarray(b) - np.testing.assert_array_equal(a_mask, b_mask) - - if strict: - # Compare all data values. - assertion(a.data, b.data, **kwargs) - else: - # Compare only unmasked data values. - assertion( - ma.compressed(a), - ma.compressed(b), - **kwargs, - ) - - def assertMaskedArrayEqual(self, a, b, strict=False): - """ - Check that masked arrays are equal. This requires the - unmasked values and masks to be identical. - - Args: - - * a, b (array-like): - Two arrays to compare. - - Kwargs: - - * strict (bool): - If True, perform a complete mask and data array equality check. - If False (default), the data array equality considers only unmasked - elements. - - """ - self._assertMaskedArray(np.testing.assert_array_equal, a, b, strict) + assertMaskedArrayEqual = staticmethod(assert_masked_array_equal) def assertArrayAlmostEqual(self, a, b, decimal=6): np.testing.assert_array_almost_equal(a, b, decimal=decimal) - def assertMaskedArrayAlmostEqual(self, a, b, decimal=6, strict=False): - """ - Check that masked arrays are almost equal. This requires the - masks to be identical, and the unmasked values to be almost - equal. - - Args: - - * a, b (array-like): - Two arrays to compare. - - Kwargs: - - * strict (bool): - If True, perform a complete mask and data array equality check. - If False (default), the data array equality considers only unmasked - elements. - - * decimal (int): - Equality tolerance level for - :meth:`numpy.testing.assert_array_almost_equal`, with the meaning - 'abs(desired-actual) < 0.5 * 10**(-decimal)' - - """ - self._assertMaskedArray( - np.testing.assert_array_almost_equal, a, b, strict, decimal=decimal - ) + assertMaskedArrayAlmostEqual = staticmethod( + assert_masked_array_almost_equal + ) def assertArrayAllClose(self, a, b, rtol=1.0e-7, atol=1.0e-8, **kwargs): """ diff --git a/lib/iris/tests/results/unit/util/mask_cube/TestCubeMask/mask_cube_2d_create_new_dim.cml b/lib/iris/tests/results/unit/util/mask_cube/TestCubeMask/mask_cube_2d_create_new_dim.cml new file mode 100644 index 0000000000..52aae1eb5e --- /dev/null +++ b/lib/iris/tests/results/unit/util/mask_cube/TestCubeMask/mask_cube_2d_create_new_dim.cml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/util/mask_cube/original_cube_full2d_global.cml b/lib/iris/tests/results/unit/util/mask_cube/original_cube_full2d_global.cml new file mode 100644 index 0000000000..abaebd51d6 --- /dev/null +++ b/lib/iris/tests/results/unit/util/mask_cube/original_cube_full2d_global.cml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_1d.cml b/lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_1d.cml new file mode 100644 index 0000000000..bf8902bcb2 --- /dev/null +++ b/lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_1d.cml @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_2d.cml b/lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_2d.cml new file mode 100644 index 0000000000..e1760775f9 --- /dev/null +++ b/lib/iris/tests/results/unit/util/mask_cube/original_cube_simple_2d.cml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/test_analysis.py b/lib/iris/tests/test_analysis.py index e95978a371..e0a5d0971e 100644 --- a/lib/iris/tests/test_analysis.py +++ b/lib/iris/tests/test_analysis.py @@ -1013,7 +1013,9 @@ def test_max_run_masked(self): # [[ 0 1 2 3] # [ 4 5 6 7] # [ 8 9 10 11]] - iris.util.mask_cube(cube, np.isin(cube.data, [0, 2, 3, 5, 7, 11])) + iris.util.mask_cube( + cube, np.isin(cube.data, [0, 2, 3, 5, 7, 11]), in_place=True + ) # [[-- 1 -- --] # [ 4 -- 6 --] # [ 8 9 10 --]] diff --git a/lib/iris/tests/unit/util/test__mask_array.py b/lib/iris/tests/unit/util/test__mask_array.py new file mode 100644 index 0000000000..91a5aca1b4 --- /dev/null +++ b/lib/iris/tests/unit/util/test__mask_array.py @@ -0,0 +1,173 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Test function :func:`iris.util._mask_array""" + +import dask.array as da +import numpy as np +import numpy.ma as ma +import pytest + +import iris._lazy_data +from iris.tests import assert_masked_array_equal +from iris.util import _mask_array + +# Set up some arrays to use through the tests. +array_1d = np.arange(4) +masked_arr_1d = ma.array(np.arange(4), mask=[1, 0, 0, 1]) +array_2by3 = np.arange(6).reshape(2, 3) + +# Any masked points on the mask itself should be ignored. So result with mask_1d +# and masked_mask_1d should be the same. +mask_1d = np.array([0, 1, 0, 1]) +masked_mask_1d = ma.array([0, 1, 1, 1], mask=[0, 0, 1, 0]) + +# Expected output depends whether input array is masked or not. +expected1 = ma.array(array_1d, mask=mask_1d) +expected2 = ma.array(array_1d, mask=[1, 1, 0, 1]) +array_choices = [(array_1d, expected1), (masked_arr_1d, expected2)] + + +@pytest.mark.parametrize( + "mask", [mask_1d, masked_mask_1d], ids=["plain-mask", "masked-mask"] +) +@pytest.mark.parametrize("lazy_mask", [False, True], ids=["real", "lazy"]) +@pytest.mark.parametrize( + "array, expected", array_choices, ids=["plain-array", "masked-array"] +) +@pytest.mark.parametrize("lazy_array", [False, True], ids=["real", "lazy"]) +def test_1d_not_in_place(array, mask, expected, lazy_array, lazy_mask): + """ + Basic test for expected behaviour when working not in place with various + array types for input. + + """ + if lazy_array: + array = iris._lazy_data.as_lazy_data(array) + + if lazy_mask: + mask = iris._lazy_data.as_lazy_data(mask) + + result = _mask_array(array, mask) + assert result is not array + + if lazy_array or lazy_mask: + assert iris._lazy_data.is_lazy_data(result) + result = iris._lazy_data.as_concrete_data(result) + + assert_masked_array_equal(expected, result) + + +# 1D in place tests. + + +def test_plain_array_in_place(): + """ + Test we get an informative error when trying to add a mask to a plain numpy + array. + + """ + arr = array_1d + mask = None + with pytest.raises( + TypeError, match="Cannot apply a mask in-place to a plain numpy array." + ): + _mask_array(arr, mask, in_place=True) + + +def test_masked_array_lazy_mask_in_place(): + """ + Test we get an informative error when trying to apply a lazy mask in-place + to a non-lazy array. + + """ + arr = masked_arr_1d + mask = da.from_array([0, 1, 0, 1]) + with pytest.raises( + TypeError, match="Cannot apply lazy mask in-place to a non-lazy array." + ): + _mask_array(arr, mask, in_place=True) + + +@pytest.mark.parametrize( + "mask", [mask_1d, masked_mask_1d], ids=["plain-mask", "masked-mask"] +) +def test_real_masked_array_in_place(mask): + """ + Check expected behaviour for applying masks in-place to a masked array. + + """ + arr = masked_arr_1d.copy() + result = _mask_array(arr, mask, in_place=True) + assert_masked_array_equal(arr, expected2) + # Resolve uses returned value regardless of whether we're working in_place. + assert result is arr + + +def test_lazy_array_in_place(): + """ + Test that in place flag is ignored for lazy arrays, and result is the same + as the not in_place case. + + """ + arr = da.from_array(np.arange(4)) + mask = np.array([0, 1, 0, 1]) + expected_computed = ma.array(range(4), mask=[0, 1, 0, 1]) + # in_place is ignored for lazy array as this is handled by _math_op_common. + result = _mask_array(arr, mask, in_place=True) + assert iris._lazy_data.is_lazy_data(result) + assert_masked_array_equal(result.compute(), expected_computed) + assert result is not arr + + +# Broadcasting tests. + +IN_PLACE_PARAMETRIZE = pytest.mark.parametrize( + "in_place", [False, True], ids=["not-in-place", "in-place"] +) + + +@IN_PLACE_PARAMETRIZE +def test_trailing_mask(in_place): + array = ma.array(array_2by3.copy()) + mask = np.array([0, 1, 0]) + expected = ma.array(array_2by3, mask=[[0, 1, 0], [0, 1, 0]]) + result = _mask_array(array, mask, in_place=in_place) + assert_masked_array_equal(result, expected) + assert result is array if in_place else result is not array + + +@IN_PLACE_PARAMETRIZE +def test_leading_mask(in_place): + arr = ma.masked_array(array_2by3.copy(), mask=[[0, 0, 0], [0, 0, 1]]) + mask = np.array([1, 0]).reshape(2, 1) + expected = ma.array(arr.data, mask=[[1, 1, 1], [0, 0, 1]]) + result = _mask_array(arr, mask, in_place=in_place) + assert_masked_array_equal(result, expected) + assert result is arr if in_place else result is not arr + + +def test_lazy_trailing_mask(): + arr = da.ma.masked_array(array_2by3, mask=[[0, 1, 1], [0, 0, 0]]) + mask = np.array([0, 1, 0]) + expected_computed = ma.array(array_2by3, mask=[[0, 1, 1], [0, 1, 0]]) + result = _mask_array(arr, mask, in_place=False) + assert iris._lazy_data.is_lazy_data(result) + assert_masked_array_equal(result.compute(), expected_computed) + assert result is not arr + + +def test_lazy_leading_mask(): + arr = da.from_array(array_2by3) + mask = da.from_array([0, 1]).reshape(2, 1) + expected_computed = ma.array(array_2by3, mask=[[0, 0, 0], [1, 1, 1]]) + result = _mask_array(arr, mask, in_place=False) + assert iris._lazy_data.is_lazy_data(result) + assert_masked_array_equal(result.compute(), expected_computed) + assert result is not arr + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/lib/iris/tests/unit/util/test_mask_cube.py b/lib/iris/tests/unit/util/test_mask_cube.py index 2d5aaa21f1..0123d0cca5 100644 --- a/lib/iris/tests/unit/util/test_mask_cube.py +++ b/lib/iris/tests/unit/util/test_mask_cube.py @@ -9,13 +9,19 @@ # importing anything else. import iris.tests as tests # isort:skip +import pathlib + +import dask.array as da import numpy as np import numpy.ma as ma from iris.tests.stock import ( make_bounds_discontiguous_at_point, sample_2d_latlons, + simple_1d, + simple_2d, ) +import iris.util from iris.util import mask_cube @@ -23,15 +29,32 @@ def full2d_global(): return sample_2d_latlons(transformed=True) -@tests.skip_data -class Test(tests.IrisTest): +class MaskCubeMixin: + def assertOriginalMetadata(self, cube, func): + """ + Check metadata matches that of input cube. func is a string indicating + which function created the original cube. + + """ + reference_dir = pathlib.Path("unit/util/mask_cube") + reference_fname = reference_dir / f"original_cube_{func}.cml" + self.assertCML( + cube, + reference_filename=str(reference_fname), + checksum=False, + ) + + +class TestArrayMask(tests.IrisTest, MaskCubeMixin): + """Tests with mask specified as numpy array.""" + def setUp(self): # Set up a 2d cube with a masked discontiguity to test masking # of 2-dimensional cubes self.cube_2d = full2d_global() make_bounds_discontiguous_at_point(self.cube_2d, 3, 3) - def test_mask_cube_2d(self): + def test_mask_cube_2d_in_place(self): # This tests the masking of a 2d data array cube = self.cube_2d discontiguity_array = ma.getmaskarray(cube.data).copy() @@ -40,9 +63,132 @@ def test_mask_cube_2d(self): # Remove mask so that we can pass an unmasked data set to # mask_discontiguities, and check that it masks the correct point by # comparing with masked data - cube.data.mask = ma.nomask - returned = mask_cube(cube, discontiguity_array) - self.assertTrue(np.all(expected.data.mask == returned.data.mask)) + cube.data = cube.data.data + returned = mask_cube(cube, discontiguity_array, in_place=True) + np.testing.assert_array_equal(expected.data.mask, cube.data.mask) + self.assertOriginalMetadata(cube, "full2d_global") + self.assertIs(returned, None) + + def test_mask_cube_2d_not_in_place(self): + # This tests the masking of a 2d data array + cube = self.cube_2d + discontiguity_array = ma.getmaskarray(cube.data).copy() + expected = cube.copy() + + # Remove mask so that we can pass an unmasked data set to + # mask_discontiguities, and check that it masks the correct point by + # comparing with masked data + cube.data = cube.data.data + returned = mask_cube(cube, discontiguity_array, in_place=False) + np.testing.assert_array_equal(expected.data.mask, returned.data.mask) + self.assertOriginalMetadata(returned, "full2d_global") + self.assertFalse(ma.is_masked(cube.data)) + + def test_mask_cube_lazy_in_place_broadcast(self): + cube = simple_2d() + cube.data = cube.lazy_data() + mask = [0, 1, 1, 0] + returned = mask_cube(cube, mask, in_place=True) + self.assertTrue(cube.has_lazy_data()) + # Touch the data so lazyness status doesn't affect CML check. + cube.data + self.assertOriginalMetadata(cube, "simple_2d") + for subcube in cube.slices("foo"): + # Mask should have been broadcast across "bar" dimension. + np.testing.assert_array_equal(subcube.data.mask, mask) + self.assertIs(returned, None) + + +class TestCoordMask(tests.IrisTest, MaskCubeMixin): + """Tests with mask specified as a Coord.""" + + def setUp(self): + self.cube = simple_2d() + + def test_mask_cube_2d_first_dim(self): + mask_coord = iris.coords.AuxCoord([0, 1, 0], long_name="mask", units=1) + self.cube.add_aux_coord(mask_coord, 0) + + returned = mask_cube(self.cube, mask_coord, in_place=False) + # Remove extra coord so we can check against original metadata. + returned.remove_coord(mask_coord) + self.assertOriginalMetadata(returned, "simple_2d") + for subcube in returned.slices("bar"): + # Mask should have been broadcast across "foo" dimension. + np.testing.assert_array_equal(subcube.data.mask, mask_coord.points) + + def test_mask_cube_2d_second_dim(self): + mask_coord = iris.coords.AuxCoord( + [0, 0, 1, 1], long_name="mask", units=1 + ) + returned = mask_cube(self.cube, mask_coord, in_place=False, dim=1) + self.assertOriginalMetadata(returned, "simple_2d") + for subcube in returned.slices("foo"): + # Mask should have been broadcast across "bar" dimension. + np.testing.assert_array_equal(subcube.data.mask, mask_coord.points) + + +class TestCubeMask(tests.IrisTest, MaskCubeMixin): + """Tests with mask specified as a Cube.""" + + def setUp(self): + self.cube = simple_2d() + + def test_mask_cube_2d_first_dim_not_in_place(self): + mask = iris.cube.Cube([0, 1, 0], long_name="mask", units=1) + mask.add_dim_coord(self.cube.coord("bar"), 0) + + returned = mask_cube(self.cube, mask, in_place=False) + self.assertOriginalMetadata(returned, "simple_2d") + for subcube in returned.slices("bar"): + # Mask should have been broadcast across 'foo' dimension. + np.testing.assert_array_equal(subcube.data.mask, mask.data) + + def test_mask_cube_2d_first_dim_in_place(self): + mask = iris.cube.Cube([0, 1, 0], long_name="mask", units=1) + mask.add_dim_coord(self.cube.coord("bar"), 0) + + returned = mask_cube(self.cube, mask, in_place=True) + self.assertOriginalMetadata(self.cube, "simple_2d") + for subcube in self.cube.slices("bar"): + # Mask should have been broadcast across 'foo' dimension. + np.testing.assert_array_equal(subcube.data.mask, mask.data) + self.assertIs(returned, None) + + def test_mask_cube_2d_create_new_dim(self): + mask = iris.cube.Cube( + [[0, 1, 0], [0, 0, 1]], long_name="mask", units=1 + ) + + broadcast_coord = iris.coords.DimCoord([1, 2], long_name="baz") + mask.add_dim_coord(broadcast_coord, 0) + mask.add_dim_coord(self.cube.coord("bar"), 1) + + # Create length-1 dimension to enable broadcasting. + self.cube.add_aux_coord(broadcast_coord[0]) + cube = iris.util.new_axis(self.cube, "baz") + + returned = mask_cube(cube, mask, in_place=False) + self.assertCML(cube, checksum=False) + + for subcube in returned.slices_over("baz"): + # Underlying data should have been broadcast across 'baz' dimension. + np.testing.assert_array_equal(subcube.data, self.cube.data) + + for subcube in returned.slices_over("foo"): + # Mask should have been broadcast across 'foo' dimension. + np.testing.assert_array_equal(subcube.data.mask, mask.data) + + def test_mask_cube_1d_lazy_mask_in_place(self): + cube = simple_1d() + mask = cube.copy(da.from_array([0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1])) + returned = mask_cube(cube, mask, in_place=True) + self.assertIs(returned, None) + self.assertTrue(cube.has_lazy_data()) + # Touch the data so lazyness status doesn't interfere with CML check. + cube.data + self.assertOriginalMetadata(cube, "simple_1d") + np.testing.assert_array_equal(cube.data.mask, mask.data) if __name__ == "__main__": diff --git a/lib/iris/util.py b/lib/iris/util.py index ded72d0f23..42f06c5d9c 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -24,6 +24,8 @@ from iris._deprecation import warn_deprecated from iris._lazy_data import as_concrete_data, is_lazy_data +from iris.common import SERVICES +from iris.common.lenient import _lenient_client import iris.exceptions @@ -1754,29 +1756,123 @@ def find_discontiguities(cube, rel_tol=1e-5, abs_tol=1e-8): return bad_points_boolean -def mask_cube(cube, points_to_mask): +def _mask_array(array, points_to_mask, in_place=False): """ - Masks any cells in the data array which correspond to cells marked `True` - in the `points_to_mask` array. + Apply masking to array where points_to_mask is True/non-zero. Designed to + work with iris.analysis.maths._binary_op_common so array and points_to_mask + will be broadcastable to each other. array and points_to_mask may be numpy + or dask types (or one of each). - Args: + If array is lazy then in_place is ignored: _math_op_common will use the + returned value regardless of in_place, so we do not need to implement it + here. If in_place is True then array must be a np.ma.MaskedArray or dask + array (must be a dask array if points_to_mask is lazy). - * cube (`iris.cube.Cube`): - A 2-dimensional instance of :class:`iris.cube.Cube`. + """ + # Decide which array library to use. + if is_lazy_data(points_to_mask) or is_lazy_data(array): + al = da + if not is_lazy_data(array) and in_place: + # Non-lazy array and lazy mask should not come up for in_place + # case, due to _binary_op_common handling added at #3790. + raise TypeError( + "Cannot apply lazy mask in-place to a non-lazy array." + ) + in_place = False - * points_to_mask (`numpy.ndarray` of bool): - A 2d boolean array of Truth values representing points to mask in the - x and y arrays of the cube. + elif in_place and not isinstance(array, ma.MaskedArray): + raise TypeError("Cannot apply a mask in-place to a plain numpy array.") + else: + al = np - Returns: + points_to_mask = points_to_mask.astype(bool) + + # Treat any masked points on our mask as False. + points_to_mask = al.ma.filled(points_to_mask, False) + + # Get broadcasted views of the arrays. Note that broadcast_arrays does not + # preserve masks, so we need to explicitly handle any exising mask on array. + array_mask = al.ma.getmaskarray(array) - * result (`iris.cube.Cube`): - A cube whose data array is masked at points specified by input array. + array_data, array_mask, points_to_mask = al.broadcast_arrays( + array, array_mask, points_to_mask + ) + + new_mask = al.logical_or(array_mask, points_to_mask) + if in_place: + array.mask = new_mask + result = array # Resolve uses returned value even if working in place. + else: + # Return a new, independent array. + result = al.ma.masked_array(array_data.copy(), mask=new_mask) + + return result + + +@_lenient_client(services=SERVICES) +def mask_cube(cube, points_to_mask, in_place=False, dim=None): """ - cube.data = ma.masked_array(cube.data) - cube.data[points_to_mask] = ma.masked - return cube + Masks any cells in the cube's data array which correspond to cells marked + ``True`` (or non zero) in ``points_to_mask``. ``points_to_mask`` may be + specified as a :class:`numpy.ndarray`, :class:`iris.coords.Coord` or + :class:`iris.cube.Cube`, following the same broadcasting approach as cube + arithmetic (see :ref:`cube maths`). + + Parameters + ---------- + + cube : iris.cube.Cube + Cube containing data that requires masking. + + points_to_mask : numpy.ndarray, iris.coords.Coord or iris.cube.Cube + Specifies booleans (or ones and zeros) indicating which points will be masked. + + in_place : bool, default=False + If `True`, masking is applied to the input cube. Otherwise a copy is masked + and returned. + + dim : int, optional + If `points_to_mask` is a coord which does not exist on the cube, specify the + dimension to which it should be mapped. + + Returns + ------- + + iris.cube.Cube + A cube whose data array is masked at points specified by ``points_to_mask``. + + Notes + ----- + + If either ``cube`` or ``points_to_mask`` is lazy, the result will be lazy. + + """ + if in_place and not cube.has_lazy_data(): + # Ensure cube data is masked type so we can work on it in-place. + cube.data = ma.asanyarray(cube.data) + mask_function = functools.partial(_mask_array, in_place=True) + else: + mask_function = _mask_array + + input_metadata = cube.metadata + result = iris.analysis.maths._binary_op_common( + mask_function, + "mask", + cube, + points_to_mask, + cube.units, + in_place=in_place, + dim=dim, + sanitise_metadata=False, + ) + + # Resolve combines the metadata from the two operands, but we want to + # preserve the metadata from the (first) input cube. + result.metadata = input_metadata + + if not in_place: + return result def equalise_attributes(cubes): From 5cd21bfe15b8aafc1c9b035c843a49fc5cd17fb5 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Wed, 10 Aug 2022 09:59:47 +0100 Subject: [PATCH 185/319] set default 'unknown' version (#4892) --- lib/iris/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 7ff0a4260e..b944f9b22f 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -101,7 +101,12 @@ def callback(cube, field, filename): import iris.io from ._deprecation import IrisDeprecation, warn_deprecated -from ._version import version as __version__ # noqa: F401 + +try: + from ._version import version as __version__ # noqa: F401 +except ModuleNotFoundError: + __version__ = "unknown" + try: import iris_sample_data From c4d9d89a1206e55f9fc0b140cdeafa5af0849048 Mon Sep 17 00:00:00 2001 From: Kristofer Krus Date: Wed, 10 Aug 2022 23:12:44 +0200 Subject: [PATCH 186/319] Prevent color bar from being misplaced (#4894) * Prevent color bar from being misplaced Currently, if `plt.gca()` gives a different Axes object than `axes` in the function `_label`, the color bar will be malpositioned, ending up either on another subfigure, or in another figure window altogether. Specifying the `ax` argument when calling `plt.colorbar` prevents this. * add whatsnew and test coverage * check axes and figure * tidy and add extra axes2 test Co-authored-by: Bill Little --- docs/src/whatsnew/latest.rst | 8 +++++++- lib/iris/quickplot.py | 2 +- lib/iris/tests/test_quickplot.py | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d7febb9e0e..f79fbc05de 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -25,7 +25,7 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. N/A +#. Welcome to `@krikru`_ who made their first contribution to Iris 🎉 ✨ Features @@ -136,6 +136,11 @@ This document explains the changes made to Iris for this release array type. This prevents masks being lost in some cases and therefore resolves :issue:`2987`. (:pull:`3790`) +#. `@krikru`_ and `@rcomer`_ updated :mod:`iris.quickplot` such that the + colorbar is added to the correct ``axes`` when specified as a keyword + argument to a plotting routine. Otherwise, by default the colorbar will be + added to the current axes of the current figure. (:pull:`4894`) + #. `@rcomer`_ and `@bjlittle`_ (reviewer) modified :func:`iris.util.mask_cube` so it either works in place or returns a new cube (:issue:`3717`, :pull:`4889`) @@ -279,6 +284,7 @@ This document explains the changes made to Iris for this release core dev names are automatically included by the common_links.inc: .. _@evertrol: https://github.com/evertrol +.. _@krikru: https://github.com/krikru .. comment diff --git a/lib/iris/quickplot.py b/lib/iris/quickplot.py index 14f9e5d2d5..18ed2554a3 100644 --- a/lib/iris/quickplot.py +++ b/lib/iris/quickplot.py @@ -71,7 +71,7 @@ def _label(cube, mode, result=None, ndims=2, coords=None, axes=None): if result is not None: draw_edges = mode == iris.coords.POINT_MODE bar = plt.colorbar( - result, orientation="horizontal", drawedges=draw_edges + result, ax=axes, orientation="horizontal", drawedges=draw_edges ) has_known_units = not ( cube.units.is_unknown() or cube.units.is_no_unit() diff --git a/lib/iris/tests/test_quickplot.py b/lib/iris/tests/test_quickplot.py index dec71a99ac..06f170c666 100644 --- a/lib/iris/tests/test_quickplot.py +++ b/lib/iris/tests/test_quickplot.py @@ -247,5 +247,39 @@ def test_not_reference_time_units(self): self.check_graphic() +@tests.skip_data +@tests.skip_plot +class TestSubplotColorbar(tests.IrisTest): + def setUp(self): + theta = _load_theta() + coords = ["model_level_number", "grid_longitude"] + self.data = next(theta.slices(coords)) + spec = (1, 1, 1) + self.figure1 = plt.figure() + self.axes1 = self.figure1.add_subplot(*spec) + self.figure2 = plt.figure() + self.axes2 = self.figure2.add_subplot(*spec) + + def _check(self, mappable, figure, axes): + self.assertIs(mappable.axes, axes) + self.assertIs(mappable.colorbar.mappable, mappable) + self.assertIs(mappable.colorbar.ax.get_figure(), figure) + + def test_with_axes1(self): + # plot using the first figure subplot axes (explicit) + mappable = qplt.contourf(self.data, axes=self.axes1) + self._check(mappable, self.figure1, self.axes1) + + def test_with_axes2(self): + # plot using the second figure subplot axes (explicit) + mappable = qplt.contourf(self.data, axes=self.axes2) + self._check(mappable, self.figure2, self.axes2) + + def test_without_axes__default(self): + # plot using the second/last figure subplot axes (default) + mappable = qplt.contourf(self.data) + self._check(mappable, self.figure2, self.axes2) + + if __name__ == "__main__": tests.main() From 6827699b17e6e8027cc6ef2041a5f30a4d915381 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 11 Aug 2022 21:47:08 +0100 Subject: [PATCH 187/319] Lockfile update and skip questionable test (#4901) * Updated environment lockfiles * skip questionable test Co-authored-by: Lockfile bot --- .../test_regrid_ProjectedUnstructured.py | 5 ++++ requirements/ci/nox.lock/py310-linux-64.lock | 28 +++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 28 +++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 28 +++++++++---------- 4 files changed, 47 insertions(+), 42 deletions(-) diff --git a/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py b/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py index 1ace02ea8a..742adc8c15 100644 --- a/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py +++ b/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py @@ -9,6 +9,8 @@ # importing anything else. import iris.tests as tests # isort:skip +import unittest + import cartopy.crs as ccrs from cf_units import Unit import numpy as np @@ -60,6 +62,9 @@ def test_nearest_sinusoidal(self): res[:, 0], (1, 73, 96), 299.99993826, 3.9223839688e-5 ) + @unittest.skip( + "Deprecated API and provenance of reference numbers unknown." + ) def test_nearest_gnomonic_uk_domain(self): crs = ccrs.Gnomonic(central_latitude=60.0) uk_grid = self.global_grid.intersection( diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index ecf81b28c9..1ac6b37683 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 598e5811a7e12a4e68fde1ffdbc292fd650eda66b7c6231c25f3f71482002097 +# input_hash: 94986062f1bd0cfb41681a8ceedeec6f34800911c4c8a9b2fcc0780a142f41a0 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -77,7 +77,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -99,7 +99,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -128,7 +128,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0. https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_0.tar.bz2#b10c888ee1372e73d6d57a40d3f8e490 +https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -172,7 +172,7 @@ https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c +https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d @@ -199,38 +199,38 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.ta https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py310hff52083_0.tar.bz2#9a7c1d5f93e2b153d5b0b4930f8b6b66 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.4.1-py310hff52083_0.tar.bz2#94ec90b4d36bfdb37c26835ce73a3e69 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.2-py310hff52083_0.tar.bz2#54ed6fbd5c15fe0ed64429a5b35e3baa +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py310hff52083_0.tar.bz2#87e141dd6d0304b879d903e63560f73b https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py310h5764c6d_0.tar.bz2#c965e9e47e42b19d26994e848d2a40e6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.0.1-hf9f4e7c_0.tar.bz2#e63a748f2f13b2b1e034ff9f5917edaa +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_hd09bd1e_1.tar.bz2#f3fd1799efae42bf8a4a77251f3319a1 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.1-pyhd8ed1ab_0.tar.bz2#0cbcd1fa7291fe72dc8f848907510498 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py310hdfbd76f_2.tar.bz2#0f394bf0f45f1a9b036fa9af374dcdad +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py310hdfbd76f_0.tar.bz2#e5d21b0cb4161a40221786f2f05b3903 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h5e49deb_3.tar.bz2#5305d80a31c7db98765b52ea95521ff6 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h863fc68_101.tar.bz2#97848ee3f2b01f6d99a43b729c1e89ef -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_1.tar.bz2#30705ca3f166305e268ae10d2d703879 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 7afc079278..d03df3f6fe 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 5701e0b5b8d78ba450e4b3497a5e9edbd18b306eac6572cd4f65e550f39d9a4c +# input_hash: de4605bacf6b21a47326d7db45307606b6a338772c4adfe765917f44dd62d228 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -76,7 +76,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -98,7 +98,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -127,7 +127,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0. https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_0.tar.bz2#b10c888ee1372e73d6d57a40d3f8e490 +https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -171,7 +171,7 @@ https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c +https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d @@ -198,38 +198,38 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py38h578d9bd_0.tar.bz2#e69405e267e41bb8409e5ec307a6cd3d +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.4.1-py38h578d9bd_0.tar.bz2#59cf2dae1ee308c077dc0971e7ec421a https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.2-py38h578d9bd_0.tar.bz2#a01038c7c46c4a52fba1645c17a8c8cb +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py38h578d9bd_0.tar.bz2#b830ca0aaf8992855af9001f0c2d7b2e https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py38h0a891b7_0.tar.bz2#7ac14fa19454e00f50d9a39506bcc3c6 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.0.1-hf9f4e7c_0.tar.bz2#e63a748f2f13b2b1e034ff9f5917edaa +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_hd09bd1e_1.tar.bz2#f3fd1799efae42bf8a4a77251f3319a1 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.1-pyhd8ed1ab_0.tar.bz2#0cbcd1fa7291fe72dc8f848907510498 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py38hea3f02b_2.tar.bz2#71f2dd6099c193da621ead62efba14a9 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py38hea3f02b_0.tar.bz2#d19e23bb56b31d2504a0ff4d46b7aabc https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h3b45516_3.tar.bz2#b2415a314858f4232aab437bc27803fc https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h863fc68_101.tar.bz2#97848ee3f2b01f6d99a43b729c1e89ef -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_1.tar.bz2#2726cea4b977031563cb3d2ae813a695 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h32db9c8_101.tar.bz2#d1451d40c8204594cdcf156363128000 diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index dd824baa39..434e233c78 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e71685e652ffa68d39b3703490ab0ecbb40a73fdac940ab8c067fd4704286361 +# input_hash: 433496c84ac9b670c078321708c97d3574b7f5cd3fe0f756969dd5b98bede638 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -77,7 +77,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.29-haf5c9bc_1.tar.bz2#c01640c8bad562720d6caff0402dbd96 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -99,7 +99,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.29-h28c427c_1.tar.bz2#36dbdbf505b131c7e79a3857f3537185 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -128,7 +128,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0. https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_0.tar.bz2#b10c888ee1372e73d6d57a40d3f8e490 +https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -172,7 +172,7 @@ https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.0-pyhd8ed1ab_0.tar.bz2#050b94cf4a8c760656e51d2d44e4632c +https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d @@ -199,38 +199,38 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.2.0-py39hf3d152e_0.tar.bz2#0a487a44f996e39d13cdf2899855c406 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.4.1-py39hf3d152e_0.tar.bz2#ce89d02f0e52921a1b1d3797032bf922 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.2-py39hf3d152e_0.tar.bz2#6d14433ac4d61370f267d2cf6b583b9c +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py39hf3d152e_0.tar.bz2#ce1cedc52c7acb8c691b6204a57d5e80 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.7.1-pyhd8ed1ab_0.tar.bz2#291b562cbd17ad2d3d84647bf60f3c0e +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py39hb9d737c_0.tar.bz2#7980ace37ccb3399672c3a9840e039ed https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.0.1-hf9f4e7c_0.tar.bz2#e63a748f2f13b2b1e034ff9f5917edaa +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.5.4-mpi_mpich_hd09bd1e_1.tar.bz2#f3fd1799efae42bf8a4a77251f3319a1 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.1-pyhd8ed1ab_0.tar.bz2#0cbcd1fa7291fe72dc8f848907510498 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.8.1-py39h8ba3f38_2.tar.bz2#a072f694938815973930e93273674231 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py39h8ba3f38_0.tar.bz2#b098a256777cb9e2605451f183c78768 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h68ae834_3.tar.bz2#2800d4dabd586cf203d8f1271a6c9e9d https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h863fc68_101.tar.bz2#97848ee3f2b01f6d99a43b729c1e89ef -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.2-pyhd8ed1ab_0.tar.bz2#ca1ecc78e4a23d0d7a7fc6de167f20c5 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_1.tar.bz2#b8d1b075536dfca33459870dbb824886 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h71b8e10_101.tar.bz2#91e01aa93a2bcca96c9d64d2ce4f65f0 From da85988ad73c27b1472cf69f09f1776fbfdf7d83 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 12 Aug 2022 10:46:48 +0100 Subject: [PATCH 188/319] Adapt to the latest setuptools editable configuration. (#4903) * Adapt to the latest setuptools editable configuration. * What's new entry. * What's new typo. * Update setuptools in Conda requirements. --- docs/src/whatsnew/latest.rst | 3 ++ pyproject.toml | 2 +- requirements/ci/nox.lock/py310-linux-64.lock | 34 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 32 +++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 34 ++++++++++---------- requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.py | 10 +++--- 9 files changed, 62 insertions(+), 59 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f79fbc05de..f64fbe8c8a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -203,6 +203,9 @@ This document explains the changes made to Iris for this release alignment of calendar behaviour in the two packages (see Incompatible Changes). (:pull:`4847`) +#. `@trexfeathers`_ updated the install process to work with setuptools `>=v64`, + making `v64` the minimum compatible version. (:pull:`4903`) + 📚 Documentation ================ diff --git a/pyproject.toml b/pyproject.toml index a45cf3a1fe..5f3071bcc4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [build-system] # Defined by PEP 518 requires = [ - "setuptools>=45", + "setuptools>=64", "setuptools_scm[toml]>=7.0", "wheel", ] diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 1ac6b37683..c267a872b9 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 94986062f1bd0cfb41681a8ceedeec6f34800911c4c8a9b2fcc0780a142f41a0 +# input_hash: 07ddbcfa1472bd4a235372d075c5b7555063f57eb3df1031bcfcf4a187a705b9 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -12,7 +12,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022b-h191b570_0.tar.bz2#48806dd9fc8893fc52aafbdc349e77e0 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab @@ -20,11 +20,11 @@ https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 -https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548 +https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -35,14 +35,14 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_1.tar.bz2#759c6f385ca4110f5fb185d404d306a3 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_0.tar.bz2#8220a603d6822487affcaaf72960fca1 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -68,7 +68,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 -https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 @@ -84,19 +84,19 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_3.tar.bz2#aece73b1c2f00e86cb9e4f16fab91d96 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 -https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 -https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 @@ -109,7 +109,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-hca18f0e_2.tar.bz2#cfd942cf6d2c31dc00f91d1b2dcd0f80 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -117,7 +117,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#79 https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b @@ -137,7 +137,7 @@ https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#b https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b +https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 @@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.ta https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.4.1-py310hff52083_0.tar.bz2#94ec90b4d36bfdb37c26835ce73a3e69 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.0-py310hff52083_0.tar.bz2#3408aea4a02798a0ae7188ebf34f5803 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 @@ -234,7 +234,7 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_1.tar.bz2#30705ca3f166305e268ae10d2d703879 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hc4f8a73_1.tar.bz2#302c545886fc6438adaf13bac64e5188 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f @@ -255,4 +255,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a \ No newline at end of file diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index d03df3f6fe..69a710231e 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: de4605bacf6b21a47326d7db45307606b6a338772c4adfe765917f44dd62d228 +# input_hash: 69f9809771403e4c5a9828c2afa28e110c5e08858cefd26b98099c3e25a55e31 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -19,11 +19,11 @@ https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 -https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548 +https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -34,14 +34,14 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_1.tar.bz2#759c6f385ca4110f5fb185d404d306a3 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_0.tar.bz2#8220a603d6822487affcaaf72960fca1 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -67,7 +67,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 -https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 @@ -83,19 +83,19 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_3.tar.bz2#aece73b1c2f00e86cb9e4f16fab91d96 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 -https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 -https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 @@ -108,7 +108,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-hca18f0e_2.tar.bz2#cfd942cf6d2c31dc00f91d1b2dcd0f80 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -116,7 +116,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#79 https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b @@ -136,7 +136,7 @@ https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#b https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b +https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 @@ -198,7 +198,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.4.1-py38h578d9bd_0.tar.bz2#59cf2dae1ee308c077dc0971e7ec421a +https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.0-py38h578d9bd_0.tar.bz2#2eb47b2a49ec80cda2603f0eafd2b4ac https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 @@ -233,7 +233,7 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_1.tar.bz2#2726cea4b977031563cb3d2ae813a695 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h32db9c8_101.tar.bz2#d1451d40c8204594cdcf156363128000 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hc4f8a73_1.tar.bz2#302c545886fc6438adaf13bac64e5188 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f @@ -254,4 +254,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a \ No newline at end of file diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 434e233c78..da35de3e95 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 433496c84ac9b670c078321708c97d3574b7f5cd3fe0f756969dd5b98bede638 +# input_hash: c86bd1559813a6ad95d6a7365d108c917dbb11f10351ebd3c0a0faa67d53d400 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -12,7 +12,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022a-h191b570_0.tar.bz2#84be5301069417a2221187d2f435e0f7 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022b-h191b570_0.tar.bz2#48806dd9fc8893fc52aafbdc349e77e0 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab @@ -20,11 +20,11 @@ https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 -https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_0.tar.bz2#ec47e97c8e0b27dcadbebc4d17764548 +https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_h77c792f_102.tar.bz2#208f18b1d596b50c6a92a12b30ebe31f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -35,14 +35,14 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.12-h166bdaf_0.tar.bz2#d56e3db8fa642fb383f18f5be35eeef2 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.20-pthreads_h78a6416_1.tar.bz2#759c6f385ca4110f5fb185d404d306a3 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_0.tar.bz2#8220a603d6822487affcaaf72960fca1 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -68,7 +68,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 -https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-15_linux64_openblas.tar.bz2#04eb983975a1be3e57d6d667414cd774 +https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 @@ -84,19 +84,19 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_2.tar.bz2#78c26dbb6e07d95ccc0eab8d4540aa0c +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_3.tar.bz2#aece73b1c2f00e86cb9e4f16fab91d96 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 -https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-15_linux64_openblas.tar.bz2#f45968428e445fd0c6472b561145812a +https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 -https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-15_linux64_openblas.tar.bz2#b7078220384b8bf8db1a45e66412ac4f +https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0d92c0b_2.tar.bz2#7c73d1e16e1a02d95dee85ec6b5574ab +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 @@ -109,7 +109,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-h0708190_1.tar.bz2#4a06f2ac2e5bfae7b6b245171c3f07aa +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-hca18f0e_2.tar.bz2#cfd942cf6d2c31dc00f91d1b2dcd0f80 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -117,7 +117,7 @@ https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#79 https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.4-hd77ab85_0.tar.bz2#7024df220bd8680192d4bad4024122d1 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b @@ -137,7 +137,7 @@ https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#b https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.7.1-pyhd8ed1ab_0.tar.bz2#7556872687250e0ea038eb503da3c44b +https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 @@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-63.4.1-py39hf3d152e_0.tar.bz2#ce89d02f0e52921a1b1d3797032bf922 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.0-py39hf3d152e_0.tar.bz2#d6d3aac692f72414b4fd25fdbb9688ea https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f @@ -234,7 +234,7 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_1.tar.bz2#b8d1b075536dfca33459870dbb824886 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h71b8e10_101.tar.bz2#91e01aa93a2bcca96c9d64d2ce4f65f0 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.8-hc4f8a73_1.tar.bz2#302c545886fc6438adaf13bac64e5188 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f @@ -255,4 +255,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a \ No newline at end of file diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 5ba9988c92..fc061e8bfc 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -7,7 +7,7 @@ dependencies: - python =3.10 # Setup dependencies. - - setuptools >=45 + - setuptools >=64 - setuptools-scm >=7 # Core dependencies. diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 016a986d9f..7a67e4ecc3 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -7,7 +7,7 @@ dependencies: - python =3.8 # Setup dependencies. - - setuptools >=45 + - setuptools >=64 - setuptools-scm >=7 # Core dependencies. diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 171d2a88e3..ff50f24a56 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -7,7 +7,7 @@ dependencies: - python =3.9 # Setup dependencies. - - setuptools >=45 + - setuptools >=64 - setuptools-scm >=7 # Core dependencies. diff --git a/setup.py b/setup.py index f48f3fe25a..c7438ee698 100644 --- a/setup.py +++ b/setup.py @@ -93,13 +93,13 @@ def run(self): # directories are in place. command_to_override.run(self) - # build_lib is defined if we are building the package. Otherwise - # we want to to the work in-place. - dest = getattr(self, "build_lib", None) - if dest is None: + if self.editable_mode: print(" [Running in-place]") - # Pick the source dir instead (currently in the sub-dir "lib") + # Pick the source dir instead (currently in the sub-dir "lib"). dest = "lib" + else: + # Not editable - must be building. + dest = self.build_lib for func in functions: func(self, dest) From 65db05ac1f7ba15846988cf9a171ccef537c9dbb Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Sat, 13 Aug 2022 16:38:14 +0100 Subject: [PATCH 189/319] pytest gallery tests (#4792) * pytest parametrize gallery tests * two context managers as fixtures * use pathlib instead of os * docstrings * use pytest.mark.filterwarnings * show_replaced_by_check_graphic as fixture * Revert previous (more readable without) This reverts commit bdca27cd301fff9de034f14db9da991dac43facf. * gallery_dir function to variable * add setup/teardown fixture * tweak docstring * remove gallerytests from Makefile * update dev guide * doc tweaks * use monkeypatch to simplify * move stuff about * monkeypatch for sys.path; comments and param ids * revert assert False * revert deliberate fails * import_patches fixture * post rebase check_graphic fix * update test references * whatsnew * test data version --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci-tests.yml | 2 +- docs/Makefile | 5 -- docs/gallery_tests/conftest.py | 67 +++++++++++++++ docs/gallery_tests/gallerytest_util.py | 86 ------------------- docs/gallery_tests/test_gallery_examples.py | 44 ++++++++++ docs/gallery_tests/test_plot_COP_1d.py | 30 ------- docs/gallery_tests/test_plot_COP_maps.py | 30 ------- docs/gallery_tests/test_plot_SOI_filtering.py | 30 ------- docs/gallery_tests/test_plot_TEC.py | 30 ------- .../test_plot_anomaly_log_colouring.py | 30 ------- .../test_plot_atlantic_profiles.py | 30 ------- docs/gallery_tests/test_plot_coriolis.py | 27 ------ docs/gallery_tests/test_plot_cross_section.py | 30 ------- .../test_plot_custom_aggregation.py | 30 ------- .../test_plot_custom_file_loading.py | 30 ------- .../test_plot_deriving_phenomena.py | 30 ------- docs/gallery_tests/test_plot_global_map.py | 30 ------- docs/gallery_tests/test_plot_hovmoller.py | 30 ------- docs/gallery_tests/test_plot_inset.py | 31 ------- .../test_plot_lagged_ensemble.py | 30 ------- .../test_plot_lineplot_with_legend.py | 30 ------- docs/gallery_tests/test_plot_load_nemo.py | 30 ------- .../test_plot_orca_projection.py | 30 ------- docs/gallery_tests/test_plot_polar_stereo.py | 30 ------- .../gallery_tests/test_plot_polynomial_fit.py | 30 ------- .../test_plot_projections_and_annotations.py | 30 ------- .../test_plot_rotated_pole_mapping.py | 30 ------- docs/gallery_tests/test_plot_wind_barbs.py | 30 ------- docs/gallery_tests/test_plot_wind_speed.py | 30 ------- .../contributing_documentation_full.rst | 16 ++-- docs/src/whatsnew/latest.rst | 4 + lib/iris/tests/results/imagerepo.json | 70 +++++++-------- 33 files changed, 163 insertions(+), 851 deletions(-) create mode 100644 docs/gallery_tests/conftest.py delete mode 100644 docs/gallery_tests/gallerytest_util.py create mode 100644 docs/gallery_tests/test_gallery_examples.py delete mode 100644 docs/gallery_tests/test_plot_COP_1d.py delete mode 100644 docs/gallery_tests/test_plot_COP_maps.py delete mode 100644 docs/gallery_tests/test_plot_SOI_filtering.py delete mode 100644 docs/gallery_tests/test_plot_TEC.py delete mode 100644 docs/gallery_tests/test_plot_anomaly_log_colouring.py delete mode 100644 docs/gallery_tests/test_plot_atlantic_profiles.py delete mode 100644 docs/gallery_tests/test_plot_coriolis.py delete mode 100644 docs/gallery_tests/test_plot_cross_section.py delete mode 100644 docs/gallery_tests/test_plot_custom_aggregation.py delete mode 100644 docs/gallery_tests/test_plot_custom_file_loading.py delete mode 100644 docs/gallery_tests/test_plot_deriving_phenomena.py delete mode 100644 docs/gallery_tests/test_plot_global_map.py delete mode 100644 docs/gallery_tests/test_plot_hovmoller.py delete mode 100644 docs/gallery_tests/test_plot_inset.py delete mode 100644 docs/gallery_tests/test_plot_lagged_ensemble.py delete mode 100644 docs/gallery_tests/test_plot_lineplot_with_legend.py delete mode 100644 docs/gallery_tests/test_plot_load_nemo.py delete mode 100644 docs/gallery_tests/test_plot_orca_projection.py delete mode 100644 docs/gallery_tests/test_plot_polar_stereo.py delete mode 100644 docs/gallery_tests/test_plot_polynomial_fit.py delete mode 100644 docs/gallery_tests/test_plot_projections_and_annotations.py delete mode 100644 docs/gallery_tests/test_plot_rotated_pole_mapping.py delete mode 100644 docs/gallery_tests/test_plot_wind_barbs.py delete mode 100644 docs/gallery_tests/test_plot_wind_speed.py diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 9ebc7a4c45..b197b58e80 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -15,7 +15,7 @@ jobs: env: IRIS_TEST_DATA_LOC_PATH: benchmarks IRIS_TEST_DATA_PATH: benchmarks/iris-test-data - IRIS_TEST_DATA_VERSION: "2.14" + IRIS_TEST_DATA_VERSION: "2.15" # Lets us manually bump the cache to rebuild ENV_CACHE_BUILD: "0" TEST_DATA_CACHE_BUILD: "2" diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 2657e1e42a..aae1ce2970 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -46,7 +46,7 @@ jobs: session: "tests" env: - IRIS_TEST_DATA_VERSION: "2.14" + IRIS_TEST_DATA_VERSION: "2.15" ENV_NAME: "ci-tests" steps: diff --git a/docs/Makefile b/docs/Makefile index 44c89206d2..f4c8d0b7f4 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -55,8 +55,3 @@ linkcheck: echo "Running linkcheck in $$i..."; \ (cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) linkcheck); done -gallerytest: - @echo - @echo "Running \"gallery\" tests..." - @echo - python -m unittest discover -v -t . diff --git a/docs/gallery_tests/conftest.py b/docs/gallery_tests/conftest.py new file mode 100644 index 0000000000..a218b305a2 --- /dev/null +++ b/docs/gallery_tests/conftest.py @@ -0,0 +1,67 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. + +"""Pytest fixtures for the gallery tests.""" + +import pathlib + +import matplotlib.pyplot as plt +import pytest + +import iris + +CURRENT_DIR = pathlib.Path(__file__).resolve() +GALLERY_DIR = CURRENT_DIR.parents[1] / "gallery_code" + + +@pytest.fixture +def image_setup_teardown(): + """ + Setup and teardown fixture. + + Ensures all figures are closed before and after test to prevent one test + polluting another if it fails with a figure unclosed. + + """ + plt.close("all") + yield + plt.close("all") + + +@pytest.fixture +def import_patches(monkeypatch): + """ + Replace plt.show() with a function that does nothing, also add all the + gallery examples to sys.path. + + """ + + def no_show(): + pass + + monkeypatch.setattr(plt, "show", no_show) + + for example_dir in GALLERY_DIR.iterdir(): + if example_dir.is_dir(): + monkeypatch.syspath_prepend(example_dir) + + yield + + +@pytest.fixture +def iris_future_defaults(): + """ + Create a fixture which resets all the iris.FUTURE settings to the defaults, + as otherwise changes made in one test can affect subsequent ones. + + """ + # Run with all default settings in iris.FUTURE. + default_future_kwargs = iris.Future().__dict__.copy() + for dead_option in iris.Future.deprecated_options: + # Avoid a warning when setting these ! + del default_future_kwargs[dead_option] + with iris.FUTURE.context(**default_future_kwargs): + yield diff --git a/docs/gallery_tests/gallerytest_util.py b/docs/gallery_tests/gallerytest_util.py deleted file mode 100644 index eb2736f194..0000000000 --- a/docs/gallery_tests/gallerytest_util.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -""" -Provides context managers which are fundamental to the ability -to run the gallery tests. - -""" - -import contextlib -import os.path -import sys -import warnings - -import matplotlib.pyplot as plt - -import iris -from iris._deprecation import IrisDeprecation -import iris.plot as iplt -import iris.quickplot as qplt - -GALLERY_DIRECTORY = os.path.join( - os.path.dirname(os.path.dirname(__file__)), "gallery_code" -) -GALLERY_DIRECTORIES = [ - os.path.join(GALLERY_DIRECTORY, the_dir) - for the_dir in os.listdir(GALLERY_DIRECTORY) -] - - -@contextlib.contextmanager -def add_gallery_to_path(): - """ - Creates a context manager which can be used to add the iris gallery - to the PYTHONPATH. The gallery entries are only importable throughout the lifetime - of this context manager. - - """ - orig_sys_path = sys.path - sys.path = sys.path[:] - sys.path += GALLERY_DIRECTORIES - yield - sys.path = orig_sys_path - - -@contextlib.contextmanager -def show_replaced_by_check_graphic(test_case): - """ - Creates a context manager which can be used to replace the functionality - of matplotlib.pyplot.show with a function which calls the check_graphic - method on the given test_case (iris.tests.IrisTest.check_graphic). - - """ - - def replacement_show(): - # form a closure on test_case and tolerance - test_case.check_graphic() - - orig_show = plt.show - plt.show = iplt.show = qplt.show = replacement_show - yield - plt.show = iplt.show = qplt.show = orig_show - - -@contextlib.contextmanager -def fail_any_deprecation_warnings(): - """ - Create a context in which any deprecation warning will cause an error. - - The context also resets all the iris.FUTURE settings to the defaults, as - otherwise changes made in one test can affect subsequent ones. - - """ - with warnings.catch_warnings(): - # Detect and error all and any Iris deprecation warnings. - warnings.simplefilter("error", IrisDeprecation) - # Run with all default settings in iris.FUTURE. - default_future_kwargs = iris.Future().__dict__.copy() - for dead_option in iris.Future.deprecated_options: - # Avoid a warning when setting these ! - del default_future_kwargs[dead_option] - with iris.FUTURE.context(**default_future_kwargs): - yield diff --git a/docs/gallery_tests/test_gallery_examples.py b/docs/gallery_tests/test_gallery_examples.py new file mode 100644 index 0000000000..0d0793a7da --- /dev/null +++ b/docs/gallery_tests/test_gallery_examples.py @@ -0,0 +1,44 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. + +import importlib + +import matplotlib.pyplot as plt +import pytest + +from iris.tests import _RESULT_PATH +from iris.tests.graphics import check_graphic + +from .conftest import GALLERY_DIR + + +def gallery_examples(): + """Generator to yield all current gallery examples.""" + + for example_file in GALLERY_DIR.glob("*/plot*.py"): + yield example_file.stem + + +@pytest.mark.filterwarnings("error::iris.IrisDeprecation") +@pytest.mark.parametrize("example", gallery_examples()) +def test_plot_example( + example, + image_setup_teardown, + import_patches, + iris_future_defaults, +): + """Test that all figures from example code match KGO.""" + + module = importlib.import_module(example) + + # Run example. + module.main() + # Loop through open figures and set each to be the current figure so check_graphic + # will find it. + for fig_num in plt.get_fignums(): + plt.figure(fig_num) + image_id = f"gallery_tests.test_{example}.{fig_num - 1}" + check_graphic(image_id, _RESULT_PATH) diff --git a/docs/gallery_tests/test_plot_COP_1d.py b/docs/gallery_tests/test_plot_COP_1d.py deleted file mode 100644 index 9771e10fb1..0000000000 --- a/docs/gallery_tests/test_plot_COP_1d.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestCOP1DPlot(tests.GraphicsTest): - """Test the COP_1d_plot gallery code.""" - - def test_plot_COP_1d(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_COP_1d - with show_replaced_by_check_graphic(self): - plot_COP_1d.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_COP_maps.py b/docs/gallery_tests/test_plot_COP_maps.py deleted file mode 100644 index a01e12527f..0000000000 --- a/docs/gallery_tests/test_plot_COP_maps.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestCOPMaps(tests.GraphicsTest): - """Test the COP_maps gallery code.""" - - def test_plot_cop_maps(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_COP_maps - with show_replaced_by_check_graphic(self): - plot_COP_maps.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_SOI_filtering.py b/docs/gallery_tests/test_plot_SOI_filtering.py deleted file mode 100644 index 1da731122a..0000000000 --- a/docs/gallery_tests/test_plot_SOI_filtering.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestSOIFiltering(tests.GraphicsTest): - """Test the SOI_filtering gallery code.""" - - def test_plot_soi_filtering(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_SOI_filtering - with show_replaced_by_check_graphic(self): - plot_SOI_filtering.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_TEC.py b/docs/gallery_tests/test_plot_TEC.py deleted file mode 100644 index cfc1fb8eec..0000000000 --- a/docs/gallery_tests/test_plot_TEC.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestTEC(tests.GraphicsTest): - """Test the TEC gallery code.""" - - def test_plot_TEC(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_TEC - with show_replaced_by_check_graphic(self): - plot_TEC.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_anomaly_log_colouring.py b/docs/gallery_tests/test_plot_anomaly_log_colouring.py deleted file mode 100644 index 41f76cc774..0000000000 --- a/docs/gallery_tests/test_plot_anomaly_log_colouring.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestAnomalyLogColouring(tests.GraphicsTest): - """Test the anomaly colouring gallery code.""" - - def test_plot_anomaly_log_colouring(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_anomaly_log_colouring - with show_replaced_by_check_graphic(self): - plot_anomaly_log_colouring.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_atlantic_profiles.py b/docs/gallery_tests/test_plot_atlantic_profiles.py deleted file mode 100644 index fdcb5fb1d1..0000000000 --- a/docs/gallery_tests/test_plot_atlantic_profiles.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestAtlanticProfiles(tests.GraphicsTest): - """Test the atlantic_profiles gallery code.""" - - def test_plot_atlantic_profiles(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_atlantic_profiles - with show_replaced_by_check_graphic(self): - plot_atlantic_profiles.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_coriolis.py b/docs/gallery_tests/test_plot_coriolis.py deleted file mode 100644 index 2e4cea8a74..0000000000 --- a/docs/gallery_tests/test_plot_coriolis.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. - -import iris.tests as tests - -from . import gallerytest_util - -with gallerytest_util.add_gallery_to_path(): - import plot_coriolis - - -class TestCoriolisPlot(tests.GraphicsTest): - """Test the Coriolis Plot gallery code.""" - - def test_plot_coriolis(self): - with gallerytest_util.show_replaced_by_check_graphic(self): - plot_coriolis.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_cross_section.py b/docs/gallery_tests/test_plot_cross_section.py deleted file mode 100644 index b0878d10bc..0000000000 --- a/docs/gallery_tests/test_plot_cross_section.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestCrossSection(tests.GraphicsTest): - """Test the cross_section gallery code.""" - - def test_plot_cross_section(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_cross_section - with show_replaced_by_check_graphic(self): - plot_cross_section.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_custom_aggregation.py b/docs/gallery_tests/test_plot_custom_aggregation.py deleted file mode 100644 index 9d0a40dd3c..0000000000 --- a/docs/gallery_tests/test_plot_custom_aggregation.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestCustomAggregation(tests.GraphicsTest): - """Test the custom aggregation gallery code.""" - - def test_plot_custom_aggregation(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_custom_aggregation - with show_replaced_by_check_graphic(self): - plot_custom_aggregation.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_custom_file_loading.py b/docs/gallery_tests/test_plot_custom_file_loading.py deleted file mode 100644 index 4d0d603a22..0000000000 --- a/docs/gallery_tests/test_plot_custom_file_loading.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestCustomFileLoading(tests.GraphicsTest): - """Test the custom_file_loading gallery code.""" - - def test_plot_custom_file_loading(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_custom_file_loading - with show_replaced_by_check_graphic(self): - plot_custom_file_loading.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_deriving_phenomena.py b/docs/gallery_tests/test_plot_deriving_phenomena.py deleted file mode 100644 index ef2f8cec87..0000000000 --- a/docs/gallery_tests/test_plot_deriving_phenomena.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestDerivingPhenomena(tests.GraphicsTest): - """Test the deriving_phenomena gallery code.""" - - def test_plot_deriving_phenomena(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_deriving_phenomena - with show_replaced_by_check_graphic(self): - plot_deriving_phenomena.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_global_map.py b/docs/gallery_tests/test_plot_global_map.py deleted file mode 100644 index 16f769deae..0000000000 --- a/docs/gallery_tests/test_plot_global_map.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestGlobalMap(tests.GraphicsTest): - """Test the global_map gallery code.""" - - def test_plot_global_map(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_global_map - with show_replaced_by_check_graphic(self): - plot_global_map.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_hovmoller.py b/docs/gallery_tests/test_plot_hovmoller.py deleted file mode 100644 index 29c0e72e05..0000000000 --- a/docs/gallery_tests/test_plot_hovmoller.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestGlobalMap(tests.GraphicsTest): - """Test the hovmoller gallery code.""" - - def test_plot_hovmoller(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_hovmoller - with show_replaced_by_check_graphic(self): - plot_hovmoller.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_inset.py b/docs/gallery_tests/test_plot_inset.py deleted file mode 100644 index 739e0a3224..0000000000 --- a/docs/gallery_tests/test_plot_inset.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. - -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestInsetPlot(tests.GraphicsTest): - """Test the inset plot gallery code.""" - - def test_plot_inset(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_inset - with show_replaced_by_check_graphic(self): - plot_inset.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_lagged_ensemble.py b/docs/gallery_tests/test_plot_lagged_ensemble.py deleted file mode 100644 index f0a0201613..0000000000 --- a/docs/gallery_tests/test_plot_lagged_ensemble.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestLaggedEnsemble(tests.GraphicsTest): - """Test the lagged ensemble gallery code.""" - - def test_plot_lagged_ensemble(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_lagged_ensemble - with show_replaced_by_check_graphic(self): - plot_lagged_ensemble.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_lineplot_with_legend.py b/docs/gallery_tests/test_plot_lineplot_with_legend.py deleted file mode 100644 index 5677667026..0000000000 --- a/docs/gallery_tests/test_plot_lineplot_with_legend.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestLineplotWithLegend(tests.GraphicsTest): - """Test the lineplot_with_legend gallery code.""" - - def test_plot_lineplot_with_legend(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_lineplot_with_legend - with show_replaced_by_check_graphic(self): - plot_lineplot_with_legend.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_load_nemo.py b/docs/gallery_tests/test_plot_load_nemo.py deleted file mode 100644 index f250dc46b4..0000000000 --- a/docs/gallery_tests/test_plot_load_nemo.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestLoadNemo(tests.GraphicsTest): - """Test the load_nemo gallery code.""" - - def test_plot_load_nemo(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_load_nemo - with show_replaced_by_check_graphic(self): - plot_load_nemo.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_orca_projection.py b/docs/gallery_tests/test_plot_orca_projection.py deleted file mode 100644 index c4058c996e..0000000000 --- a/docs/gallery_tests/test_plot_orca_projection.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestOrcaProjection(tests.GraphicsTest): - """Test the orca projection gallery code.""" - - def test_plot_orca_projection(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_orca_projection - with show_replaced_by_check_graphic(self): - plot_orca_projection.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_polar_stereo.py b/docs/gallery_tests/test_plot_polar_stereo.py deleted file mode 100644 index 4d32ee5830..0000000000 --- a/docs/gallery_tests/test_plot_polar_stereo.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestPolarStereo(tests.GraphicsTest): - """Test the polar_stereo gallery code.""" - - def test_plot_polar_stereo(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_polar_stereo - with show_replaced_by_check_graphic(self): - plot_polar_stereo.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_polynomial_fit.py b/docs/gallery_tests/test_plot_polynomial_fit.py deleted file mode 100644 index b522dcf43c..0000000000 --- a/docs/gallery_tests/test_plot_polynomial_fit.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestPolynomialFit(tests.GraphicsTest): - """Test the polynomial_fit gallery code.""" - - def test_plot_polynomial_fit(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_polynomial_fit - with show_replaced_by_check_graphic(self): - plot_polynomial_fit.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_projections_and_annotations.py b/docs/gallery_tests/test_plot_projections_and_annotations.py deleted file mode 100644 index 1c24202251..0000000000 --- a/docs/gallery_tests/test_plot_projections_and_annotations.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestProjectionsAndAnnotations(tests.GraphicsTest): - """Test the atlantic_profiles gallery code.""" - - def test_plot_projections_and_annotations(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_projections_and_annotations - with show_replaced_by_check_graphic(self): - plot_projections_and_annotations.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_rotated_pole_mapping.py b/docs/gallery_tests/test_plot_rotated_pole_mapping.py deleted file mode 100644 index cd9b04fc66..0000000000 --- a/docs/gallery_tests/test_plot_rotated_pole_mapping.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestRotatedPoleMapping(tests.GraphicsTest): - """Test the rotated_pole_mapping gallery code.""" - - def test_plot_rotated_pole_mapping(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_rotated_pole_mapping - with show_replaced_by_check_graphic(self): - plot_rotated_pole_mapping.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_wind_barbs.py b/docs/gallery_tests/test_plot_wind_barbs.py deleted file mode 100644 index 6003860a5e..0000000000 --- a/docs/gallery_tests/test_plot_wind_barbs.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestWindBarbs(tests.GraphicsTest): - """Test the wind_barbs example code.""" - - def test_wind_barbs(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_wind_barbs - with show_replaced_by_check_graphic(self): - plot_wind_barbs.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/gallery_tests/test_plot_wind_speed.py b/docs/gallery_tests/test_plot_wind_speed.py deleted file mode 100644 index ebaf97adbe..0000000000 --- a/docs/gallery_tests/test_plot_wind_speed.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -# Import Iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -from .gallerytest_util import ( - add_gallery_to_path, - fail_any_deprecation_warnings, - show_replaced_by_check_graphic, -) - - -class TestWindSpeed(tests.GraphicsTest): - """Test the wind_speed gallery code.""" - - def test_plot_wind_speed(self): - with fail_any_deprecation_warnings(): - with add_gallery_to_path(): - import plot_wind_speed - with show_replaced_by_check_graphic(self): - plot_wind_speed.main() - - -if __name__ == "__main__": - tests.main() diff --git a/docs/src/developers_guide/contributing_documentation_full.rst b/docs/src/developers_guide/contributing_documentation_full.rst index a98ff0a7e9..ac62a67373 100755 --- a/docs/src/developers_guide/contributing_documentation_full.rst +++ b/docs/src/developers_guide/contributing_documentation_full.rst @@ -72,14 +72,20 @@ This is useful for a final test before committing your changes. Testing ~~~~~~~ -There are a ways to test various aspects of the documentation. The -``make`` commands shown below can be run in the ``docs`` or -``docs/src`` directory. +There are various ways to test aspects of the documentation. Each :ref:`contributing.documentation.gallery` entry has a corresponding test. -To run the tests:: +To run all the gallery tests:: - make gallerytest + pytest -v docs/gallery_tests/test_gallery_examples.py + +To run a test for a single gallery example, use the ``pytest -k`` option for +pattern matching, e.g.:: + + pytest -v -k plot_coriolis docs/gallery_tests/test_gallery_examples.py + +The ``make`` commands shown below can be run in the ``docs`` or ``docs/src`` +directory. Many documentation pages includes python code itself that can be run to ensure it is still valid or to demonstrate examples. To ensure these tests pass diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f64fbe8c8a..411298fc50 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -282,6 +282,10 @@ This document explains the changes made to Iris for this release publishing of ``iris`` PyPI ``sdist`` and binary ``wheels`` as part of our GitHub Continuous-Integration. (:pull:`4849`) +#. `@rcomer`_ and `@wjbenfold`_ (reviewer) used ``pytest`` parametrization to + streamline the gallery test code. (:pull:`4792`) + + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index 28d6f0bb03..342428ca67 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -1,39 +1,39 @@ { - "gallery_tests.test_plot_COP_1d.TestCOP1DPlot.test_plot_COP_1d.0": "aefec91c3601249cc9b3336dc4c8cdb31a64c6d997b3c0eccb5932d285e42f33", - "gallery_tests.test_plot_COP_maps.TestCOPMaps.test_plot_cop_maps.0": "ea9130db95668524913e6ac168991f0d956e917ec76396b96a853dcf94696935", - "gallery_tests.test_plot_SOI_filtering.TestSOIFiltering.test_plot_soi_filtering.0": "fa56f295c5e0694a3c17a58d95e8da536233da99984c5af4c6739b4a9a444eb4", - "gallery_tests.test_plot_TEC.TestTEC.test_plot_TEC.0": "e5a761b69a589a4bc46f9e48c65c6631ce61d1ce3982c13739b33193c0ee3f8c", - "gallery_tests.test_plot_anomaly_log_colouring.TestAnomalyLogColouring.test_plot_anomaly_log_colouring.0": "ec4464e384a39b13931a9b1c85696da968d5e6e63e26847bdbd399938d3c5a4c", - "gallery_tests.test_plot_atlantic_profiles.TestAtlanticProfiles.test_plot_atlantic_profiles.0": "97c160f462a88f07203ebc77a1e36707e61f4e38d8f3d08a910597fc877cec58", - "gallery_tests.test_plot_atlantic_profiles.TestAtlanticProfiles.test_plot_atlantic_profiles.1": "eeea64dd6ea8cd99991d1322b3741e2684571cd89995b3131f32a4765ee2a1cc", - "gallery_tests.test_plot_coriolis.TestCoriolisPlot.test_plot_coriolis.0": "e68665de9a699659c1fe99a5896965966996c46e3e19c1da3a652669c51e1a26", - "gallery_tests.test_plot_cross_section.TestCrossSection.test_plot_cross_section.0": "ea91b17b9562e4d1609f5a05856e4ca45a52957e5ea5f13b1bca9dc0b17b1ac1", - "gallery_tests.test_plot_cross_section.TestCrossSection.test_plot_cross_section.1": "ea9521fb956a394068931e93e07e4aa5856cc47e4a91957b1ba55bb5b17a3b81", - "gallery_tests.test_plot_custom_aggregation.TestCustomAggregation.test_plot_custom_aggregation.0": "ee816f81917e907eb03ec73f856f7ac198d070186e90811f1be33ee1a57a6e18", - "gallery_tests.test_plot_custom_file_loading.TestCustomFileLoading.test_plot_custom_file_loading.0": "fa81cb47845e34bc932797436cccc8343f11359b73523746c48c72d9d9b34da5", - "gallery_tests.test_plot_deriving_phenomena.TestDerivingPhenomena.test_plot_deriving_phenomena.0": "ec97681793689768943c97e8926669d186e8c33f6c99c32e6b936c83d33e2c98", - "gallery_tests.test_plot_global_map.TestGlobalMap.test_plot_global_map.0": "fb997b958466846ed13e87467a997a898d66d17e2cc9906684696f99d3162e81", - "gallery_tests.test_plot_hovmoller.TestGlobalMap.test_plot_hovmoller.0": "eeb46cb4934b934bc07e974bc14b38949943c0fe3e94c17f6ea46cb4c07b3f00", - "gallery_tests.test_plot_inset.TestInsetPlot.test_plot_inset.0": "ebff6992b50096ad9267dac4d640949294924cdbc95d4b699d29952dcda46ed4", - "gallery_tests.test_plot_lagged_ensemble.TestLaggedEnsemble.test_plot_lagged_ensemble.0": "bbbb31b1c44e64e4b1579b5b917133cecc61f146c414668eb1119b1bb197ce34", - "gallery_tests.test_plot_lagged_ensemble.TestLaggedEnsemble.test_plot_lagged_ensemble.1": "aafec5e9e5e03e099a07e0f86542db879438261ec3b13ce78d8dc65a92d83d89", - "gallery_tests.test_plot_lineplot_with_legend.TestLineplotWithLegend.test_plot_lineplot_with_legend.0": "eafd9e12a5a061e9925ec716de489e9685078ec981b229e70ddb79219cc3768d", - "gallery_tests.test_plot_load_nemo.TestLoadNemo.test_plot_load_nemo.0": "a3ff34e87f0049496d17c4d9c04fc225d256971392db9f1696df0f16cec00736", - "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.0": "bb11721a87cce5e4cce79e81d19b3b5e1e1cd3783168e07835853485e65e2e1e", - "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.1": "e58661969e799659c1f719a6c867359a1996c0773649c09c3e612679c07b3f66", - "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.2": "a58660ce9e739b31c93d1c89c8df33863783e23b3f11c07f2664366cc8ee3cc1", - "gallery_tests.test_plot_orca_projection.TestOrcaProjection.test_plot_orca_projection.3": "be817a8784dea56cec79817a919e338437a5c1e73fa16c726c4a3e816a1c6b1c", - "gallery_tests.test_plot_polar_stereo.TestPolarStereo.test_plot_polar_stereo.0": "ba1e615ec7e097ad961f9cb190f038e091c2c1e73f07c11f6f386b3cc1793e01", - "gallery_tests.test_plot_polynomial_fit.TestPolynomialFit.test_plot_polynomial_fit.0": "aeffcb34d244348be5a2c96c3a4fc6d0c4b69f2d87294ccb9f1a125684cd7c11", - "gallery_tests.test_plot_projections_and_annotations.TestProjectionsAndAnnotations.test_plot_projections_and_annotations.0": "fa854f19851a30e4cc76cd0bb0f932dca7c665b0c93ccb4b4ed19e9c3721b5c8", - "gallery_tests.test_plot_projections_and_annotations.TestProjectionsAndAnnotations.test_plot_projections_and_annotations.1": "e3856d999c389662734331afcd2d5a7184dba592b9b69b64d26dc29954b185b2", - "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.0": "ee46607e97a19781c0de1f81d0bb3e241f20c16f3fc0c1fe3d263d33d06f3e80", - "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.1": "ea57685f95a886a1c0de9da090be3e2697e1c0ff3f00c17e6b266c17c07f3f00", - "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.2": "ea57685f95a886a1c0de9da090be3e2497e1c0ff3f01c17e6b366c17c07b3f00", - "gallery_tests.test_plot_rotated_pole_mapping.TestRotatedPoleMapping.test_plot_rotated_pole_mapping.3": "fa8172c6857ecd38cb3392ce36c564311931d85ec64e9787719a39993c316e66", - "gallery_tests.test_plot_wind_barbs.TestWindBarbs.test_wind_barbs.0": "e9e161e996169316c1fe9e96c29e36739e13c07c3d61c07f39813929c07f3f01", - "gallery_tests.test_plot_wind_speed.TestWindSpeed.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e03c06c3d61c07f3da139e1c07f3f01", - "gallery_tests.test_plot_wind_speed.TestWindSpeed.test_plot_wind_speed.1": "e9e960e996169306c1ee9f96c29e36739653c06c3d61c07f39a139e1c07f3f01", + "gallery_tests.test_plot_COP_1d.0": "aefec91c3601249cc9b3336dc4c8cdb31a64c6d997b3c0eccb5932d285e42f33", + "gallery_tests.test_plot_COP_maps.0": "ea9130db95668524913e6ac168991f0d956e917ec76396b96a853dcf94696935", + "gallery_tests.test_plot_SOI_filtering.0": "fa56f295c5e0694a3c17a58d95e8da536233da99984c5af4c6739b4a9a444eb4", + "gallery_tests.test_plot_TEC.0": "e5a761b69a589a4bc46f9e48c65c6631ce61d1ce3982c13739b33193c0ee3f8c", + "gallery_tests.test_plot_anomaly_log_colouring.0": "ec4464e384a39b13931a9b1c85696da968d5e6e63e26847bdbd399938d3c5a4c", + "gallery_tests.test_plot_atlantic_profiles.0": "97c160f462a88f07203ebc77a1e36707e61f4e38d8f3d08a910597fc877cec58", + "gallery_tests.test_plot_atlantic_profiles.1": "eeea64dd6ea8cd99991d1322b3741e2684571cd89995b3131f32a4765ee2a1cc", + "gallery_tests.test_plot_coriolis.0": "e68665de9a699659c1fe99a5896965966996c46e3e19c1da3a652669c51e1a26", + "gallery_tests.test_plot_cross_section.0": "ea91b17b9562e4d1609f5a05856e4ca45a52957e5ea5f13b1bca9dc0b17b1ac1", + "gallery_tests.test_plot_cross_section.1": "ea9521fb956a394068931e93e07e4aa5856cc47e4a91957b1ba55bb5b17a3b81", + "gallery_tests.test_plot_custom_aggregation.0": "ee816f81917e907eb03ec73f856f7ac198d070186e90811f1be33ee1a57a6e18", + "gallery_tests.test_plot_custom_file_loading.0": "fa81cb47845e34bc932797436cccc8343f11359b73523746c48c72d9d9b34da5", + "gallery_tests.test_plot_deriving_phenomena.0": "ec97681793689768943c97e8926669d186e8c33f6c99c32e6b936c83d33e2c98", + "gallery_tests.test_plot_global_map.0": "fb997b958466846ed13e87467a997a898d66d17e2cc9906684696f99d3162e81", + "gallery_tests.test_plot_hovmoller.0": "eeb46cb4934b934bc07e974bc14b38949943c0fe3e94c17f6ea46cb4c07b3f00", + "gallery_tests.test_plot_inset.0": "ebff6992b50096ad9267dac4d640949294924cdbc95d4b699d29952dcda46ed4", + "gallery_tests.test_plot_lagged_ensemble.0": "bbbb31b1c44e64e4b1579b5b917133cecc61f146c414668eb1119b1bb197ce34", + "gallery_tests.test_plot_lagged_ensemble.1": "aafec5e9e5e03e099a07e0f86542db879438261ec3b13ce78d8dc65a92d83d89", + "gallery_tests.test_plot_lineplot_with_legend.0": "eafd9e12a5a061e9925ec716de489e9685078ec981b229e70ddb79219cc3768d", + "gallery_tests.test_plot_load_nemo.0": "a3ff34e87f0049496d17c4d9c04fc225d256971392db9f1696df0f16cec00736", + "gallery_tests.test_plot_orca_projection.0": "bb11721a87cce5e4cce79e81d19b3b5e1e1cd3783168e07835853485e65e2e1e", + "gallery_tests.test_plot_orca_projection.1": "e58661969e799659c1f719a6c867359a1996c0773649c09c3e612679c07b3f66", + "gallery_tests.test_plot_orca_projection.2": "a58660ce9e739b31c93d1c89c8df33863783e23b3f11c07f2664366cc8ee3cc1", + "gallery_tests.test_plot_orca_projection.3": "be817a8784dea56cec79817a919e338437a5c1e73fa16c726c4a3e816a1c6b1c", + "gallery_tests.test_plot_polar_stereo.0": "ba1e615ec7e097ad961f9cb190f038e091c2c1e73f07c11f6f386b3cc1793e01", + "gallery_tests.test_plot_polynomial_fit.0": "aeffcb34d244348be5a2c96c3a4fc6d0c4b69f2d87294ccb9f1a125684cd7c11", + "gallery_tests.test_plot_projections_and_annotations.0": "fa854f19851a30e4cc76cd0bb0f932dca7c665b0c93ccb4b4ed19e9c3721b5c8", + "gallery_tests.test_plot_projections_and_annotations.1": "e3856d999c389662734331afcd2d5a7184dba592b9b69b64d26dc29954b185b2", + "gallery_tests.test_plot_rotated_pole_mapping.0": "ee46607e97a19781c0de1f81d0bb3e241f20c16f3fc0c1fe3d263d33d06f3e80", + "gallery_tests.test_plot_rotated_pole_mapping.1": "ea57685f95a886a1c0de9da090be3e2697e1c0ff3f00c17e6b266c17c07f3f00", + "gallery_tests.test_plot_rotated_pole_mapping.2": "ea57685f95a886a1c0de9da090be3e2497e1c0ff3f01c17e6b366c17c07b3f00", + "gallery_tests.test_plot_rotated_pole_mapping.3": "fa8172c6857ecd38cb3392ce36c564311931d85ec64e9787719a39993c316e66", + "gallery_tests.test_plot_wind_barbs.0": "e9e161e996169316c1fe9e96c29e36739e13c07c3d61c07f39813929c07f3f01", + "gallery_tests.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e03c06c3d61c07f3da139e1c07f3f01", + "gallery_tests.test_plot_wind_speed.1": "e9e960e996169306c1ee9f96c29e36739653c06c3d61c07f39a139e1c07f3f01", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f", From 7a187630e6c91e691780cec9144f7b1d1dd0f77e Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 10:34:03 +0100 Subject: [PATCH 190/319] Updated environment lockfiles (#4904) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 39 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 39 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 39 ++++++++++---------- 3 files changed, 60 insertions(+), 57 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index c267a872b9..ffd5896d7f 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 07ddbcfa1472bd4a235372d075c5b7555063f57eb3df1031bcfcf4a187a705b9 +# input_hash: 26e5295416e089473f7143dc79fd155837256841e73e052eeea25d2e4be13560 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -47,7 +47,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_2.tar.bz2#99c0160c84e61348aa8eb2949b24e6d3 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 @@ -65,9 +65,10 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 -https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f @@ -75,8 +76,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa @@ -84,9 +89,8 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_3.tar.bz2#aece73b1c2f00e86cb9e4f16fab91d96 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_4.tar.bz2#a6b5e941bc2998fbd59c84645247c2c7 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c @@ -95,12 +99,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -115,12 +117,11 @@ https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 https://conda.anaconda.org/conda-forge/linux-64/python-3.10.5-h582c2e5_0_cpython.tar.bz2#ccbed83043b9b7b5693164591317f327 @@ -157,7 +158,7 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2-pyhd8ed1ab_0.tar.bz2#dc99e49653ff49ff64a4a3d698124ba4 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -176,7 +177,7 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 @@ -186,11 +187,11 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_1.tar.bz2#54eaedc01b1aee271e4f150d03ffc8b0 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py310h53a5b5f_0.tar.bz2#9b86a46d908354fe7f91da24f5ea3f36 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 -https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310he619898_0.tar.bz2#5808b13c720854aaa3307c6e04cc1505 +https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310he619898_1.tar.bz2#af9690193837e58a2fd2af38dce537f4 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed @@ -199,7 +200,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.ta https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.0-py310hff52083_0.tar.bz2#3408aea4a02798a0ae7188ebf34f5803 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.3-py310hff52083_0.tar.bz2#44c851111753c9fb1e71e98a0d04c3e3 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 @@ -255,4 +256,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a \ No newline at end of file +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 69a710231e..168c6fdd59 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 69f9809771403e4c5a9828c2afa28e110c5e08858cefd26b98099c3e25a55e31 +# input_hash: 8a110789ad07590a0a46c6da77a9a2c37c2cc302c639cfb02dac7666c38afa09 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -46,7 +46,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_2.tar.bz2#99c0160c84e61348aa8eb2949b24e6d3 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 @@ -64,9 +64,10 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 -https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f @@ -74,8 +75,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa @@ -83,9 +88,8 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_3.tar.bz2#aece73b1c2f00e86cb9e4f16fab91d96 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_4.tar.bz2#a6b5e941bc2998fbd59c84645247c2c7 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c @@ -94,12 +98,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -114,12 +116,11 @@ https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce @@ -156,7 +157,7 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2-pyhd8ed1ab_0.tar.bz2#dc99e49653ff49ff64a4a3d698124ba4 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -175,7 +176,7 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 @@ -185,11 +186,11 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0. https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_1.tar.bz2#7a65afac627e81e2d4c1fef44409dbf5 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_2.tar.bz2#fccce86e5fc8183bf2658ac9bfc535b4 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py38h3a7f9d9_0.tar.bz2#90cf44c14b2bfe19ce7b875979b90cb9 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 -https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h0ee0e06_0.tar.bz2#7ef61f9084bda76b2f7a668ec5d1499a +https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h0ee0e06_1.tar.bz2#b3738ab363ee3311b4624f9a5abc424c https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 @@ -198,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.0-py38h578d9bd_0.tar.bz2#2eb47b2a49ec80cda2603f0eafd2b4ac +https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.3-py38h578d9bd_0.tar.bz2#e1813849f70436eab3afa951d5a12bb7 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 @@ -254,4 +255,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a \ No newline at end of file +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index da35de3e95..ef6cdc9b9e 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: c86bd1559813a6ad95d6a7365d108c917dbb11f10351ebd3c0a0faa67d53d400 +# input_hash: 850fe195d775cf7364300e20b0da20712bd1e1ce31f156011cba771e908fae9b @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -47,7 +47,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d -https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.3-h166bdaf_2.tar.bz2#99c0160c84e61348aa8eb2949b24e6d3 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 @@ -65,9 +65,10 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 -https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.5-h516909a_1.tar.bz2#33f601066901f3e1a85af3522a8113f9 +https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f @@ -75,8 +76,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa @@ -84,9 +89,8 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_3.tar.bz2#aece73b1c2f00e86cb9e4f16fab91d96 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_4.tar.bz2#a6b5e941bc2998fbd59c84645247c2c7 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h10796ff_3.tar.bz2#21a8d66dc17f065023b33145c42652fe https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c @@ -95,12 +99,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-ha56f1ee_2.tar.bz2#6ab4eaa11ff01801cffca0a27489dc04 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_3.tar.bz2#b6f4a0850ba620030a48b88c25497aaa -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_0.tar.bz2#2d9e11c1183391882e95fec81d0d71c8 +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_0.tar.bz2#2cf5cb4cd116a78e639977eb61ad9987 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -115,12 +117,11 @@ https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-hf5a7f15_1.tar.bz2#005557d6df00af70e438bcd532ce2304 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.3-h522a892_1.tar.bz2#424fabaabbfb6ec60492d3aba900f513 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 https://conda.anaconda.org/conda-forge/linux-64/python-3.9.13-h9a8a25e_0_cpython.tar.bz2#69bc307cc4d7396c5fccb26bbcc9c379 @@ -157,7 +158,7 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.1-pyhd8ed1ab_0.tar.bz2#b87d66d6d3991d988fb31510c95a9267 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2-pyhd8ed1ab_0.tar.bz2#dc99e49653ff49ff64a4a3d698124ba4 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -176,7 +177,7 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1011.tar.bz2#0b53c7f7af13244374ef7226bac3f843 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c @@ -186,11 +187,11 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0. https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_1.tar.bz2#a0475f4b801777d641058ca5ff08f07f +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_2.tar.bz2#b809706525f081610469169b671b2600 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py39hba7629e_0.tar.bz2#ee8dff1fb28e0e2c458e845aae9d915a https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 -https://conda.anaconda.org/conda-forge/noarch/partd-1.2.0-pyhd8ed1ab_0.tar.bz2#0c32f563d7f22e3a34c95cad8cc95651 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hae2aec6_0.tar.bz2#ea9760ab3700e63809de6ae71a11664b +https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hae2aec6_1.tar.bz2#b1a83be945a712e6d2943c76d0d15dfd https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef @@ -199,7 +200,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.0-py39hf3d152e_0.tar.bz2#d6d3aac692f72414b4fd25fdbb9688ea +https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.3-py39hf3d152e_0.tar.bz2#e4b47ae0a0025ae73212c11bf9555955 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f @@ -255,4 +256,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 -https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a \ No newline at end of file +https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a From 3484aa1bb6a9ec113e749edd2b69c6c9db27a101 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 16 Aug 2022 13:17:59 +0200 Subject: [PATCH 191/319] Revert Cell.__hash__ fix (#4874) * Revert Cell.__hash__ fix * add whatsnew entry * update comments * Revert #4701 cell nan equality Co-authored-by: lbdreyer --- docs/src/whatsnew/latest.rst | 8 +--- lib/iris/coords.py | 48 +------------------ .../tests/integration/merge/test_merge.py | 29 +---------- lib/iris/tests/unit/coords/test_Cell.py | 26 ---------- 4 files changed, 4 insertions(+), 107 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 411298fc50..075b122cd3 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -115,12 +115,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`) @@ -273,7 +267,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`) diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 3c28fa591f..0a1aecb983 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -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. @@ -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 @@ -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): """ @@ -1384,27 +1353,14 @@ def __eq__(self, other): 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] ) diff --git a/lib/iris/tests/integration/merge/test_merge.py b/lib/iris/tests/integration/merge/test_merge.py index 8c221d7201..f5f92a7a7d 100644 --- a/lib/iris/tests/integration/merge/test_merge.py +++ b/lib/iris/tests/integration/merge/test_merge.py @@ -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 @@ -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() diff --git a/lib/iris/tests/unit/coords/test_Cell.py b/lib/iris/tests/unit/coords/test_Cell.py index 2910fde9e3..650f9ded6c 100644 --- a/lib/iris/tests/unit/coords/test_Cell.py +++ b/lib/iris/tests/unit/coords/test_Cell.py @@ -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): From 456c430d4dfa0043fc36307645612b4b5741a55a Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 17 Aug 2022 11:29:08 +0100 Subject: [PATCH 192/319] Add whats new entry for 4734 (#4908) --- docs/src/whatsnew/latest.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 075b122cd3..52495546a3 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -250,6 +250,9 @@ This document explains the changes made to Iris for this release #. `@wjbenfold`_ made :func:`iris.tests.stock.simple_1d` respect the ``with_bounds`` argument. (:pull:`4658`) +#. `@lbdreyer`_ replaced `nose`_ with `pytest`_ as Iris' test runner. + (:pull:`4734`) + #. `@bjlittle`_ and `@trexfeathers`_ (reviewer) migrated to GitHub Actions for Continuous-Integration. (:pull:`4503`) @@ -293,5 +296,7 @@ This document explains the changes made to Iris for this release .. _Calendar: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#calendar .. _Cell Boundaries: https://cfconventions.org/Data/cf-conventions/cf-conventions-1.9/cf-conventions.html#cell-boundaries +.. _nose: https://nose.readthedocs.io .. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html +.. _pytest: https://docs.pytest.org .. _setuptools-scm: https://github.com/pypa/setuptools_scm \ No newline at end of file From 2fa5e6dfd6d4eb1ff4f79598e1e04643b5f0df44 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 17 Aug 2022 14:44:58 +0100 Subject: [PATCH 193/319] Extend new_axis to reshape _DimensionalMetadata objects (#4896) * extend iris.util.new_axis * add tests * fix tests * Rewrite new_axis; extra stock cube; cube _dimensional_metadata utility and tests * Add new_axis tests * Convert to pytest * Rename broadcast -> expand; review actions * Add whatsnew entry Co-authored-by: stephen.worsley --- docs/src/whatsnew/latest.rst | 6 + lib/iris/cube.py | 24 ++ lib/iris/tests/stock/__init__.py | 37 ++- lib/iris/tests/unit/cube/test_Cube.py | 61 +++++ lib/iris/tests/unit/util/test_new_axis.py | 285 ++++++++++++++++------ lib/iris/util.py | 96 ++++++-- 6 files changed, 409 insertions(+), 100 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 52495546a3..09bc89c819 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -80,6 +80,12 @@ This document explains the changes made to Iris for this release to provide lazy evaluation and greater flexibility with respect to input types. (:issue:`3936`, :pull:`4889`) +#. `@stephenworsley`_ and `@lbdreyer`_ added a new kwarg ``expand_extras`` to + :func:`iris.util.new_axis` which can be used to specify instances of + :class:`~iris.coords.AuxCoord`, :class:`~iris.coords.CellMeasure` and + :class:`~iris.coords.AncillaryVariable` which should also be expanded to map + to the new axis. (:pull:`4896`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/cube.py b/lib/iris/cube.py index cdfc656914..8879ade621 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -1012,6 +1012,30 @@ def _names(self): """ return self._metadata_manager._names + def _dimensional_metadata(self, name_or_dimensional_metadata): + """ + Return a single _DimensionalMetadata instance that matches the given + name_or_dimensional_metadata. If one is not found, raise an error. + + """ + found_item = None + for cube_method in [ + self.coord, + self.cell_measure, + self.ancillary_variable, + ]: + try: + found_item = cube_method(name_or_dimensional_metadata) + if found_item: + break + except KeyError: + pass + if not found_item: + raise KeyError( + f"{name_or_dimensional_metadata} was not found in {self}." + ) + return found_item + def is_compatible(self, other, ignore=None): """ Return whether the cube is compatible with another. diff --git a/lib/iris/tests/stock/__init__.py b/lib/iris/tests/stock/__init__.py index 4310906c79..632dc95e20 100644 --- a/lib/iris/tests/stock/__init__.py +++ b/lib/iris/tests/stock/__init__.py @@ -20,7 +20,13 @@ from iris.coord_systems import GeogCS, RotatedGeogCS import iris.coords import iris.coords as icoords -from iris.coords import AuxCoord, CellMethod, DimCoord +from iris.coords import ( + AncillaryVariable, + AuxCoord, + CellMeasure, + CellMethod, + DimCoord, +) from iris.cube import Cube from ._stock_2d_latlons import ( # noqa @@ -404,6 +410,35 @@ def simple_2d_w_multidim_and_scalars(): return cube +def simple_2d_w_cell_measure_ancil_var(): + """ + Returns a two dimensional cube with a CellMeasure and AncillaryVariable. + + >>> print(simple_2d_w_cell_measure_ancil_var()) + thingness / (1) (bar: 3; foo: 4) + Dimension coordinates: + bar x - + foo - x + Cell measures: + cell_area x x + Ancillary variables: + quality_flag x - + Scalar coordinates: + wibble 1 + + """ + cube = simple_2d() + cube.add_aux_coord(AuxCoord([1], long_name="wibble"), None) + cube.add_ancillary_variable( + AncillaryVariable([1, 2, 3], standard_name="quality_flag"), 0 + ) + cube.add_cell_measure( + CellMeasure(np.arange(12).reshape(3, 4), standard_name="cell_area"), + (0, 1), + ) + return cube + + def hybrid_height(): """ Returns a two-dimensional (Z, X), hybrid-height cube. diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index ea4a200c5c..944d216a30 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -15,6 +15,7 @@ from cf_units import Unit import numpy as np import numpy.ma as ma +import pytest from iris._lazy_data import as_lazy_data import iris.analysis @@ -2875,5 +2876,65 @@ def test_cell_method_correct_order(self): self.assertTrue(cube1 == cube2) +class Test__dimensional_metadata: + @pytest.fixture + def cube(self): + return stock.simple_2d_w_cell_measure_ancil_var() + + def test_not_found(self, cube): + with pytest.raises(KeyError, match="was not found in"): + cube._dimensional_metadata("grid_latitude") + + def test_dim_coord_name_found(self, cube): + res = cube._dimensional_metadata("bar") + assert res == cube.coord("bar") + + def test_dim_coord_instance_found(self, cube): + res = cube._dimensional_metadata(cube.coord("bar")) + assert res == cube.coord("bar") + + def test_aux_coord_name_found(self, cube): + res = cube._dimensional_metadata("wibble") + assert res == cube.coord("wibble") + + def test_aux_coord_instance_found(self, cube): + res = cube._dimensional_metadata(cube.coord("wibble")) + assert res == cube.coord("wibble") + + def test_cell_measure_name_found(self, cube): + res = cube._dimensional_metadata("cell_area") + assert res == cube.cell_measure("cell_area") + + def test_cell_measure_instance_found(self, cube): + res = cube._dimensional_metadata(cube.cell_measure("cell_area")) + assert res == cube.cell_measure("cell_area") + + def test_ancillary_var_name_found(self, cube): + res = cube._dimensional_metadata("quality_flag") + assert res == cube.ancillary_variable("quality_flag") + + def test_ancillary_var_instance_found(self, cube): + res = cube._dimensional_metadata( + cube.ancillary_variable("quality_flag") + ) + assert res == cube.ancillary_variable("quality_flag") + + def test_two_with_same_name(self, cube): + # If a cube has two _DimensionalMetadata objects with the same name, the + # current behaviour results in _dimensional_metadata returning the first + # one it finds. + cube.cell_measure("cell_area").rename("wibble") + res = cube._dimensional_metadata("wibble") + assert res == cube.coord("wibble") + + def test_two_with_same_name_specify_instance(self, cube): + # The cube has two _DimensionalMetadata objects with the same name so + # we specify the _DimensionalMetadata instance to ensure it returns the + # correct one. + cube.cell_measure("cell_area").rename("wibble") + res = cube._dimensional_metadata(cube.cell_measure("wibble")) + assert res == cube.cell_measure("wibble") + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/util/test_new_axis.py b/lib/iris/tests/unit/util/test_new_axis.py index 74b59cc7ec..d81f2c40d7 100644 --- a/lib/iris/tests/unit/util/test_new_axis.py +++ b/lib/iris/tests/unit/util/test_new_axis.py @@ -7,95 +7,131 @@ # Import iris.tests first so that some things can be initialised before # importing anything else. -import iris.tests as tests # isort:skip +# isort: off +import iris.tests as tests # noqa + +# isort: on import copy -import unittest import numpy as np +import pytest import iris from iris._lazy_data import as_lazy_data +from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord +from iris.cube import Cube import iris.tests.stock as stock from iris.util import new_axis -class Test(tests.IrisTest): - def setUp(self): - self.data = np.array([[1, 2], [1, 2]]) - self.cube = iris.cube.Cube(self.data) - lat = iris.coords.DimCoord([1, 2], standard_name="latitude") - lon = iris.coords.DimCoord([1, 2], standard_name="longitude") - +class Test: + @pytest.fixture + def stock_cube(self): + cube = stock.simple_2d_w_cell_measure_ancil_var() time = iris.coords.DimCoord([1], standard_name="time") - wibble = iris.coords.AuxCoord([1], long_name="wibble") - - self.cube.add_dim_coord(lat, 0) - self.cube.add_dim_coord(lon, 1) - self.cube.add_aux_coord(time, None) - self.cube.add_aux_coord(wibble, None) - - self.coords = {"lat": lat, "lon": lon, "time": time, "wibble": wibble} + cube.add_aux_coord(time, None) + cube.coord("wibble").bounds = np.array([0, 2]).reshape((1, 2)) + return cube def _assert_cube_notis(self, cube_a, cube_b): + assert cube_a.metadata is not cube_b.metadata + for coord_a, coord_b in zip(cube_a.coords(), cube_b.coords()): - self.assertIsNot(coord_a, coord_b) + assert coord_a is not coord_b + + for av_a, av_b in zip( + cube_a.ancillary_variables(), cube_b.ancillary_variables() + ): + assert av_a is not av_b - self.assertIsNot(cube_a.metadata, cube_b.metadata) + for cm_a, cm_b in zip(cube_a.cell_measures(), cube_b.cell_measures()): + assert cm_a is not cm_b for factory_a, factory_b in zip( cube_a.aux_factories, cube_b.aux_factories ): - self.assertIsNot(factory_a, factory_b) + assert factory_a is not factory_b - def test_no_coord(self): + def test_promote_no_coord(self, stock_cube): # Providing no coordinate to promote. - res = new_axis(self.cube) - com = iris.cube.Cube(self.data[None]) - com.add_dim_coord(self.coords["lat"].copy(), 1) - com.add_dim_coord(self.coords["lon"].copy(), 2) - com.add_aux_coord(self.coords["time"].copy(), None) - com.add_aux_coord(self.coords["wibble"].copy(), None) + result = new_axis(stock_cube) + expected = iris.cube.Cube( + stock_cube.data[None], long_name="thingness", units="1" + ) + expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) + expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) + expected.add_aux_coord(stock_cube.coord("time").copy(), None) + expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) + expected.add_ancillary_variable( + stock_cube.ancillary_variable("quality_flag"), 1 + ) + expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) - self.assertEqual(res, com) - self._assert_cube_notis(res, self.cube) + assert result == expected + self._assert_cube_notis(result, stock_cube) - def test_scalar_dimcoord(self): + def test_promote_scalar_dimcoord(self, stock_cube): # Providing a scalar coordinate to promote. - res = new_axis(self.cube, "time") - com = iris.cube.Cube(self.data[None]) - com.add_dim_coord(self.coords["lat"].copy(), 1) - com.add_dim_coord(self.coords["lon"].copy(), 2) - com.add_aux_coord(self.coords["time"].copy(), 0) - com.add_aux_coord(self.coords["wibble"].copy(), None) + result = new_axis(stock_cube, "time") + expected = iris.cube.Cube( + stock_cube.data[None], long_name="thingness", units="1" + ) + expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) + expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) + expected.add_aux_coord(stock_cube.coord("time").copy(), 0) + expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) + expected.add_ancillary_variable( + stock_cube.ancillary_variable("quality_flag"), 1 + ) + expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) - self.assertEqual(res, com) - self._assert_cube_notis(res, self.cube) + assert result == expected + # Explicitly check time has been made a cube dim coord as cube equality + # does not check this. + assert result.coord("time") in [ + item[0] for item in result._dim_coords_and_dims + ] + self._assert_cube_notis(result, stock_cube) - def test_scalar_auxcoord(self): + def test_promote_scalar_auxcoord(self, stock_cube): # Providing a scalar coordinate to promote. - res = new_axis(self.cube, "wibble") - com = iris.cube.Cube(self.data[None]) - com.add_dim_coord(self.coords["lat"].copy(), 1) - com.add_dim_coord(self.coords["lon"].copy(), 2) - com.add_aux_coord(self.coords["time"].copy(), None) - com.add_aux_coord(self.coords["wibble"].copy(), 0) + result = new_axis(stock_cube, "wibble") + expected = iris.cube.Cube( + stock_cube.data[None], long_name="thingness", units="1" + ) + expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) + expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) + expected.add_aux_coord(stock_cube.coord("time").copy(), None) + expected.add_aux_coord(stock_cube.coord("wibble").copy(), 0) + expected.add_ancillary_variable( + stock_cube.ancillary_variable("quality_flag"), 1 + ) + expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) + + assert result == expected + # Explicitly check wibble has been made a cube dim coord as cube + # equality does not check this. + assert result.coord("wibble") in [ + item[0] for item in result._dim_coords_and_dims + ] + self._assert_cube_notis(result, stock_cube) - self.assertEqual(res, com) - self._assert_cube_notis(res, self.cube) + def test_promote_non_scalar(self, stock_cube): + # Provide a dimensional coordinate which is not scalar + with pytest.raises(ValueError, match="is not a scalar coordinate."): + new_axis(stock_cube, "foo") def test_maint_factory(self): # Ensure that aux factory persists. data = np.arange(12, dtype="i8").reshape((3, 4)) - orography = iris.coords.AuxCoord( + orography = AuxCoord( [10, 25, 50, 5], standard_name="surface_altitude", units="m" ) - model_level = iris.coords.AuxCoord( - [2, 1, 0], standard_name="model_level_number" - ) + model_level = AuxCoord([2, 1, 0], standard_name="model_level_number") - level_height = iris.coords.DimCoord( + level_height = DimCoord( [100, 50, 10], long_name="level_height", units="m", @@ -103,7 +139,7 @@ def test_maint_factory(self): bounds=[[150, 75], [75, 20], [20, 0]], ) - sigma = iris.coords.AuxCoord( + sigma = AuxCoord( [0.8, 0.9, 0.95], long_name="sigma", bounds=[[0.7, 0.85], [0.85, 0.97], [0.97, 1.0]], @@ -113,7 +149,7 @@ def test_maint_factory(self): level_height, sigma, orography ) - cube = iris.cube.Cube( + cube = Cube( data, standard_name="air_temperature", units="K", @@ -122,7 +158,7 @@ def test_maint_factory(self): aux_factories=[hybrid_height], ) - com = iris.cube.Cube( + com = Cube( data[None], standard_name="air_temperature", units="K", @@ -136,7 +172,7 @@ def test_maint_factory(self): ) res = new_axis(cube) - self.assertEqual(res, com) + assert res == com self._assert_cube_notis(res, cube) # Check that factory dependencies are actual coords within the cube. @@ -145,23 +181,14 @@ def test_maint_factory(self): deps = factory.dependencies for dep_name, dep_coord in deps.items(): coord_name = dep_coord.name() - msg = ( - "Factory dependency {!r} is a coord named {!r}, " - "but it is *not* the coord of that name in the new cube." - ) - self.assertIs( - dep_coord, - res.coord(coord_name), - msg.format(dep_name, coord_name), - ) - - def test_lazy_data(self): - cube = iris.cube.Cube(as_lazy_data(self.data)) - cube.add_aux_coord(iris.coords.DimCoord([1], standard_name="time")) - res = new_axis(cube, "time") - self.assertTrue(cube.has_lazy_data()) - self.assertTrue(res.has_lazy_data()) - self.assertEqual(res.shape, (1,) + cube.shape) + assert dep_coord is res.coord(coord_name) + + def test_lazy_cube_data(self, stock_cube): + stock_cube.data = as_lazy_data(stock_cube.data) + res = new_axis(stock_cube) + assert stock_cube.has_lazy_data() + assert res.has_lazy_data() + assert res.shape == (1,) + stock_cube.shape def test_masked_unit_array(self): cube = stock.simple_3d_mask() @@ -170,8 +197,114 @@ def test_masked_unit_array(self): test_cube = new_axis(test_cube, "latitude") data_shape = test_cube.data.shape mask_shape = test_cube.data.mask.shape - self.assertEqual(data_shape, mask_shape) + assert data_shape == mask_shape + + def test_expand_scalar_coord(self, stock_cube): + result = new_axis(stock_cube, "time", expand_extras=["wibble"]) + + expected = iris.cube.Cube( + stock_cube.data[None], long_name="thingness", units="1" + ) + expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) + expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) + expected.add_aux_coord(stock_cube.coord("time").copy(), 0) + expected.add_aux_coord(stock_cube.coord("wibble").copy(), 0) + expected.add_ancillary_variable( + stock_cube.ancillary_variable("quality_flag"), 1 + ) + expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) + + assert result == expected + self._assert_cube_notis(result, stock_cube) + + def test_expand_scalar_coord_lazy_points(self, stock_cube): + stock_cube.coord("wibble").points = as_lazy_data( + stock_cube.coord("wibble").points + ) + result = new_axis(stock_cube, "time", expand_extras=["wibble"]) + assert stock_cube.coord("wibble").has_lazy_points() + assert result.coord("wibble").has_lazy_points() + assert ( + result.coord("wibble").points.shape + == stock_cube.coord("wibble").points.shape + ) + + def test_expand_scalar_coord_lazy_bounds(self, stock_cube): + stock_cube.coord("wibble").bounds = as_lazy_data(np.array([[0, 2]])) + result = new_axis(stock_cube, "time", expand_extras=["wibble"]) + assert stock_cube.coord("wibble").has_lazy_bounds() + assert result.coord("wibble").has_lazy_bounds() + assert ( + result.coord("wibble").bounds.shape + == stock_cube.coord("wibble").bounds.shape + ) + + def test_expand_cell_measure(self, stock_cube): + result = new_axis(stock_cube, "time", expand_extras=["cell_area"]) + + expected = iris.cube.Cube( + stock_cube.data[None], long_name="thingness", units="1" + ) + expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) + expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) + expected.add_aux_coord(stock_cube.coord("time").copy(), 0) + expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) + expected.add_ancillary_variable( + stock_cube.ancillary_variable("quality_flag"), 1 + ) + + expected_cm = CellMeasure( + stock_cube.cell_measure("cell_area").data[None], + standard_name="cell_area", + ) + expected.add_cell_measure(expected_cm, (0, 1, 2)) + assert result == expected + self._assert_cube_notis(result, stock_cube) + + def test_expand_ancil_var(self, stock_cube): + result = new_axis(stock_cube, "time", expand_extras=["quality_flag"]) + + expected = iris.cube.Cube( + stock_cube.data[None], long_name="thingness", units="1" + ) + expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) + expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) + expected.add_aux_coord(stock_cube.coord("time").copy(), 0) + expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) + expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) + + expected_av = AncillaryVariable( + stock_cube.ancillary_variable("quality_flag").data[None], + standard_name="quality_flag", + ) + + expected.add_ancillary_variable(expected_av, (0, 1)) + + assert result == expected + self._assert_cube_notis(result, stock_cube) + + def test_expand_multiple(self, stock_cube): + result = new_axis( + stock_cube, "time", expand_extras=["wibble", "cell_area"] + ) + + expected = iris.cube.Cube( + stock_cube.data[None], long_name="thingness", units="1" + ) + expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) + expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) + expected.add_aux_coord(stock_cube.coord("time").copy(), 0) + expected.add_aux_coord(stock_cube.coord("wibble").copy(), 0) + expected.add_ancillary_variable( + stock_cube.ancillary_variable("quality_flag"), 1 + ) + + expected_cm = CellMeasure( + stock_cube.cell_measure("cell_area").data[None], + standard_name="cell_area", + ) + expected.add_cell_measure(expected_cm, (0, 1, 2)) -if __name__ == "__main__": - unittest.main() + assert result == expected + self._assert_cube_notis(result, stock_cube) diff --git a/lib/iris/util.py b/lib/iris/util.py index 42f06c5d9c..7f57088bcd 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -1096,7 +1096,7 @@ def format_array(arr): return result -def new_axis(src_cube, scalar_coord=None): +def new_axis(src_cube, scalar_coord=None, expand_extras=[]): """ Create a new axis as the leading dimension of the cube, promoting a scalar coordinate if specified. @@ -1111,9 +1111,16 @@ def new_axis(src_cube, scalar_coord=None): * scalar_coord (:class:`iris.coord.Coord` or 'string') Scalar coordinate to promote to a dimension coordinate. + * expand_extras (list) + List of auxiliary coordinates, ancillary variables and cell measures + that will be expanded so that they map to the new dimension as well + as the existing dimensions. + Returns: A new :class:`iris.cube.Cube` instance with one extra leading dimension - (length 1). + (length 1). Chosen auxiliary coordinates, cell measures and ancillary + variables will also be given an additional dimension, associated with + the leading dimension of the cube. For example:: @@ -1122,40 +1129,83 @@ def new_axis(src_cube, scalar_coord=None): >>> ncube = iris.util.new_axis(cube, 'time') >>> ncube.shape (1, 360, 360) - """ - from iris.coords import DimCoord - from iris.cube import Cube + + def _reshape_data_array(data_manager): + # Indexing numpy arrays requires loading deferred data here returning a + # copy of the data with a new leading dimension. + # If the data of the source cube (or values of the dimensional metadata + # object) is a Masked Constant, it is changed here to a Masked Array to + # allow the mask to gain an extra dimension with the data. + if data_manager.has_lazy_data(): + new_data = data_manager.lazy_data()[None] + else: + if isinstance(data_manager.data, ma.core.MaskedConstant): + new_data = ma.array([np.nan], mask=[True]) + else: + new_data = data_manager.data[None] + return new_data + + def _handle_dimensional_metadata( + cube, dm_item, cube_add_method, expand_extras + ): + cube_dims = dm_item.cube_dims(cube) + if dm_item in expand_extras: + if cube_dims == (): + new_dm_item, new_dims = dm_item.copy(), 0 + else: + new_dims = np.concatenate([(0,), np.array(cube_dims) + 1]) + new_values = _reshape_data_array(dm_item._values_dm) + kwargs = dm_item.metadata._asdict() + new_dm_item = dm_item.__class__(new_values, **kwargs) + try: + if dm_item.has_bounds(): + new_dm_item.bounds = _reshape_data_array( + dm_item._bounds_dm + ) + except AttributeError: + pass + else: + new_dims = np.array(cube_dims) + 1 + new_dm_item = dm_item.copy() + + cube_add_method(new_dm_item, new_dims) if scalar_coord is not None: scalar_coord = src_cube.coord(scalar_coord) + if not scalar_coord.shape == (1,): + emsg = scalar_coord.name() + "is not a scalar coordinate." + raise ValueError(emsg) - # Indexing numpy arrays requires loading deferred data here returning a - # copy of the data with a new leading dimension. - # If the source cube is a Masked Constant, it is changed here to a Masked - # Array to allow the mask to gain an extra dimension with the data. - if src_cube.has_lazy_data(): - new_cube = Cube(src_cube.lazy_data()[None]) - else: - if isinstance(src_cube.data, ma.core.MaskedConstant): - new_data = ma.array([np.nan], mask=[True]) - else: - new_data = src_cube.data[None] - new_cube = Cube(new_data) + expand_extras = [ + src_cube._dimensional_metadata(item) for item in expand_extras + ] + new_cube = iris.cube.Cube(_reshape_data_array(src_cube._data_manager)) new_cube.metadata = src_cube.metadata + for coord in src_cube.dim_coords: + coord_dims = np.array(src_cube.coord_dims(coord)) + 1 + new_cube.add_dim_coord(coord.copy(), coord_dims) + for coord in src_cube.aux_coords: if scalar_coord and scalar_coord == coord: - dim_coord = DimCoord.from_coord(coord) + dim_coord = iris.coords.DimCoord.from_coord(coord) new_cube.add_dim_coord(dim_coord, 0) else: - dims = np.array(src_cube.coord_dims(coord)) + 1 - new_cube.add_aux_coord(coord.copy(), dims) + _handle_dimensional_metadata( + src_cube, coord, new_cube.add_aux_coord, expand_extras + ) - for coord in src_cube.dim_coords: - coord_dims = np.array(src_cube.coord_dims(coord)) + 1 - new_cube.add_dim_coord(coord.copy(), coord_dims) + for cm in src_cube.cell_measures(): + _handle_dimensional_metadata( + src_cube, cm, new_cube.add_cell_measure, expand_extras + ) + + for av in src_cube.ancillary_variables(): + _handle_dimensional_metadata( + src_cube, av, new_cube.add_ancillary_variable, expand_extras + ) nonderived_coords = src_cube.dim_coords + src_cube.aux_coords coord_mapping = { From 22fe29c0e1d3b8194b1081d009818fbf13855ebb Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Wed, 17 Aug 2022 15:29:43 +0100 Subject: [PATCH 194/319] Standard names table update (v79) (#4910) * update cf standard name table * add whatsnew * fix whatsnew --- docs/src/whatsnew/latest.rst | 3 + etc/cf-standard-name-table.xml | 2552 +++++++++++++++++--------------- 2 files changed, 1370 insertions(+), 1185 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 09bc89c819..c111110bd1 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -86,6 +86,9 @@ This document explains the changes made to Iris for this release :class:`~iris.coords.AncillaryVariable` which should also be expanded to map to the new axis. (:pull:`4896`) +#. `@stephenworsley`_ updated to the latest CF Standard Names Table ``v79`` + (19 March 2022). (:pull:`4910`) + 🐛 Bugs Fixed ============= diff --git a/etc/cf-standard-name-table.xml b/etc/cf-standard-name-table.xml index bd76168192..9c5fcd9cf0 100644 --- a/etc/cf-standard-name-table.xml +++ b/etc/cf-standard-name-table.xml @@ -1,7 +1,7 @@ - 78 - 2021-09-21T11:55:06Z + 79 + 2022-03-19T15:25:54Z Centre for Environmental Data Analysis support@ceda.ac.uk @@ -8014,6 +8014,20 @@ The phrase "magnitude_of_X" means magnitude of a vector X. The surface called "surface" means the lower boundary of the atmosphere. "Surface stress" means the shear stress (force per unit area) exerted by the wind at the surface. A downward stress is a downward flux of momentum. Over large bodies of water, wind stress can drive near-surface currents. "Downward" indicates a vector component which is positive when directed downward (negative upward). + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of 19’-butanoyloxyfucoxanthin is C46H64O8. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/BUTAXXXX/1/. + + + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of 19'-hexanoyloxyfucoxanthin is C48H68O8. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/HEXAXXXX/2/. + + kg m-3 @@ -8028,6 +8042,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for aceto-nitrile is CH3CN. The IUPAC name for aceto-nitrile is ethanenitrile. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/ATPXZZDZ/2/. + + kg m-3 @@ -8042,6 +8063,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. Alkenes are unsaturated hydrocarbons as they contain chemical double bonds between adjacent carbon atoms. Alkenes contain only hydrogen and carbon combined in the general proportions C(n)H(2n); "alkenes" is the term used in standard names to describe the group of chemical species having this common structure that are represented within a given model. The list of individual species that are included in a quantity having a group chemical standard name can vary between models. Where possible, the data variable should be accompanied by a complete description of the species represented, for example, by using a comment attribute. Standard names exist for some individual alkene species, e.g., ethene and propene. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of alpha-carotene is C40H56. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/BECAXXP1/2/. + + kg m-3 @@ -8112,6 +8140,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for benzene is C6H6. Benzene is the simplest aromatic hydrocarbon and has a ring structure consisting of six carbon atoms joined by alternating single and double chemical bonds. Each carbon atom is additionally bonded to one hydrogen atom. There are standard names that refer to aromatic_compounds as a group, as well as those for individual species. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of beta-carotene is C40H56. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/BBCAXXP1/2/. + + kg m-3 @@ -8217,6 +8252,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula of carbon tetrachloride is CCl4. The IUPAC name for carbon tetrachloride is tetrachloromethane. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Carotene" refers to the sum of all forms of the carotenoid pigment carotene. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/CAROXXXX/1/. + + kg m-3 @@ -8287,6 +8329,41 @@ 'Mass concentration' means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. Chlorophylls are the green pigments found in most plants, algae and cyanobacteria; their presence is essential for photosynthesis to take place. There are several different forms of chlorophyll that occur naturally. All contain a chlorin ring (chemical formula C20H16N4) which gives the green pigment and a side chain whose structure varies. The naturally occurring forms of chlorophyll contain between 35 and 55 carbon atoms. Chlorophyll-a is the most commonly occurring form of natural chlorophyll. The chemical formula of chlorophyll-a is C55H72O5N4Mg. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". Chlorophylls are the green pigments found in most plants, algae and cyanobacteria; their presence is essential for photosynthesis to take place. There are several different forms of chlorophyll that occur naturally. All contain a chlorin ring (chemical formula C20H16N4) which gives the green pigment and a side chain whose structure varies. The naturally occurring forms of chlorophyll contain between 35 and 55 carbon atoms. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/CHLBXXPX/2/. + + + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". Chlorophylls are the green pigments found in most plants, algae and cyanobacteria; their presence is essential for photosynthesis to take place. There are several different forms of chlorophyll that occur naturally. All contain a chlorin ring (chemical formula C20H16N4) which gives the green pigment and a side chain whose structure varies. The naturally occurring forms of chlorophyll contain between 35 and 55 carbon atoms. Chlorophyll c1c2 (sometimes written c1-c2 or c1+c2) means the sum of chlorophyll c1 and chlorophyll c2. The chemical formula of chlorophyll c1 is C35H30MgN4O5, and chlorophyll c2 is C35H28MgN4O5. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/CHLC12PX/3/. + + + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". Chlorophylls are the green pigments found in most plants, algae and cyanobacteria; their presence is essential for photosynthesis to take place. There are several different forms of chlorophyll that occur naturally. All contain a chlorin ring (chemical formula C20H16N4) which gives the green pigment and a side chain whose structure varies. The naturally occurring forms of chlorophyll contain between 35 and 55 carbon atoms. The chemical formula of chlorophyll c3 is C36H44MgN4O7. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/CHLC03PX/2/. + + + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". Chlorophylls are the green pigments found in most plants, algae and cyanobacteria; their presence is essential for photosynthesis to take place. There are several different forms of chlorophyll that occur naturally. All contain a chlorin ring (chemical formula C20H16N4) which gives the green pigment and a side chain whose structure varies. The naturally occurring forms of chlorophyll contain between 35 and 55 carbon atoms. Chlorophyll-c means chlorophyll c1+c2+c3. The chemical formula of chlorophyll c1 is C35H30MgN4O5, and chlorophyll c2 is C35H28MgN4O5. The chemical formula of chlorophyll c3 is C36H44MgN4O7. + + + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of chlorophyllide-a is C35H34MgN4O5. + + kg m-3 @@ -8322,6 +8399,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. Condensed water means liquid and ice. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of diadinoxanthin is C40H54O3. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/DIADXXXX/2/. + + kg m-3 @@ -8378,6 +8462,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for dinitrogen pentoxide is N2O5. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". + + kg m-3 @@ -8455,6 +8546,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for formic acid is HCOOH. The IUPAC name for formic acid is methanoic acid. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of fucoxanthin is C42H58O6. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/FUCXZZZZ/2/. + + kg m-3 @@ -8637,6 +8735,13 @@ Mass concentration means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The mass concentration of liquid water takes into account all cloud droplets and liquid precipitation regardless of drop size or fall speed. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of lutein is C40H56O2. + + kg m-3 @@ -8707,6 +8812,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for molecular hydrogen is H2. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". + + kg m-3 @@ -8833,6 +8945,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol takes up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the aerosol. "Dry aerosol particles" means aerosol particles without any water uptake. The term "particulate_organic_matter_dry_aerosol" means all particulate organic matter dry aerosol except elemental carbon. It is the sum of primary_particulate_organic_matter_dry_aerosol and secondary_particulate_organic_matter_dry_aerosol. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/PERDXXXX/2/. + + kg m-3 @@ -8861,6 +8980,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. It means the ratio of the mass of X to the mass of Y (including X). A chemical species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". Petroleum hydrocarbons are compounds containing just carbon and hydrogen originating from the fossil fuel crude oil. + + kg m-3 + + + Concentration of phaeopigment per unit volume of the water body, where the filtration size or collection method is unspecified (equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/. "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". Phaeopigments are a group of non-photosynthetic pigments that are the degradation product of algal chlorophyll pigments. Phaeopigments contain phaeophytin, which fluoresces in response to excitation light, and phaeophorbide, which is colorless and does not fluoresce (source: https://academic.oup.com/plankt/article/24/11/1221/1505482). Phaeopigment concentration commonly increases during the development phase of marine phytoplankton blooms, and declines in the post bloom stage (source: https://www.sciencedirect.com/science/article/pii/0967063793901018). + + kg m-3 @@ -8931,6 +9057,13 @@ Mass concentration means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Aerosol" means the system of suspended liquid or solid particles in air (except cloud droplets) and their carrier gas, the air itself. Aerosol particles take up ambient water (a process known as hygroscopic growth) depending on the relative humidity and the composition of the particles. "Dry aerosol particles" means aerosol particles without any water uptake. "Pm2p5 aerosol" means atmospheric particulate compounds with an aerodynamic diameter of less than or equal to 2.5 micrometers. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of prasinoxanthin is C40H56O4. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/PXAPXXXX/2/. + + kg m-3 @@ -9036,6 +9169,13 @@ "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula for toluene is C6H5CH3. Toluene has the same structure as benzene, except that one of the hydrogen atoms is replaced by a methyl group. The IUPAC name for toluene is methylbenzene. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of violaxanthin is C40H56O4. + + kg m-3 @@ -9064,6 +9204,13 @@ Mass concentration means mass per unit volume and is used in the construction mass_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for xylene is C6H4C2H6. In chemistry, xylene is a generic term for a group of three isomers of dimethylbenzene. The IUPAC names for the isomers are 1,2-dimethylbenzene, 1,3-dimethylbenzene and 1,4-dimethylbenzene. Xylene is an aromatic hydrocarbon. There are standard names that refer to aromatic_compounds as a group, as well as those for individual species. + + kg m-3 + + + "Mass concentration" means mass per unit volume and is used in the construction "mass_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The chemical formula of zeaxanthin is C40H56O2. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/ZEAXXXXX/2/. + + kg m-3 @@ -10737,6 +10884,13 @@ Mole concentration means number of moles per unit volume, also called "molarity", and is used in the construction mole_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for aceto-nitrile is CH3CN. The IUPAC name for aceto-nitrile is ethanenitrile. + + mol m-3 + + + "Mole concentration" means number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/ATPXZZDZ/2/. + + mol m-3 @@ -11185,6 +11339,13 @@ Mole concentration means number of moles per unit volume, also called "molarity", and is used in the construction mole_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The concentration of any chemical species, whether particulate or dissolved, may vary with depth in the ocean. A depth profile may go through one or more local minima in concentration. The mole_concentration_of_molecular_oxygen_in_sea_water_at_shallowest_local_minimum_in_vertical_profile is the mole concentration of oxygen at the local minimum in the concentration profile that occurs closest to the sea surface. The chemical formula for molecular oxygen is O2. + + mol m-3 + + + "Mole concentration" means number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Dissolved nitrogen" means the sum of all nitrogen in solution: inorganic nitrogen (nitrite, nitrate and ammonium) plus nitrogen in carbon compounds. + + mol m-3 @@ -11199,6 +11360,20 @@ "Mole concentration" means number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Dissolved organic nitrogen" describes the nitrogen held in carbon compounds in solution. These are mostly generated by plankton excretion and decay. + + mol m-3 + + + "Mole concentration" means number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical or biological species denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". "Organic phosphorus" means phosphorus in carbon compounds. The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/ORGPDSZZ/4/. + + + + mol m-3 + + + "Mole concentration" means number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". Phosphorus means phosphorus in all chemical forms, commonly referred to as "total phosphorus". The equivalent term in the NERC P01 Parameter Usage Vocabulary may be found at http://vocab.nerc.ac.uk/collection/P01/current/TPHSDSZZ/6/. + + mol m-3 @@ -11626,6 +11801,13 @@ Mole concentration means number of moles per unit volume, also called "molarity", and is used in the construction mole_concentration_of_X_in_Y, where X is a material constituent of Y. A chemical species denoted by X may be described by a single term such as 'nitrogen' or a phrase such as 'nox_expressed_as_nitrogen'. The chemical formula for ozone is O3. + + mol m-3 + + + "Mole concentration" means number of moles per unit volume, also called "molarity", and is used in the construction "mole_concentration_of_X_in_Y", where X is a material constituent of Y. A chemical species or biological group denoted by X may be described by a single term such as "nitrogen" or a phrase such as "nox_expressed_as_nitrogen". The phrase "expressed_as" is used in the construction "A_expressed_as_B", where B is a chemical constituent of A. It means that the quantity indicated by the standard name is calculated solely with respect to the B contained in A, neglecting all other chemical constituents of A. + + mol m-3 @@ -18595,21 +18777,21 @@ Pa - "Sea surface wave radiation stress" describes the excess momentum flux caused by sea surface waves. Radiation stresses behave as a second-order tensor. "xx" indicates the component of the tensor along the grid x_ axis. + "Sea surface wave radiation stress" describes the excess momentum flux caused by sea surface waves. Radiation stresses behave as a second-order tensor. "xx" indicates the component of the tensor along the grid x_ axis. Pa - "Sea surface wave radiation stress" describes the excess momentum flux caused by sea surface waves. Radiation stresses behave as a second-order tensor. "xy" indicates the lateral contributions to x_ and y_ components of the tensor. + "Sea surface wave radiation stress" describes the excess momentum flux caused by sea surface waves. Radiation stresses behave as a second-order tensor. "xy" indicates the lateral contributions to x_ and y_ components of the tensor. Pa - "Sea surface wave radiation stress" describes the excess momentum flux caused by sea surface waves. Radiation stresses behave as a second-order tensor. "yy" indicates the component of the tensor along the grid y_ axis. + "Sea surface wave radiation stress" describes the excess momentum flux caused by sea surface waves. Radiation stresses behave as a second-order tensor. "yy" indicates the component of the tensor along the grid y_ axis. @@ -31472,16 +31654,12 @@ - - biological_taxon_lsid - - temperature_in_ground - - surface_snow_density + + biological_taxon_lsid @@ -31516,14 +31694,18 @@ tendency_of_atmosphere_mass_content_of_water_vapor_due_to_sublimation_of_surface_snow_and_ice - - atmosphere_upward_absolute_vorticity + + surface_snow_density atmosphere_upward_relative_vorticity + + atmosphere_upward_absolute_vorticity + + area_type @@ -31532,34 +31714,46 @@ area_type - - iron_growth_limitation_of_diazotrophic_phytoplankton + + mass_fraction_of_liquid_precipitation_in_air - - growth_limitation_of_diazotrophic_phytoplankton_due_to_solar_irradiance + + mass_fraction_of_liquid_precipitation_in_air tendency_of_mole_concentration_of_particulate_organic_matter_expressed_as_carbon_in_sea_water_due_to_net_primary_production_by_diazotrophic_phytoplankton - - mole_concentration_of_diazotrophic_phytoplankton_expressed_as_carbon_in_sea_water + + nitrogen_growth_limitation_of_diazotrophic_phytoplankton - - mass_fraction_of_liquid_precipitation_in_air + + net_primary_mole_productivity_of_biomass_expressed_as_carbon_by_diazotrophic_phytoplankton - - mass_fraction_of_liquid_precipitation_in_air + + net_primary_mole_productivity_of_biomass_expressed_as_carbon_by_diazotrophic_phytoplankton + + + + mole_concentration_of_diazotrophic_phytoplankton_expressed_as_carbon_in_sea_water mass_concentration_of_diazotrophic_phytoplankton_expressed_as_chlorophyll_in_sea_water + + iron_growth_limitation_of_diazotrophic_phytoplankton + + + + growth_limitation_of_diazotrophic_phytoplankton_due_to_solar_irradiance + + air_pseudo_equivalent_potential_temperature @@ -31576,64 +31770,300 @@ tendency_of_mass_fraction_of_stratiform_cloud_ice_in_air_due_to_riming_from_cloud_liquid_water - - nitrogen_growth_limitation_of_diazotrophic_phytoplankton + + sea_water_velocity_from_direction - - net_primary_mole_productivity_of_biomass_expressed_as_carbon_by_diazotrophic_phytoplankton + + sea_water_velocity_to_direction - - net_primary_mole_productivity_of_biomass_expressed_as_carbon_by_diazotrophic_phytoplankton + + sea_water_velocity_to_direction - - air_pseudo_equivalent_temperature + + integral_wrt_depth_of_product_of_salinity_and_sea_water_density - - air_equivalent_temperature + + integral_wrt_depth_of_product_of_conservative_temperature_and_sea_water_density - - atmosphere_mass_content_of_convective_cloud_liquid_water + + integral_wrt_depth_of_product_of_potential_temperature_and_sea_water_density - - effective_radius_of_cloud_liquid_water_particles_at_liquid_water_cloud_top + + volume_fraction_of_condensed_water_in_soil_at_wilting_point - - northward_heat_flux_in_air_due_to_eddy_advection + + volume_fraction_of_condensed_water_in_soil_at_field_capacity - - northward_eliassen_palm_flux_in_air + + volume_fraction_of_condensed_water_in_soil_at_critical_point - - net_primary_productivity_of_biomass_expressed_as_carbon_accumulated_in_wood + + volume_fraction_of_condensed_water_in_soil - - net_primary_productivity_of_biomass_expressed_as_carbon_accumulated_in_leaves + + product_of_lagrangian_tendency_of_air_pressure_and_specific_humidity - - net_primary_productivity_of_biomass_expressed_as_carbon + + product_of_lagrangian_tendency_of_air_pressure_and_specific_humidity - - mole_concentration_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air + + product_of_lagrangian_tendency_of_air_pressure_and_geopotential_height - - mole_concentration_of_microzooplankton_expressed_as_nitrogen_in_sea_water + + product_of_lagrangian_tendency_of_air_pressure_and_air_temperature - - mole_concentration_of_mesozooplankton_expressed_as_nitrogen_in_sea_water + + product_of_lagrangian_tendency_of_air_pressure_and_air_temperature + + + + tendency_of_sea_water_salinity_expressed_as_salt_content_due_to_parameterized_dianeutral_mixing + + + + tendency_of_sea_water_potential_temperature_expressed_as_heat_content_due_to_parameterized_dianeutral_mixing + + + + tendency_of_sea_water_conservative_temperature_expressed_as_heat_content_due_to_parameterized_dianeutral_mixing + + + + effective_radius_of_stratiform_cloud_snow_particles + + + + tendency_of_atmosphere_moles_of_cfc11 + + + + moles_of_cfc11_per_unit_mass_in_sea_water + + + + atmosphere_moles_of_cfc11 + + + + tendency_of_atmosphere_moles_of_cfc113 + + + + atmosphere_moles_of_cfc113 + + + + tendency_of_atmosphere_moles_of_cfc114 + + + + atmosphere_moles_of_cfc114 + + + + tendency_of_atmosphere_moles_of_cfc115 + + + + atmosphere_moles_of_cfc115 + + + + tendency_of_atmosphere_moles_of_cfc12 + + + + atmosphere_moles_of_cfc12 + + + + tendency_of_atmosphere_moles_of_halon1202 + + + + atmosphere_moles_of_halon1202 + + + + tendency_of_atmosphere_moles_of_halon1211 + + + + atmosphere_moles_of_halon1211 + + + + tendency_of_atmosphere_moles_of_halon1301 + + + + atmosphere_moles_of_halon1301 + + + + tendency_of_atmosphere_moles_of_halon2402 + + + + atmosphere_moles_of_halon2402 + + + + tendency_of_atmosphere_moles_of_hcc140a + + + + atmosphere_moles_of_hcc140a + + + + tendency_of_troposphere_moles_of_hcc140a + + + + tendency_of_middle_atmosphere_moles_of_hcc140a + + + + tendency_of_troposphere_moles_of_hcfc22 + + + + tendency_of_atmosphere_moles_of_hcfc22 + + + + atmosphere_moles_of_hcfc22 + + + + tendency_of_atmosphere_number_content_of_aerosol_particles_due_to_turbulent_deposition + + + + lagrangian_tendency_of_atmosphere_sigma_coordinate + + + + lagrangian_tendency_of_atmosphere_sigma_coordinate + + + + electrical_mobility_diameter_of_ambient_aerosol_particles + + + + diameter_of_ambient_aerosol_particles + + + + mass_concentration_of_biomass_burning_dry_aerosol_particles_in_air + + + + effective_radius_of_stratiform_cloud_rain_particles + + + + effective_radius_of_stratiform_cloud_ice_particles + + + + effective_radius_of_stratiform_cloud_graupel_particles + + + + effective_radius_of_convective_cloud_snow_particles + + + + effective_radius_of_convective_cloud_rain_particles + + + + effective_radius_of_convective_cloud_ice_particles + + + + histogram_of_backscattering_ratio_in_air_over_height_above_reference_ellipsoid + + + + backscattering_ratio_in_air + + + + product_of_northward_wind_and_lagrangian_tendency_of_air_pressure + + + + product_of_eastward_wind_and_lagrangian_tendency_of_air_pressure + + + + carbon_mass_flux_into_litter_and_soil_due_to_anthropogenic_land_use_or_land_cover_change + + + + floating_ice_shelf_area_fraction + + + + atmosphere_moles_of_carbon_tetrachloride + + + + mole_fraction_of_methylglyoxal_in_air + + + + mole_fraction_of_dichlorine_peroxide_in_air + + + + atmosphere_mass_content_of_convective_cloud_liquid_water + + + + effective_radius_of_cloud_liquid_water_particles_at_liquid_water_cloud_top + + + + air_equivalent_temperature + + + + air_pseudo_equivalent_temperature + + + + mass_content_of_cloud_liquid_water_in_atmosphere_layer + + + + air_equivalent_potential_temperature + + + + number_concentration_of_stratiform_cloud_liquid_water_particles_at_stratiform_liquid_water_cloud_top + + + + number_concentration_of_convective_cloud_liquid_water_particles_at_convective_liquid_water_cloud_top @@ -31660,360 +32090,104 @@ atmosphere_mass_content_of_cloud_liquid_water - - mass_fraction_of_sulfate_dry_aerosol_particles_in_air - - - - mass_fraction_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air - - - - mass_fraction_of_ammonium_dry_aerosol_particles_in_air - - - - tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_shallow_convection - - - - tendency_of_mass_content_of_water_vapor_in_atmosphere_layer - - - - mass_content_of_cloud_ice_in_atmosphere_layer - - - - mass_concentration_of_secondary_particulate_organic_matter_dry_aerosol_particles_in_air - - - - mass_concentration_of_mercury_dry_aerosol_particles_in_air - - - - mass_concentration_of_coarse_mode_ambient_aerosol_particles_in_air - - - - sea_water_velocity_to_direction - - - - sea_water_velocity_to_direction - - - - gross_primary_productivity_of_biomass_expressed_as_carbon - - - - eastward_water_vapor_flux_in_air - - - - atmosphere_moles_of_nitric_acid_trihydrate_ambient_aerosol_particles - - - - tendency_of_middle_atmosphere_moles_of_carbon_monoxide - - - - tendency_of_atmosphere_mass_content_of_water_vapor_due_to_advection - - - - tendency_of_atmosphere_mass_content_of_water_vapor - - - - lwe_thickness_of_atmosphere_mass_content_of_water_vapor - - - - change_over_time_in_atmosphere_mass_content_of_water_due_to_advection - - - - change_over_time_in_atmosphere_mass_content_of_water_due_to_advection - - - - atmosphere_mass_content_of_water_vapor - - - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_gravitational_settling - - - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_gravitational_settling - - - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_dry_deposition - - - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_dry_deposition - - - - tendency_of_middle_atmosphere_moles_of_methyl_bromide - - - - atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur - - - - atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur - - - - atmosphere_mass_content_of_sulfate - - - - atmosphere_mass_content_of_sulfate - - - - tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_wet_deposition - - - - tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production - - - - tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production - - - - tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_dry_deposition - - - - atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles - - - - tendency_of_atmosphere_mass_content_of_water_vapor_due_to_deep_convection - - - - tendency_of_atmosphere_mass_content_of_water_vapor_due_to_convection - - - - atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles - - - - mass_content_of_cloud_liquid_water_in_atmosphere_layer - - - - air_equivalent_potential_temperature - - - - number_concentration_of_stratiform_cloud_liquid_water_particles_at_stratiform_liquid_water_cloud_top - - - - number_concentration_of_convective_cloud_liquid_water_particles_at_convective_liquid_water_cloud_top - - - - wave_frequency - - - - upward_eastward_momentum_flux_in_air_due_to_nonorographic_eastward_gravity_waves - - - - tendency_of_troposphere_moles_of_carbon_monoxide - - - - tendency_of_atmosphere_moles_of_sulfate_dry_aerosol_particles - - - - tendency_of_atmosphere_mass_content_of_nitrate_dry_aerosol_particles_due_to_dry_deposition - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_waste_treatment_and_disposal - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_savanna_and_grassland_fires - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_maritime_transport - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_land_transport - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_forest_fires - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_agricultural_waste_burning - - - - tendency_of_atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles_due_to_wet_deposition - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_wet_deposition - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_turbulent_deposition - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production_and_emission - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production_and_emission - - - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_gravitational_settling + + mole_fraction_of_noy_expressed_as_nitrogen_in_air - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_dry_deposition + + tendency_of_atmosphere_moles_of_methane - - atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles + + rate_of_hydroxyl_radical_destruction_due_to_reaction_with_nmvoc - - integral_wrt_depth_of_product_of_conservative_temperature_and_sea_water_density + + net_primary_mole_productivity_of_biomass_expressed_as_carbon_by_miscellaneous_phytoplankton - - integral_wrt_depth_of_product_of_salinity_and_sea_water_density + + mole_fraction_of_inorganic_bromine_in_air - - tendency_of_atmosphere_moles_of_methyl_bromide + + water_vapor_saturation_deficit_in_air - - integral_wrt_depth_of_product_of_potential_temperature_and_sea_water_density + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_agricultural_waste_burning - - atmosphere_moles_of_methyl_bromide + + tendency_of_atmosphere_moles_of_carbon_tetrachloride - - product_of_lagrangian_tendency_of_air_pressure_and_specific_humidity + + tendency_of_atmosphere_moles_of_carbon_monoxide - - product_of_lagrangian_tendency_of_air_pressure_and_specific_humidity + + platform_yaw - - tendency_of_sea_water_potential_temperature_expressed_as_heat_content_due_to_parameterized_dianeutral_mixing + + platform_pitch - - tendency_of_sea_water_conservative_temperature_expressed_as_heat_content_due_to_parameterized_dianeutral_mixing + + platform_roll - - volume_fraction_of_condensed_water_in_soil_at_wilting_point + + tendency_of_specific_humidity_due_to_stratiform_precipitation - - volume_fraction_of_condensed_water_in_soil_at_field_capacity + + tendency_of_air_temperature_due_to_stratiform_precipitation - - volume_fraction_of_condensed_water_in_soil_at_critical_point + + stratiform_precipitation_flux - - volume_fraction_of_condensed_water_in_soil + + stratiform_precipitation_amount - - product_of_lagrangian_tendency_of_air_pressure_and_geopotential_height + + lwe_thickness_of_stratiform_precipitation_amount - - product_of_lagrangian_tendency_of_air_pressure_and_air_temperature + + lwe_stratiform_precipitation_rate - - product_of_lagrangian_tendency_of_air_pressure_and_air_temperature + + water_evaporation_amount_from_canopy - - tendency_of_sea_water_salinity_expressed_as_salt_content_due_to_parameterized_dianeutral_mixing + + water_evaporation_flux_from_canopy - - atmosphere_moles_of_methane + + precipitation_flux_onto_canopy - - electrical_mobility_diameter_of_ambient_aerosol_particles + + outgoing_water_volume_transport_along_river_channel - - histogram_of_backscattering_ratio_in_air_over_height_above_reference_ellipsoid + + tendency_of_sea_ice_amount_due_to_conversion_of_snow_to_sea_ice tendency_of_atmosphere_mass_content_of_mercury_dry_aerosol_particles_due_to_emission - - effective_radius_of_stratiform_cloud_snow_particles - - - - mass_concentration_of_biomass_burning_dry_aerosol_particles_in_air - - - - atmosphere_mass_content_of_nitric_acid_trihydrate_ambient_aerosol_particles - - - - atmosphere_mass_content_of_nitrate_dry_aerosol_particles - - - - atmosphere_mass_content_of_mercury_dry_aerosol_particles - - - - backscattering_ratio_in_air - - - - product_of_northward_wind_and_lagrangian_tendency_of_air_pressure + + mass_fraction_of_mercury_dry_aerosol_particles_in_air @@ -32024,256 +32198,224 @@ tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_wet_deposition - - tendency_of_atmosphere_moles_of_cfc11 - - - - moles_of_cfc11_per_unit_mass_in_sea_water - - - - atmosphere_moles_of_cfc11 - - - - tendency_of_atmosphere_moles_of_hcc140a - - - - effective_radius_of_convective_cloud_rain_particles - - - - tendency_of_troposphere_moles_of_hcc140a - - - - tendency_of_middle_atmosphere_moles_of_hcc140a - - - - tendency_of_troposphere_moles_of_hcfc22 - - - - tendency_of_atmosphere_moles_of_hcfc22 + + stratiform_cloud_area_fraction - - atmosphere_moles_of_hcfc22 + + magnitude_of_sea_ice_displacement - - tendency_of_atmosphere_number_content_of_aerosol_particles_due_to_turbulent_deposition + + surface_downwelling_spherical_irradiance_per_unit_wavelength_in_sea_water - - lagrangian_tendency_of_atmosphere_sigma_coordinate + + surface_downwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol - - lagrangian_tendency_of_atmosphere_sigma_coordinate + + surface_downwelling_shortwave_flux_in_air_assuming_clear_sky - - diameter_of_ambient_aerosol_particles + + surface_downwelling_shortwave_flux_in_air - - effective_radius_of_stratiform_cloud_ice_particles + + surface_downwelling_radiative_flux_per_unit_wavelength_in_sea_water - - effective_radius_of_convective_cloud_ice_particles + + surface_downwelling_radiative_flux_per_unit_wavelength_in_air - - effective_radius_of_stratiform_cloud_graupel_particles + + surface_downwelling_radiance_per_unit_wavelength_in_sea_water - - effective_radius_of_stratiform_cloud_rain_particles + + surface_downwelling_photon_spherical_irradiance_per_unit_wavelength_in_sea_water - - effective_radius_of_convective_cloud_snow_particles + + surface_downwelling_photon_radiance_per_unit_wavelength_in_sea_water - - product_of_eastward_wind_and_lagrangian_tendency_of_air_pressure + + surface_downwelling_photon_flux_per_unit_wavelength_in_sea_water - - carbon_mass_flux_into_litter_and_soil_due_to_anthropogenic_land_use_or_land_cover_change + + surface_downwelling_longwave_flux_in_air - - stratiform_cloud_area_fraction + + integral_wrt_time_of_surface_downwelling_shortwave_flux_in_air - - sea_water_velocity_from_direction + + integral_wrt_time_of_surface_downwelling_longwave_flux_in_air - - thickness_of_stratiform_snowfall_amount + + downwelling_spherical_irradiance_per_unit_wavelength_in_sea_water - - optical_thickness_of_atmosphere_layer_due_to_ambient_aerosol_particles + + downwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol - - optical_thickness_of_atmosphere_layer_due_to_ambient_aerosol_particles + + downwelling_radiative_flux_per_unit_wavelength_in_sea_water - - lwe_thickness_of_stratiform_snowfall_amount + + downwelling_radiative_flux_per_unit_wavelength_in_air - - equivalent_thickness_at_stp_of_atmosphere_ozone_content + + downwelling_radiance_per_unit_wavelength_in_sea_water - - atmosphere_optical_thickness_due_to_water_in_ambient_aerosol_particles + + downwelling_radiance_per_unit_wavelength_in_air - - atmosphere_optical_thickness_due_to_dust_dry_aerosol_particles + + downwelling_photon_spherical_irradiance_per_unit_wavelength_in_sea_water - - atmosphere_optical_thickness_due_to_dust_ambient_aerosol_particles + + downwelling_photon_radiance_per_unit_wavelength_in_sea_water - - atmosphere_optical_thickness_due_to_ambient_aerosol_particles + + downwelling_photon_flux_per_unit_wavelength_in_sea_water - - atmosphere_optical_thickness_due_to_ambient_aerosol_particles + + surface_upwelling_shortwave_flux_in_air_assuming_clear_sky - - atmosphere_net_upward_convective_mass_flux + + surface_upwelling_longwave_flux_in_air_assuming_clear_sky - - mass_fraction_of_mercury_dry_aerosol_particles_in_air + + upwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol - - atmosphere_moles_of_hcc140a + + upwelling_radiative_flux_per_unit_wavelength_in_sea_water - - floating_ice_shelf_area_fraction + + upwelling_radiative_flux_per_unit_wavelength_in_air - - atmosphere_moles_of_carbon_tetrachloride + + upwelling_radiance_per_unit_wavelength_in_air - - mole_fraction_of_methylglyoxal_in_air + + surface_upwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol - - mole_fraction_of_dichlorine_peroxide_in_air + + surface_upwelling_shortwave_flux_in_air - - mole_fraction_of_noy_expressed_as_nitrogen_in_air + + surface_upwelling_radiative_flux_per_unit_wavelength_in_sea_water - - net_primary_mole_productivity_of_biomass_expressed_as_carbon_by_miscellaneous_phytoplankton + + surface_upwelling_radiative_flux_per_unit_wavelength_in_air - - mole_fraction_of_inorganic_bromine_in_air + + surface_upwelling_radiance_per_unit_wavelength_in_sea_water - - water_vapor_saturation_deficit_in_air + + volume_scattering_coefficient_of_radiative_flux_in_air_due_to_ambient_aerosol_particles - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_agricultural_waste_burning + + volume_scattering_coefficient_of_radiative_flux_in_air_due_to_dried_aerosol_particles - - tendency_of_atmosphere_moles_of_carbon_tetrachloride + + soil_mass_content_of_carbon - - tendency_of_atmosphere_moles_of_carbon_monoxide + + slow_soil_pool_mass_content_of_carbon - - tendency_of_atmosphere_moles_of_cfc113 + + root_mass_content_of_carbon - - atmosphere_moles_of_cfc113 + + miscellaneous_living_matter_mass_content_of_carbon - - tendency_of_atmosphere_moles_of_cfc114 + + fast_soil_pool_mass_content_of_carbon - - atmosphere_moles_of_cfc114 + + medium_soil_pool_mass_content_of_carbon - - tendency_of_atmosphere_moles_of_cfc115 + + leaf_mass_content_of_carbon - - atmosphere_moles_of_cfc115 + + carbon_mass_content_of_forestry_and_agricultural_products - - tendency_of_atmosphere_moles_of_cfc12 + + carbon_mass_content_of_forestry_and_agricultural_products - - atmosphere_moles_of_cfc12 + + surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_plant_respiration_for_biomass_maintenance - - tendency_of_atmosphere_moles_of_halon1202 + + surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_plant_respiration_for_biomass_growth - - atmosphere_moles_of_halon1202 + + surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_plant_respiration - - tendency_of_atmosphere_moles_of_halon1211 + + surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_respiration_in_soil - - atmosphere_moles_of_halon1211 + + surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_heterotrophic_respiration - - tendency_of_atmosphere_moles_of_halon1301 + + northward_transformed_eulerian_mean_air_velocity - - atmosphere_moles_of_halon1301 + + eastward_transformed_eulerian_mean_air_velocity - - tendency_of_atmosphere_moles_of_halon2402 + + surface_litter_mass_content_of_carbon - - atmosphere_moles_of_halon2402 + + litter_mass_content_of_carbon @@ -32308,14 +32450,14 @@ mole_concentration_of_diatoms_expressed_as_nitrogen_in_sea_water - - tendency_of_mole_concentration_of_dissolved_inorganic_phosphorus_in_sea_water_due_to_biological_processes - - tendency_of_mole_concentration_of_dissolved_inorganic_silicon_in_sea_water_due_to_biological_processes + + tendency_of_mole_concentration_of_dissolved_inorganic_phosphorus_in_sea_water_due_to_biological_processes + + tendency_of_atmosphere_mole_concentration_of_carbon_monoxide_due_to_chemical_destruction @@ -32324,56 +32466,64 @@ volume_extinction_coefficient_in_air_due_to_ambient_aerosol_particles - - atmosphere_mass_content_of_convective_cloud_condensed_water + + water_vapor_partial_pressure_in_air - - water_evaporation_flux_from_canopy + + platform_name - - precipitation_flux_onto_canopy + + platform_id - - surface_downwelling_shortwave_flux_in_air_assuming_clear_sky + + mass_flux_of_carbon_into_litter_from_vegetation - - surface_downwelling_radiance_per_unit_wavelength_in_sea_water + + subsurface_litter_mass_content_of_carbon - - upwelling_radiative_flux_per_unit_wavelength_in_sea_water + + stem_mass_content_of_carbon - - downwelling_photon_flux_per_unit_wavelength_in_sea_water + + mole_concentration_of_dissolved_inorganic_14C_in_sea_water - - downwelling_radiance_per_unit_wavelength_in_sea_water + + surface_downward_mass_flux_of_14C_dioxide_abiotic_analogue_expressed_as_carbon - - surface_downwelling_photon_radiance_per_unit_wavelength_in_sea_water + + surface_downward_mass_flux_of_13C_dioxide_abiotic_analogue_expressed_as_13C - - surface_downwelling_spherical_irradiance_per_unit_wavelength_in_sea_water + + mole_concentration_of_dissolved_inorganic_13C_in_sea_water - - surface_upwelling_radiative_flux_per_unit_wavelength_in_sea_water + + surface_upwelling_radiance_per_unit_wavelength_in_air_reflected_by_sea_water - - surface_downwelling_shortwave_flux_in_air + + surface_upwelling_radiance_per_unit_wavelength_in_air_emerging_from_sea_water - - tendency_of_sea_ice_amount_due_to_conversion_of_snow_to_sea_ice + + surface_upwelling_radiance_per_unit_wavelength_in_air + + + + surface_upwelling_longwave_flux_in_air + + + + incoming_water_volume_transport_along_river_channel @@ -32392,792 +32542,820 @@ sea_ice_temperature_expressed_as_heat_content - - outgoing_water_volume_transport_along_river_channel + + water_evapotranspiration_flux - - lwe_thickness_of_stratiform_precipitation_amount + + surface_water_evaporation_flux - - tendency_of_atmosphere_moles_of_methane + + water_volume_transport_into_sea_water_from_rivers - - rate_of_hydroxyl_radical_destruction_due_to_reaction_with_nmvoc + + stratiform_graupel_flux - - magnitude_of_sea_ice_displacement + + wood_debris_mass_content_of_carbon - - surface_downwelling_radiative_flux_per_unit_wavelength_in_sea_water + + toa_outgoing_shortwave_flux_assuming_clear_sky_and_no_aerosol - - surface_downwelling_radiative_flux_per_unit_wavelength_in_air + + water_flux_into_sea_water_from_rivers - - surface_downwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol + + integral_wrt_height_of_product_of_northward_wind_and_specific_humidity - - surface_downwelling_photon_spherical_irradiance_per_unit_wavelength_in_sea_water + + integral_wrt_height_of_product_of_eastward_wind_and_specific_humidity - - surface_downwelling_photon_flux_per_unit_wavelength_in_sea_water + + integral_wrt_depth_of_sea_water_temperature - - surface_downwelling_longwave_flux_in_air + + integral_wrt_depth_of_sea_water_temperature - - integral_wrt_time_of_surface_downwelling_shortwave_flux_in_air + + integral_wrt_depth_of_sea_water_temperature - - integral_wrt_time_of_surface_downwelling_longwave_flux_in_air + + integral_wrt_depth_of_sea_water_temperature - - downwelling_spherical_irradiance_per_unit_wavelength_in_sea_water + + integral_wrt_depth_of_sea_water_practical_salinity - - downwelling_radiative_flux_per_unit_wavelength_in_sea_water + + northward_ocean_heat_transport_due_to_parameterized_eddy_advection - - downwelling_radiative_flux_per_unit_wavelength_in_air + + tendency_of_ocean_eddy_kinetic_energy_content_due_to_parameterized_eddy_advection - - downwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol + + ocean_tracer_laplacian_diffusivity_due_to_parameterized_mesoscale_eddy_advection - - downwelling_photon_spherical_irradiance_per_unit_wavelength_in_sea_water + + ocean_tracer_biharmonic_diffusivity_due_to_parameterized_mesoscale_eddy_advection - - downwelling_radiance_per_unit_wavelength_in_air + + upward_sea_water_velocity_due_to_parameterized_mesoscale_eddies - - downwelling_photon_radiance_per_unit_wavelength_in_sea_water + + sea_water_y_velocity_due_to_parameterized_mesoscale_eddies - - surface_upwelling_shortwave_flux_in_air_assuming_clear_sky + + sea_water_x_velocity_due_to_parameterized_mesoscale_eddies - - surface_upwelling_longwave_flux_in_air_assuming_clear_sky + + eastward_sea_water_velocity_due_to_parameterized_mesoscale_eddies - - upwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol + + northward_sea_water_velocity_due_to_parameterized_mesoscale_eddies - - upwelling_radiative_flux_per_unit_wavelength_in_air + + tendency_of_sea_water_temperature_due_to_parameterized_eddy_advection - - upwelling_radiance_per_unit_wavelength_in_air + + tendency_of_sea_water_salinity_due_to_parameterized_eddy_advection - - surface_upwelling_shortwave_flux_in_air_assuming_clear_sky_and_no_aerosol + + ocean_y_overturning_mass_streamfunction_due_to_parameterized_eddy_advection - - surface_upwelling_shortwave_flux_in_air + + ocean_meridional_overturning_mass_streamfunction_due_to_parameterized_eddy_advection - - surface_upwelling_radiance_per_unit_wavelength_in_sea_water + + ocean_mass_y_transport_due_to_advection_and_parameterized_eddy_advection - - incoming_water_volume_transport_along_river_channel + + ocean_mass_x_transport_due_to_advection_and_parameterized_eddy_advection - - surface_upwelling_longwave_flux_in_air + + ocean_heat_y_transport_due_to_parameterized_eddy_advection - - surface_upwelling_radiance_per_unit_wavelength_in_air_emerging_from_sea_water + + ocean_heat_x_transport_due_to_parameterized_eddy_advection - - surface_upwelling_radiative_flux_per_unit_wavelength_in_air + + northward_ocean_salt_transport_due_to_parameterized_eddy_advection - - surface_upwelling_radiance_per_unit_wavelength_in_air + + northward_ocean_freshwater_transport_due_to_parameterized_eddy_advection - - surface_upwelling_radiance_per_unit_wavelength_in_air_reflected_by_sea_water + + integral_wrt_time_of_toa_outgoing_longwave_flux - - wood_debris_mass_content_of_carbon + + integral_wrt_time_of_toa_net_downward_shortwave_flux - - water_flux_into_sea_water_from_rivers + + integral_wrt_time_of_surface_net_downward_shortwave_flux - - integral_wrt_depth_of_sea_water_temperature + + integral_wrt_time_of_surface_net_downward_longwave_flux - - integral_wrt_depth_of_sea_water_temperature + + integral_wrt_time_of_surface_downward_sensible_heat_flux - - integral_wrt_depth_of_sea_water_temperature + + integral_wrt_time_of_surface_downward_latent_heat_flux - - integral_wrt_depth_of_sea_water_temperature + + integral_wrt_time_of_air_temperature_excess - - volume_scattering_coefficient_of_radiative_flux_in_air_due_to_ambient_aerosol_particles + + integral_wrt_time_of_air_temperature_deficit - - volume_scattering_coefficient_of_radiative_flux_in_air_due_to_dried_aerosol_particles + + tendency_of_mass_concentration_of_elemental_carbon_dry_aerosol_particles_in_air_due_to_emission_from_aviation - - integral_wrt_height_of_product_of_northward_wind_and_specific_humidity + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_wet_deposition - - integral_wrt_depth_of_sea_water_practical_salinity + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_turbulent_deposition - - integral_wrt_height_of_product_of_eastward_wind_and_specific_humidity + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_gravitational_settling - - platform_yaw + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_waste_treatment_and_disposal - - platform_roll + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_savanna_and_grassland_fires - - water_vapor_partial_pressure_in_air + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_residential_and_commercial_combustion - - platform_name + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_maritime_transport - - platform_id + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_land_transport - - platform_pitch + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_industrial_processes_and_combustion - - tendency_of_specific_humidity_due_to_stratiform_precipitation + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_forest_fires - - tendency_of_air_temperature_due_to_stratiform_precipitation + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_energy_production_and_distribution - - water_evaporation_amount_from_canopy + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission - - tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_turbulent_deposition + + tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_gravitational_settling + + mass_fraction_of_elemental_carbon_dry_aerosol_particles_in_air - - tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_emission + + atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles - - atmosphere_mass_content_of_cloud_ice + + mass_concentration_of_elemental_carbon_dry_aerosol_particles_in_air - - stratiform_precipitation_amount + + lagrangian_tendency_of_air_pressure - - tendency_of_atmosphere_moles_of_nitrous_oxide + + lagrangian_tendency_of_air_pressure - - tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_dry_deposition + + air_pressure_at_mean_sea_level - - medium_soil_pool_mass_content_of_carbon + + sea_floor_depth_below_geoid - - surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_plant_respiration + + sea_surface_height_above_geoid - - surface_downward_mass_flux_of_14C_dioxide_abiotic_analogue_expressed_as_carbon + + sea_surface_height_above_geoid - - mole_concentration_of_dissolved_inorganic_13C_in_sea_water + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_emission - - surface_litter_mass_content_of_carbon + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_emission - - surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_heterotrophic_respiration + + atmosphere_absorption_optical_thickness_due_to_sea_salt_ambient_aerosol_particles - - fast_soil_pool_mass_content_of_carbon + + atmosphere_absorption_optical_thickness_due_to_sea_salt_ambient_aerosol_particles - - soil_mass_content_of_carbon + + tendency_of_atmosphere_mass_content_of_nitrogen_compounds_expressed_as_nitrogen_due_to_deposition - - slow_soil_pool_mass_content_of_carbon + + tendency_of_atmosphere_mass_content_of_nitrogen_compounds_expressed_as_nitrogen_due_to_dry_deposition - - root_mass_content_of_carbon + + surface_geostrophic_eastward_sea_water_velocity - - miscellaneous_living_matter_mass_content_of_carbon + + surface_geostrophic_northward_sea_water_velocity - - carbon_mass_content_of_forestry_and_agricultural_products + + tendency_of_sea_surface_height_above_mean_sea_level + + + + surface_geostrophic_sea_water_y_velocity_assuming_mean_sea_level_for_geoid + + + + surface_geostrophic_sea_water_x_velocity_assuming_mean_sea_level_for_geoid + + + + surface_geostrophic_northward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + + + surface_geostrophic_northward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + + + surface_geostrophic_eastward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + + + surface_geostrophic_eastward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + + + sea_surface_height_above_mean_sea_level - - carbon_mass_content_of_forestry_and_agricultural_products + + sea_surface_height_above_mean_sea_level - - surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_plant_respiration_for_biomass_maintenance + + sea_floor_depth_below_mean_sea_level - - surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_plant_respiration_for_biomass_growth + + mass_fraction_of_pm10_ambient_aerosol_particles_in_air - - surface_upward_mass_flux_of_carbon_dioxide_expressed_as_carbon_due_to_respiration_in_soil + + mass_fraction_of_pm10_ambient_aerosol_particles_in_air - - northward_transformed_eulerian_mean_air_velocity + + mass_concentration_of_pm10_ambient_aerosol_particles_in_air - - eastward_transformed_eulerian_mean_air_velocity + + atmosphere_optical_thickness_due_to_pm10_ambient_aerosol_particles - - mass_flux_of_carbon_into_litter_from_vegetation + + mass_fraction_of_pm2p5_ambient_aerosol_particles_in_air - - subsurface_litter_mass_content_of_carbon + + mass_fraction_of_pm2p5_ambient_aerosol_particles_in_air - - litter_mass_content_of_carbon + + mass_concentration_of_pm2p5_ambient_aerosol_particles_in_air - - stem_mass_content_of_carbon + + atmosphere_optical_thickness_due_to_pm2p5_ambient_aerosol_particles - - mole_concentration_of_dissolved_inorganic_14C_in_sea_water + + mass_fraction_of_pm1_ambient_aerosol_particles_in_air - - surface_downward_mass_flux_of_13C_dioxide_abiotic_analogue_expressed_as_13C + + mass_fraction_of_pm1_ambient_aerosol_particles_in_air - - stratiform_precipitation_flux + + mass_concentration_of_pm1_ambient_aerosol_particles_in_air - - lwe_stratiform_precipitation_rate + + atmosphere_optical_thickness_due_to_pm1_ambient_aerosol_particles - - surface_water_evaporation_flux + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_wet_deposition - - water_evapotranspiration_flux + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_wet_deposition - - water_volume_transport_into_sea_water_from_rivers + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_turbulent_deposition - - stratiform_graupel_flux + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_turbulent_deposition - - toa_outgoing_shortwave_flux_assuming_clear_sky_and_no_aerosol + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_gravitational_settling - - ocean_y_overturning_mass_streamfunction_due_to_parameterized_eddy_advection + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_gravitational_settling - - ocean_tracer_laplacian_diffusivity_due_to_parameterized_mesoscale_eddy_advection + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_dry_deposition - - sea_water_x_velocity_due_to_parameterized_mesoscale_eddies + + tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_sea_water_temperature_due_to_parameterized_eddy_advection + + tendency_of_atmosphere_mass_content_of_pm2p5_sea_salt_dry_aerosol_particles_due_to_wet_deposition - - northward_ocean_heat_transport_due_to_parameterized_eddy_advection + + tendency_of_atmosphere_mass_content_of_pm10_sea_salt_dry_aerosol_particles_due_to_wet_deposition - - upward_sea_water_velocity_due_to_parameterized_mesoscale_eddies + + tendency_of_atmosphere_mass_content_of_pm10_sea_salt_dry_aerosol_particles_due_to_emission - - tendency_of_sea_water_salinity_due_to_parameterized_eddy_advection + + tendency_of_atmosphere_mass_content_of_pm10_sea_salt_dry_aerosol_particles_due_to_dry_deposition - - integral_wrt_time_of_surface_net_downward_shortwave_flux + + mass_fraction_of_sea_salt_dry_aerosol_particles_in_air - - tendency_of_ocean_eddy_kinetic_energy_content_due_to_parameterized_eddy_advection + + mass_fraction_of_sea_salt_dry_aerosol_particles_in_air - - sea_water_y_velocity_due_to_parameterized_mesoscale_eddies + + mass_concentration_of_sea_salt_dry_aerosol_particles_in_air - - ocean_tracer_biharmonic_diffusivity_due_to_parameterized_mesoscale_eddy_advection + + mass_concentration_of_sea_salt_dry_aerosol_particles_in_air - - eastward_sea_water_velocity_due_to_parameterized_mesoscale_eddies + + atmosphere_optical_thickness_due_to_sea_salt_ambient_aerosol_particles - - northward_sea_water_velocity_due_to_parameterized_mesoscale_eddies + + atmosphere_optical_thickness_due_to_sea_salt_ambient_aerosol_particles - - ocean_heat_y_transport_due_to_parameterized_eddy_advection + + atmosphere_mass_content_of_sea_salt_dry_aerosol_particles - - ocean_meridional_overturning_mass_streamfunction_due_to_parameterized_eddy_advection + + atmosphere_mass_content_of_sea_salt_dry_aerosol_particles - - ocean_mass_y_transport_due_to_advection_and_parameterized_eddy_advection + + ocean_mixed_layer_thickness_defined_by_vertical_tracer_diffusivity_deficit - - ocean_mass_x_transport_due_to_advection_and_parameterized_eddy_advection + + sea_surface_swell_wave_mean_period - - ocean_heat_x_transport_due_to_parameterized_eddy_advection + + sea_surface_wind_wave_mean_period - - northward_ocean_freshwater_transport_due_to_parameterized_eddy_advection + + sea_surface_wave_mean_period - - northward_ocean_salt_transport_due_to_parameterized_eddy_advection + + sea_surface_wind_wave_to_direction - - integral_wrt_time_of_toa_outgoing_longwave_flux + + sea_surface_swell_wave_to_direction - - integral_wrt_time_of_toa_net_downward_shortwave_flux + + mass_content_of_water_in_soil - - integral_wrt_time_of_surface_net_downward_longwave_flux + + mass_content_of_water_in_soil_layer - - integral_wrt_time_of_surface_downward_sensible_heat_flux + + sea_surface_wave_significant_height - - integral_wrt_time_of_surface_downward_latent_heat_flux + + sea_surface_wind_wave_significant_height - - integral_wrt_time_of_air_temperature_excess + + sea_surface_swell_wave_significant_height - - integral_wrt_time_of_air_temperature_deficit + + tendency_of_atmosphere_moles_of_sulfate_dry_aerosol_particles - - tendency_of_atmosphere_mass_content_of_ammonium_dry_aerosol_particles_due_to_wet_deposition + + tendency_of_atmosphere_moles_of_nitric_acid_trihydrate_ambient_aerosol_particles - - tendency_of_atmosphere_mass_content_of_ammonium_dry_aerosol_particles_due_to_dry_deposition + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_turbulent_deposition - - atmosphere_absorption_optical_thickness_due_to_sulfate_ambient_aerosol_particles + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_turbulent_deposition - - atmosphere_absorption_optical_thickness_due_to_particulate_organic_matter_ambient_aerosol_particles + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_gravitational_settling - - atmosphere_absorption_optical_thickness_due_to_dust_ambient_aerosol_particles + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_gravitational_settling - - angstrom_exponent_of_ambient_aerosol_in_air + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_dry_deposition - - atmosphere_convective_available_potential_energy + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_dry_deposition - - atmosphere_convective_available_potential_energy + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_mass_concentration_of_elemental_carbon_dry_aerosol_particles_in_air_due_to_emission_from_aviation + + tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_wet_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_wet_deposition + + tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_turbulent_deposition + + tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_gravitational_settling + + tendency_of_atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_waste_treatment_and_disposal + + tendency_of_atmosphere_mass_content_of_nitrate_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_savanna_and_grassland_fires + + tendency_of_atmosphere_mass_content_of_mercury_dry_aerosol_particles_due_to_wet_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_residential_and_commercial_combustion + + tendency_of_atmosphere_mass_content_of_mercury_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_maritime_transport + + tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_wet_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_land_transport + + tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_turbulent_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_industrial_processes_and_combustion + + tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_gravitational_settling - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_forest_fires + + tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission_from_energy_production_and_distribution + + tendency_of_atmosphere_mass_content_of_ammonium_dry_aerosol_particles_due_to_wet_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_emission + + tendency_of_atmosphere_mass_content_of_ammonium_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles_due_to_dry_deposition + + optical_thickness_of_atmosphere_layer_due_to_ambient_aerosol_particles - - mass_fraction_of_elemental_carbon_dry_aerosol_particles_in_air + + optical_thickness_of_atmosphere_layer_due_to_ambient_aerosol_particles - - atmosphere_mass_content_of_elemental_carbon_dry_aerosol_particles + + number_concentration_of_nucleation_mode_ambient_aerosol_particles_in_air - - mass_concentration_of_elemental_carbon_dry_aerosol_particles_in_air + + number_concentration_of_coarse_mode_ambient_aerosol_particles_in_air - - lagrangian_tendency_of_air_pressure + + number_concentration_of_ambient_aerosol_particles_in_air - - lagrangian_tendency_of_air_pressure + + mole_fraction_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air - - sea_surface_height_above_geoid + + mole_concentration_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air - - sea_surface_height_above_geoid + + mass_fraction_of_water_in_ambient_aerosol_particles_in_air - - surface_geostrophic_eastward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + mass_fraction_of_sulfate_dry_aerosol_particles_in_air - - surface_geostrophic_eastward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + mass_fraction_of_secondary_particulate_organic_matter_dry_aerosol_particles_in_air - - sea_surface_height_above_mean_sea_level + + mass_fraction_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air - - sea_surface_height_above_mean_sea_level + + mass_fraction_of_nitrate_dry_aerosol_particles_in_air - - surface_geostrophic_northward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + mass_fraction_of_dust_dry_aerosol_particles_in_air - - surface_geostrophic_northward_sea_water_velocity_assuming_mean_sea_level_for_geoid + + mass_fraction_of_ammonium_dry_aerosol_particles_in_air - - surface_geostrophic_sea_water_y_velocity_assuming_mean_sea_level_for_geoid + + mass_concentration_of_water_in_ambient_aerosol_particles_in_air - - sea_floor_depth_below_geoid + + mass_concentration_of_sulfate_dry_aerosol_particles_in_air - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_emission + + mass_concentration_of_sulfate_ambient_aerosol_particles_in_air - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_emission + + mass_concentration_of_sulfate_ambient_aerosol_particles_in_air - - atmosphere_absorption_optical_thickness_due_to_sea_salt_ambient_aerosol_particles + + mass_concentration_of_secondary_particulate_organic_matter_dry_aerosol_particles_in_air - - atmosphere_absorption_optical_thickness_due_to_sea_salt_ambient_aerosol_particles + + mass_concentration_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air - - tendency_of_atmosphere_mass_content_of_pm10_sea_salt_dry_aerosol_particles_due_to_emission + + mass_concentration_of_nitrate_dry_aerosol_particles_in_air - - tendency_of_atmosphere_mass_content_of_nitrogen_compounds_expressed_as_nitrogen_due_to_deposition + + mass_concentration_of_mercury_dry_aerosol_particles_in_air - - tendency_of_atmosphere_mass_content_of_nitrogen_compounds_expressed_as_nitrogen_due_to_dry_deposition + + atmosphere_optical_thickness_due_to_water_in_ambient_aerosol_particles - - surface_geostrophic_northward_sea_water_velocity + + mass_concentration_of_particulate_organic_matter_dry_aerosol_particles_in_air - - surface_geostrophic_sea_water_x_velocity_assuming_mean_sea_level_for_geoid + + mass_concentration_of_primary_particulate_organic_matter_dry_aerosol_particles_in_air - - air_pressure_at_mean_sea_level + + atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur - - sea_floor_depth_below_mean_sea_level + + atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur - - ocean_mixed_layer_thickness_defined_by_vertical_tracer_diffusivity_deficit + + mass_concentration_of_ammonium_dry_aerosol_particles_in_air - - sea_surface_wind_wave_mean_period + + mass_concentration_of_coarse_mode_ambient_aerosol_particles_in_air - - sea_surface_wave_mean_period + + mass_concentration_of_dust_dry_aerosol_particles_in_air - - sea_surface_swell_wave_mean_period + + atmosphere_optical_thickness_due_to_particulate_organic_matter_ambient_aerosol_particles - - sea_surface_wind_wave_to_direction + + atmosphere_optical_thickness_due_to_dust_dry_aerosol_particles - - sea_surface_swell_wave_to_direction + + atmosphere_optical_thickness_due_to_dust_ambient_aerosol_particles - - mass_content_of_water_in_soil_layer + + atmosphere_optical_thickness_due_to_ambient_aerosol_particles - - mass_content_of_water_in_soil + + atmosphere_optical_thickness_due_to_ambient_aerosol_particles - - sea_surface_wind_wave_significant_height + + atmosphere_moles_of_nitric_acid_trihydrate_ambient_aerosol_particles - - sea_surface_swell_wave_significant_height + + atmosphere_mass_content_of_water_in_ambient_aerosol_particles - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_turbulent_deposition + + atmosphere_mass_content_of_sulfate_dry_aerosol_particles - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_expressed_as_sulfur_due_to_turbulent_deposition + + atmosphere_mass_content_of_sulfate_ambient_aerosol_particles - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_due_to_emission + + atmosphere_mass_content_of_sulfate_ambient_aerosol_particles - - atmosphere_optical_thickness_due_to_particulate_organic_matter_ambient_aerosol_particles + + atmosphere_mass_content_of_secondary_particulate_organic_matter_dry_aerosol_particles - - mass_concentration_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air + + atmosphere_mass_content_of_nitric_acid_trihydrate_ambient_aerosol_particles - - atmosphere_mass_content_of_water_in_ambient_aerosol_particles + + atmosphere_mass_content_of_nitrate_dry_aerosol_particles - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_residential_and_commercial_combustion + + atmosphere_mass_content_of_mercury_dry_aerosol_particles - - tendency_of_atmosphere_mass_content_of_mercury_dry_aerosol_particles_due_to_wet_deposition + + atmosphere_mass_content_of_dust_dry_aerosol_particles - - tendency_of_atmosphere_mass_content_of_mercury_dry_aerosol_particles_due_to_dry_deposition + + atmosphere_mass_content_of_ammonium_dry_aerosol_particles - - mass_fraction_of_nitrate_dry_aerosol_particles_in_air + + atmosphere_absorption_optical_thickness_due_to_sulfate_ambient_aerosol_particles - - mass_concentration_of_sulfate_dry_aerosol_particles_in_air + + atmosphere_absorption_optical_thickness_due_to_particulate_organic_matter_ambient_aerosol_particles - - mass_fraction_of_water_in_ambient_aerosol_particles_in_air + + atmosphere_absorption_optical_thickness_due_to_dust_ambient_aerosol_particles - - mass_fraction_of_secondary_particulate_organic_matter_dry_aerosol_particles_in_air + + angstrom_exponent_of_ambient_aerosol_in_air - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_industrial_processes_and_combustion + + atmosphere_absorption_optical_thickness_due_to_ambient_aerosol_particles - - tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_energy_production_and_distribution + + tendency_of_atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles_due_to_wet_deposition - - mass_concentration_of_sulfate_ambient_aerosol_particles_in_air + + tendency_of_atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles_due_to_dry_deposition - - mass_concentration_of_sulfate_ambient_aerosol_particles_in_air + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_wet_deposition - - mass_concentration_of_dust_dry_aerosol_particles_in_air + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_turbulent_deposition - - tendency_of_atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles_due_to_emission + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_gravitational_settling - - tendency_of_atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles_due_to_dry_deposition + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_dry_deposition @@ -33188,528 +33366,532 @@ mass_fraction_of_particulate_organic_matter_dry_aerosol_particles_in_air - - number_concentration_of_coarse_mode_ambient_aerosol_particles_in_air + + atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles - - sea_surface_wave_significant_height + + atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles - - tendency_of_atmosphere_moles_of_nitric_acid_trihydrate_ambient_aerosol_particles + + tendency_of_atmosphere_mass_content_of_pm2p5_sea_salt_dry_aerosol_particles_due_to_emission + + + + tendency_of_atmosphere_mass_content_of_pm2p5_sea_salt_dry_aerosol_particles_due_to_dry_deposition - - tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_due_to_dry_deposition + + net_primary_productivity_of_biomass_expressed_as_carbon_accumulated_in_wood - - tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_wet_deposition + + net_primary_productivity_of_biomass_expressed_as_carbon_accumulated_in_roots - - number_concentration_of_nucleation_mode_ambient_aerosol_particles_in_air + + net_primary_productivity_of_biomass_expressed_as_carbon_accumulated_in_leaves - - number_concentration_of_ambient_aerosol_particles_in_air + + net_primary_productivity_of_biomass_expressed_as_carbon - - mole_fraction_of_nitric_acid_trihydrate_ambient_aerosol_particles_in_air + + gross_primary_productivity_of_biomass_expressed_as_carbon - - mass_fraction_of_dust_dry_aerosol_particles_in_air + + atmosphere_convective_available_potential_energy - - mass_concentration_of_water_in_ambient_aerosol_particles_in_air + + atmosphere_convective_available_potential_energy - - mass_concentration_of_nitrate_dry_aerosol_particles_in_air + + mass_concentration_of_chlorophyll_in_sea_water - - mass_concentration_of_particulate_organic_matter_dry_aerosol_particles_in_air + + mass_concentration_of_chlorophyll_in_sea_water - - mass_concentration_of_ammonium_dry_aerosol_particles_in_air + + omnidirectional_spherical_irradiance_per_unit_wavelength_in_sea_water - - atmosphere_mass_content_of_sulfate_ambient_aerosol_particles + + isotropic_radiance_per_unit_wavelength_in_air - - atmosphere_mass_content_of_sulfate_ambient_aerosol_particles + + isotropic_radiance_per_unit_wavelength_in_air - - atmosphere_mass_content_of_dust_dry_aerosol_particles + + land_ice_lwe_surface_specific_mass_balance_rate - - atmosphere_absorption_optical_thickness_due_to_ambient_aerosol_particles + + land_ice_surface_specific_mass_balance_rate - - atmosphere_mass_content_of_sulfate_dry_aerosol_particles + + tendency_of_atmosphere_mass_content_of_water_vapor_due_to_advection - - tendency_of_atmosphere_mass_content_of_water_vapor_due_to_turbulence + + equivalent_thickness_at_stp_of_atmosphere_ozone_content - - surface_upward_mole_flux_of_carbon_dioxide + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_industrial_processes_and_combustion - - surface_downward_mole_flux_of_carbon_dioxide + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_forest_fires - - atmosphere_mass_content_of_cloud_condensed_water + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production_and_emission - - northward_water_vapor_flux_in_air + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_due_to_net_chemical_production_and_emission - - lwe_stratiform_snowfall_rate + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_maritime_transport - - stratiform_snowfall_amount + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_energy_production_and_distribution - - stratiform_rainfall_rate + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_agricultural_waste_burning - - stratiform_rainfall_flux + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_land_transport - - stratiform_rainfall_amount + + tendency_of_atmosphere_mass_content_of_primary_particulate_organic_matter_dry_aerosol_particles_due_to_emission - - tendency_of_atmosphere_mass_content_of_pm2p5_sea_salt_dry_aerosol_particles_due_to_emission + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_savanna_and_grassland_fires - - tendency_of_atmosphere_mass_content_of_pm2p5_sea_salt_dry_aerosol_particles_due_to_dry_deposition + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_waste_treatment_and_disposal - - tendency_of_sea_surface_height_above_mean_sea_level + + tendency_of_atmosphere_mass_content_of_particulate_organic_matter_dry_aerosol_particles_expressed_as_carbon_due_to_emission_from_residential_and_commercial_combustion - - mass_fraction_of_pm10_ambient_aerosol_particles_in_air + + tendency_of_atmosphere_mass_content_of_dust_dry_aerosol_particles_due_to_emission - - mass_fraction_of_pm10_ambient_aerosol_particles_in_air + + tendency_of_atmosphere_mass_content_of_sulfate_dry_aerosol_particles_due_to_emission - - mass_concentration_of_pm10_ambient_aerosol_particles_in_air + + tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_turbulence - - atmosphere_optical_thickness_due_to_pm10_ambient_aerosol_particles + + tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_shallow_convection - - surface_geostrophic_eastward_sea_water_velocity + + tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_deep_convection - - mass_fraction_of_pm2p5_ambient_aerosol_particles_in_air + + atmosphere_net_upward_convective_mass_flux - - mass_fraction_of_pm2p5_ambient_aerosol_particles_in_air + + tendency_of_troposphere_moles_of_molecular_hydrogen - - mass_concentration_of_pm2p5_ambient_aerosol_particles_in_air + + tendency_of_troposphere_moles_of_methyl_chloride - - atmosphere_optical_thickness_due_to_pm2p5_ambient_aerosol_particles + + tendency_of_troposphere_moles_of_methyl_bromide - - mass_fraction_of_pm1_ambient_aerosol_particles_in_air + + tendency_of_troposphere_moles_of_methane - - mass_fraction_of_pm1_ambient_aerosol_particles_in_air + + tendency_of_troposphere_moles_of_carbon_monoxide - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_wet_deposition + + tendency_of_middle_atmosphere_moles_of_molecular_hydrogen - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_wet_deposition + + tendency_of_middle_atmosphere_moles_of_methyl_chloride - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_gravitational_settling + + tendency_of_middle_atmosphere_moles_of_methyl_bromide - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_gravitational_settling + + tendency_of_middle_atmosphere_moles_of_methane - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_turbulent_deposition + + tendency_of_middle_atmosphere_moles_of_carbon_monoxide - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_turbulent_deposition + + tendency_of_atmosphere_moles_of_nitrous_oxide - - mass_concentration_of_pm1_ambient_aerosol_particles_in_air + + tendency_of_atmosphere_moles_of_molecular_hydrogen - - atmosphere_optical_thickness_due_to_pm1_ambient_aerosol_particles + + tendency_of_atmosphere_moles_of_methyl_chloride - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_dry_deposition + + tendency_of_atmosphere_moles_of_methyl_bromide - - tendency_of_atmosphere_mass_content_of_sea_salt_dry_aerosol_particles_due_to_dry_deposition + + y_wind - - tendency_of_atmosphere_mass_content_of_pm2p5_sea_salt_dry_aerosol_particles_due_to_wet_deposition + + x_wind - - tendency_of_atmosphere_mass_content_of_pm10_sea_salt_dry_aerosol_particles_due_to_wet_deposition + + sea_water_y_velocity - - tendency_of_atmosphere_mass_content_of_pm10_sea_salt_dry_aerosol_particles_due_to_dry_deposition + + sea_water_x_velocity - - mass_fraction_of_sea_salt_dry_aerosol_particles_in_air + + mole_concentration_of_organic_detritus_expressed_as_silicon_in_sea_water - - mass_fraction_of_sea_salt_dry_aerosol_particles_in_air + + mole_concentration_of_organic_detritus_expressed_as_nitrogen_in_sea_water - - mass_concentration_of_sea_salt_dry_aerosol_particles_in_air + + mole_concentration_of_microzooplankton_expressed_as_nitrogen_in_sea_water - - mass_concentration_of_sea_salt_dry_aerosol_particles_in_air + + mole_concentration_of_mesozooplankton_expressed_as_nitrogen_in_sea_water - - atmosphere_optical_thickness_due_to_sea_salt_ambient_aerosol_particles + + atmosphere_moles_of_nitrous_oxide - - atmosphere_optical_thickness_due_to_sea_salt_ambient_aerosol_particles + + atmosphere_moles_of_molecular_hydrogen - - atmosphere_mass_content_of_sea_salt_dry_aerosol_particles + + atmosphere_moles_of_methyl_chloride - - atmosphere_mass_content_of_sea_salt_dry_aerosol_particles + + atmosphere_moles_of_methyl_bromide - - surface_upward_sensible_heat_flux + + atmosphere_moles_of_methane - - surface_temperature + + atmosphere_moles_of_carbon_monoxide - - surface_temperature + + tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_convection - - surface_temperature + + tendency_of_mass_content_of_water_vapor_in_atmosphere_layer - - surface_net_downward_radiative_flux + + tendency_of_atmosphere_mass_content_of_water_vapor_due_to_turbulence - - wind_mixing_energy_flux_into_sea_water + + tendency_of_atmosphere_mass_content_of_water_vapor_due_to_shallow_convection - - water_flux_into_sea_water + + tendency_of_atmosphere_mass_content_of_water_vapor_due_to_deep_convection - - upward_eliassen_palm_flux_in_air + + tendency_of_atmosphere_mass_content_of_water_vapor_due_to_convection - - upward_eastward_momentum_flux_in_air_due_to_orographic_gravity_waves + + tendency_of_atmosphere_mass_content_of_water_vapor - - upward_eastward_momentum_flux_in_air_due_to_nonorographic_westward_gravity_waves + + tendency_of_atmosphere_mass_content_of_water_due_to_advection - - specific_gravitational_potential_energy + + mass_content_of_water_vapor_in_atmosphere_layer - - product_of_northward_wind_and_specific_humidity + + mass_content_of_water_in_atmosphere_layer - - mole_fraction_of_ozone_in_air + + mass_content_of_cloud_ice_in_atmosphere_layer - - isotropic_shortwave_radiance_in_air + + mass_content_of_cloud_condensed_water_in_atmosphere_layer - - isotropic_longwave_radiance_in_air + + lwe_thickness_of_atmosphere_mass_content_of_water_vapor - - mass_concentration_of_primary_particulate_organic_matter_dry_aerosol_particles_in_air + + change_over_time_in_atmosphere_mass_content_of_water_due_to_advection - - atmosphere_mass_content_of_ammonium_dry_aerosol_particles + + change_over_time_in_atmosphere_mass_content_of_water_due_to_advection - - stratiform_snowfall_flux + + atmosphere_mass_content_of_sulfate - - thickness_of_stratiform_rainfall_amount + + atmosphere_mass_content_of_sulfate - - sea_surface_wind_wave_period + + surface_upward_mole_flux_of_carbon_dioxide - - omnidirectional_spherical_irradiance_per_unit_wavelength_in_sea_water + + surface_downward_mole_flux_of_carbon_dioxide - - tendency_of_middle_atmosphere_moles_of_molecular_hydrogen + + atmosphere_mass_content_of_water_vapor - - tendency_of_middle_atmosphere_moles_of_methyl_chloride + + atmosphere_mass_content_of_convective_cloud_condensed_water - - tendency_of_middle_atmosphere_moles_of_methane + + atmosphere_mass_content_of_cloud_ice - - sea_water_y_velocity + + atmosphere_mass_content_of_cloud_condensed_water - - sea_water_x_velocity + + thickness_of_stratiform_snowfall_amount - - mole_fraction_of_hypochlorous_acid_in_air + + thickness_of_stratiform_rainfall_amount - - tendency_of_troposphere_moles_of_molecular_hydrogen + + stratiform_snowfall_flux - - tendency_of_troposphere_moles_of_methyl_chloride + + stratiform_snowfall_amount - - mass_content_of_water_vapor_in_atmosphere_layer + + stratiform_rainfall_rate - - mass_content_of_water_in_atmosphere_layer + + stratiform_rainfall_flux - - tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_turbulence + + stratiform_rainfall_amount - - tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_deep_convection + + northward_water_vapor_flux_in_air - - tendency_of_troposphere_moles_of_methyl_bromide + + lwe_thickness_of_stratiform_snowfall_amount - - tendency_of_mass_content_of_water_vapor_in_atmosphere_layer_due_to_convection + + lwe_stratiform_snowfall_rate - - tendency_of_atmosphere_mass_content_of_water_vapor_due_to_shallow_convection + + kinetic_energy_dissipation_in_atmosphere_boundary_layer - - radiation_wavelength + + eastward_water_vapor_flux_in_air - - tendency_of_troposphere_moles_of_methane + + surface_upward_sensible_heat_flux - - tendency_of_atmosphere_mass_content_of_water_due_to_advection + + surface_temperature - - mole_fraction_of_chlorine_monoxide_in_air + + surface_temperature - - mole_fraction_of_chlorine_dioxide_in_air + + surface_temperature - - mass_fraction_of_ozone_in_air + + surface_net_downward_radiative_flux - - mass_fraction_of_convective_cloud_condensed_water_in_air + + mole_fraction_of_hypochlorous_acid_in_air - - sea_surface_swell_wave_period + + mole_fraction_of_chlorine_monoxide_in_air - - surface_drag_coefficient_in_air + + mole_fraction_of_chlorine_dioxide_in_air - - mass_content_of_cloud_condensed_water_in_atmosphere_layer + + wind_mixing_energy_flux_into_sea_water - - mole_concentration_of_organic_detritus_expressed_as_silicon_in_sea_water + + water_flux_into_sea_water - - mole_concentration_of_organic_detritus_expressed_as_nitrogen_in_sea_water + + upward_eastward_momentum_flux_in_air_due_to_orographic_gravity_waves - - y_wind + + upward_eastward_momentum_flux_in_air_due_to_nonorographic_westward_gravity_waves - - kinetic_energy_dissipation_in_atmosphere_boundary_layer + + upward_eastward_momentum_flux_in_air_due_to_nonorographic_eastward_gravity_waves - - mass_concentration_of_suspended_matter_in_sea_water + + upward_eliassen_palm_flux_in_air - - x_wind + + northward_heat_flux_in_air_due_to_eddy_advection - - isotropic_radiance_per_unit_wavelength_in_air + + northward_eliassen_palm_flux_in_air - - isotropic_radiance_per_unit_wavelength_in_air + + wave_frequency - - atmosphere_moles_of_nitrous_oxide + + sea_surface_wind_wave_period - - atmosphere_moles_of_molecular_hydrogen + + sea_surface_swell_wave_period - - net_primary_productivity_of_biomass_expressed_as_carbon_accumulated_in_roots + + mass_concentration_of_suspended_matter_in_sea_water - - atmosphere_moles_of_methyl_chloride + + surface_drag_coefficient_in_air - - land_ice_surface_specific_mass_balance_rate + + surface_drag_coefficient_for_momentum_in_air - - land_ice_lwe_surface_specific_mass_balance_rate + + surface_drag_coefficient_for_heat_in_air - - tendency_of_atmosphere_moles_of_molecular_hydrogen + + specific_gravitational_potential_energy - - atmosphere_moles_of_carbon_monoxide + + radiation_wavelength - - tendency_of_atmosphere_moles_of_methyl_chloride + + product_of_northward_wind_and_specific_humidity - - surface_drag_coefficient_for_momentum_in_air + + mole_fraction_of_ozone_in_air - - surface_drag_coefficient_for_heat_in_air + + isotropic_shortwave_radiance_in_air - - leaf_mass_content_of_carbon + + isotropic_longwave_radiance_in_air - - mass_concentration_of_chlorophyll_in_sea_water + + mass_fraction_of_ozone_in_air - - mass_concentration_of_chlorophyll_in_sea_water + + mass_fraction_of_convective_cloud_condensed_water_in_air From d23669eca6bd4cdb97e1b27c104c7816d1d921b1 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Wed, 17 Aug 2022 16:57:10 +0100 Subject: [PATCH 195/319] Pin shapely (#4911) * pin shapely * update whatsnew --- docs/src/whatsnew/latest.rst | 3 ++ requirements/ci/nox.lock/py310-linux-64.lock | 51 +++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 51 +++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 53 ++++++++++---------- requirements/ci/py310.yml | 1 + requirements/ci/py38.yml | 1 + requirements/ci/py39.yml | 1 + 7 files changed, 82 insertions(+), 79 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index c111110bd1..a6f4423639 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -209,6 +209,9 @@ This document explains the changes made to Iris for this release #. `@trexfeathers`_ updated the install process to work with setuptools `>=v64`, making `v64` the minimum compatible version. (:pull:`4903`) +#. `@stephenworsley`_ introduced the ``shapely < 1.8.3`` maximum pin, avoiding a + bug caused by its interaction with cartopy. (:pull:`4911`) + 📚 Documentation ================ diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index ffd5896d7f..b174b21781 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 26e5295416e089473f7143dc79fd155837256841e73e052eeea25d2e4be13560 +# input_hash: 47e8f4325818ea52101543c35eb782f69a9c59cfd5847e679670a43a8208a383 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -12,7 +12,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022b-h191b570_0.tar.bz2#48806dd9fc8893fc52aafbdc349e77e0 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab @@ -42,14 +42,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_0.tar.bz2#8220a603d6822487affcaaf72960fca1 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_1.tar.bz2#45127206f58f9989f96041c6e87a534a https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -76,6 +75,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -89,16 +90,15 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_4.tar.bz2#a6b5e941bc2998fbd59c84645247c2c7 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 @@ -111,7 +111,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-hca18f0e_2.tar.bz2#cfd942cf6d2c31dc00f91d1b2dcd0f80 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -123,13 +123,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/linux-64/python-3.10.5-h582c2e5_0_cpython.tar.bz2#ccbed83043b9b7b5693164591317f327 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -139,7 +140,6 @@ https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e @@ -148,6 +148,7 @@ https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 @@ -158,7 +159,7 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2-pyhd8ed1ab_0.tar.bz2#dc99e49653ff49ff64a4a3d698124ba4 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -177,21 +178,20 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py310h53a5b5f_0.tar.bz2#9b86a46d908354fe7f91da24f5ea3f36 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py310h53a5b5f_0.tar.bz2#8b3cfad14508018915e88612f5a963cd https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310he619898_1.tar.bz2#af9690193837e58a2fd2af38dce537f4 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310hbd86126_2.tar.bz2#443272de4234f6df4a78f50105edc741 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed @@ -200,7 +200,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.ta https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.3-py310hff52083_0.tar.bz2#44c851111753c9fb1e71e98a0d04c3e3 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py310hff52083_0.tar.bz2#fe4e6907626e7d9a6de503c88f5f7b27 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 @@ -209,16 +209,16 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_100 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py310h5764c6d_0.tar.bz2#c965e9e47e42b19d26994e848d2a40e6 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py310h5764c6d_0.tar.bz2#71d8a7387c733792ee9922357a44232d https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 @@ -231,26 +231,25 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py310h5701ce4_1.tar.bz2#30705ca3f166305e268ae10d2d703879 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py310h5701ce4_1.tar.bz2#36adf7d1b16bee40fcad8bfce54baa85 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_1.tar.bz2#a06eff87c5bbd8ece667e155854fec2b https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py310hff52083_1.tar.bz2#ac5726cced21482d6b817921a441d6a2 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_1.tar.bz2#7fa212f8ef62e0ec36f20448267ce95a https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 168c6fdd59..7f45d17b06 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 8a110789ad07590a0a46c6da77a9a2c37c2cc302c639cfb02dac7666c38afa09 +# input_hash: 713876566ea2ac7e2e527b6083261e07b09f96f5113611bd51a12599aa2efe1e @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -41,14 +41,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_0.tar.bz2#8220a603d6822487affcaaf72960fca1 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_1.tar.bz2#45127206f58f9989f96041c6e87a534a https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -75,6 +74,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -88,16 +89,15 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_4.tar.bz2#a6b5e941bc2998fbd59c84645247c2c7 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 @@ -110,7 +110,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-hca18f0e_2.tar.bz2#cfd942cf6d2c31dc00f91d1b2dcd0f80 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -122,13 +122,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -138,7 +139,6 @@ https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e @@ -147,6 +147,7 @@ https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 @@ -157,7 +158,7 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2-pyhd8ed1ab_0.tar.bz2#dc99e49653ff49ff64a4a3d698124ba4 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -176,21 +177,20 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258 -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_2.tar.bz2#fccce86e5fc8183bf2658ac9bfc535b4 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py38h3a7f9d9_0.tar.bz2#90cf44c14b2bfe19ce7b875979b90cb9 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py38h3a7f9d9_0.tar.bz2#a7579626c41b3975da213c0b53aefa29 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h0ee0e06_1.tar.bz2#b3738ab363ee3311b4624f9a5abc424c +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38ha3b2c9c_2.tar.bz2#a077cc2bb9d854074b1cf4607252da7a https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 @@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.3-py38h578d9bd_0.tar.bz2#e1813849f70436eab3afa951d5a12bb7 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py38h578d9bd_0.tar.bz2#2a01f71dd562cc9fbf2ab93c813b6e8d https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 @@ -208,16 +208,16 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py38h0a891b7_0.tar.bz2#7ac14fa19454e00f50d9a39506bcc3c6 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py38h0a891b7_0.tar.bz2#3fe3830d29d4fe9962eb6afe73f9a878 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b @@ -230,26 +230,25 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py38h826bfd8_1.tar.bz2#2726cea4b977031563cb3d2ae813a695 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py38h826bfd8_1.tar.bz2#454fd59caae7913651f08e3856c4c2e9 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h32db9c8_101.tar.bz2#d1451d40c8204594cdcf156363128000 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_1.tar.bz2#9e07b83f7d5e70bb48d13938ff93faf9 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py38h578d9bd_1.tar.bz2#8f00ce2589f003a6c796e23073765602 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py38h578d9bd_1.tar.bz2#6b6a356f4ff4a8b26b74add4ba8c0c22 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index ef6cdc9b9e..51a0fb2dbd 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 850fe195d775cf7364300e20b0da20712bd1e1ce31f156011cba771e908fae9b +# input_hash: 1306a5e6925617b42f229cf1688af21e43e70dc6fd9a54e44dbec125671aeba8 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -12,7 +12,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022b-h191b570_0.tar.bz2#48806dd9fc8893fc52aafbdc349e77e0 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab @@ -42,14 +42,13 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_0.tar.bz2#8220a603d6822487affcaaf72960fca1 +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_1.tar.bz2#45127206f58f9989f96041c6e87a534a https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -76,6 +75,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -89,16 +90,15 @@ https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h8a70e8d_4.tar.bz2#a6b5e941bc2998fbd59c84645247c2c7 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-h727a467_0.tar.bz2#a22567abfea169ff8048506b1ca9b230 -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_3.tar.bz2#3e868978a04de8bf65a97bb86760f47a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 @@ -111,7 +111,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.10.4-hca18f0e_2.tar.bz2#cfd942cf6d2c31dc00f91d1b2dcd0f80 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -123,13 +123,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.4.0-hb52868f_1.tar.bz2#b7ad78ad2e9ee155f59e6428406ee824 +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/linux-64/python-3.9.13-h9a8a25e_0_cpython.tar.bz2#69bc307cc4d7396c5fccb26bbcc9c379 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -139,7 +140,6 @@ https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e @@ -148,6 +148,7 @@ https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 @@ -158,7 +159,7 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2-pyhd8ed1ab_0.tar.bz2#dc99e49653ff49ff64a4a3d698124ba4 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -177,21 +178,20 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_2.tar.bz2#fea5dea40592ea943aa56f4935308ee4 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_3.tar.bz2#d7f3ed5d1c3c52b5fc6ba4474bf85184 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_2.tar.bz2#b809706525f081610469169b671b2600 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.1-py39hba7629e_0.tar.bz2#ee8dff1fb28e0e2c458e845aae9d915a +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py39hba7629e_0.tar.bz2#25285f960f9c7f4e8ef56171af5e2a22 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hae2aec6_1.tar.bz2#b1a83be945a712e6d2943c76d0d15dfd +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hd5dbb17_2.tar.bz2#3b74a959f6a8008f5901de60b3572c09 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef @@ -200,7 +200,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-64.0.3-py39hf3d152e_0.tar.bz2#e4b47ae0a0025ae73212c11bf9555955 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py39hf3d152e_0.tar.bz2#9065ec0b391ae4d56ff832c3c9d103c2 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f @@ -209,16 +209,16 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.34.4-py39hb9d737c_0.tar.bz2#7980ace37ccb3399672c3a9840e039ed +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py39hb9d737c_0.tar.bz2#f883146012e119b78120fc23eeba297c https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.12.0-pyhd8ed1ab_0.tar.bz2#cb27e2ded147e5bcc7eafc1c6d343cb3 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 @@ -231,26 +231,25 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.2-py39h700656a_1.tar.bz2#b8d1b075536dfca33459870dbb824886 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py39h700656a_1.tar.bz2#43191b76750399349519f53feac83094 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h71b8e10_101.tar.bz2#91e01aa93a2bcca96c9d64d2ce4f65f0 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_1.tar.bz2#7264fa97520021d1a01f5be9741493c2 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.2-py39hf3d152e_1.tar.bz2#7a3ff2d1e66e674631a65ea32d5cdbbd +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py39hf3d152e_1.tar.bz2#bed9a319c4a3ac57376f5d95ba923d78 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index fc061e8bfc..81c33c494b 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -21,6 +21,7 @@ dependencies: - python-xxhash - pyproj - scipy + - shapely < 1.8.3 # Optional dependencies. - esmpy >=7.0 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 7a67e4ecc3..6353cfa3a5 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -21,6 +21,7 @@ dependencies: - python-xxhash - pyproj - scipy + - shapely < 1.8.3 # Optional dependencies. - esmpy >=7.0 diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index ff50f24a56..14a93f00fd 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -21,6 +21,7 @@ dependencies: - python-xxhash - pyproj - scipy + - shapely < 1.8.3 # Optional dependencies. - esmpy >=7.0 From 505dca8ca5d7fbe5b45d2e24180280cd62ebfab8 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 17 Aug 2022 17:13:41 +0100 Subject: [PATCH 196/319] Improvements to Pandas-to-Iris bridge (#4890) * First pass using merge to parse ndim Pandas DataFrame. * Second pass actually doing array manipulation this time. * Tidying for public consumption. * Improved index handling. * Pytest conversion. * Minor re-factoring. * Support for copy argument. * Calendar handling. * Correct coord reshaping. * Correct array indexing. * DataFrame Series agnosticism. * Docstrings and structure. * Error as_cube if FUTURE behaviour is set. * Tests WIP. * More tests. * Completed tests. * Suppress deprecation warning in TestSeriesAsCube. * Docstring corrections. * Another docstring fix. * Another docstring fix. * Another docstring fix. * Doctests. * Doctests. * Strict expectation of one Cube in tests. * format_dimensional_metadata typo. * Rename pandas_array to pandas_structure. * Leaner warnings for Series with column arguments. * Fix date tests. * Back out pandas_ndim FUTURE flag. * Uncomment code, whoops. * What's new entry. * Remove erroneous pass. * Doctest typo. * Handle an empty Pandas structure. * Test showing that dimension order is preserved. * Informative error for ragged indexes. --- docs/src/conf.py | 1 + docs/src/whatsnew/latest.rst | 13 +- lib/iris/pandas.py | 435 +++++++++++++++++++++++++++-- lib/iris/tests/test_pandas.py | 509 ++++++++++++++++++++++++++++++++-- 4 files changed, 902 insertions(+), 56 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 082d7eb384..7f0c517e05 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -225,6 +225,7 @@ def _dotv(version): "numpy": ("https://numpy.org/doc/stable/", None), "python": ("https://docs.python.org/3/", None), "scipy": ("https://docs.scipy.org/doc/scipy/", None), + "pandas": ("https://pandas.pydata.org/docs/", None), } # The name of the Pygments (syntax highlighting) style to use. diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a6f4423639..13c5f069f0 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -89,6 +89,14 @@ This document explains the changes made to Iris for this release #. `@stephenworsley`_ updated to the latest CF Standard Names Table ``v79`` (19 March 2022). (:pull:`4910`) +#. `@trexfeathers`_ and `@lbdreyer`_ (reviewer) added + :func:`iris.pandas.as_cubes`, which provides richer conversion from + Pandas :class:`~pandas.Series` / :class:`~pandas.DataFrame`\s to one or more + :class:`~iris.cube.Cube`\s. This includes: n-dimensional datasets, + :class:`~iris.coords.AuxCoord`\s, :class:`~iris.coords.CellMeasure`\s, + :class:`~iris.coords.AncillaryVariable`\s, and multi-dimensional + coordinates. (:pull:`4890`) + 🐛 Bugs Fixed ============= @@ -190,7 +198,10 @@ This document explains the changes made to Iris for this release 🔥 Deprecations =============== -#. N/A +#. `@trexfeathers`_ and `@lbdreyer`_ (reviewer) deprecated + :func:`iris.pandas.as_cube` in favour of the new + :func:`iris.pandas.as_cubes` - see `✨ Features`_ for more details. + (:pull:`4890`) 🔗 Dependencies diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index 6b35a1d1cd..b00eb3f117 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -11,6 +11,8 @@ """ import datetime +from itertools import chain, combinations +import warnings import cf_units from cf_units import Unit @@ -25,13 +27,14 @@ from pandas.tseries.index import DatetimeIndex # pandas <0.20 import iris -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube +from iris._deprecation import warn_deprecated +from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord +from iris.cube import Cube, CubeList -def _add_iris_coord(cube, name, points, dim, calendar=None): +def _get_dimensional_metadata(name, values, calendar=None, dm_class=None): """ - Add a Coord to a Cube from a Pandas index or columns array. + Create a Coord or other dimensional metadata from a Pandas index or columns array. If no calendar is specified for a time series, Standard is assumed. @@ -40,54 +43,130 @@ def _add_iris_coord(cube, name, points, dim, calendar=None): if calendar is None: calendar = cf_units.CALENDAR_STANDARD - # Convert pandas datetime objects to python datetime obejcts. - if isinstance(points, DatetimeIndex): - points = np.array([i.to_pydatetime() for i in points]) + # Getting everything into a single datetime format is hard! + + # Convert out of NumPy's own datetime format. + if np.issubdtype(values.dtype, np.datetime64): + values = pandas.to_datetime(values) + + # Convert pandas datetime objects to python datetime objects. + if isinstance(values, DatetimeIndex): + values = np.array([i.to_pydatetime() for i in values]) # Convert datetime objects to Iris' current datetime representation. - if points.dtype == object: + if values.dtype == object: dt_types = (datetime.datetime, cftime.datetime) - if all([isinstance(i, dt_types) for i in points]): + if all([isinstance(i, dt_types) for i in values]): units = Unit("hours since epoch", calendar=calendar) - points = units.date2num(points) - - points = np.array(points) - if np.issubdtype(points.dtype, np.number) and iris.util.monotonic( - points, strict=True - ): - coord = DimCoord(points, units=units) - coord.rename(name) + values = units.date2num(values) + + values = np.array(values) + + if dm_class is None: + if np.issubdtype(values.dtype, np.number) and iris.util.monotonic( + values, strict=True + ): + dm_class = DimCoord + else: + dm_class = AuxCoord + + instance = dm_class(values, units=units) + if name is not None: + # Use rename() to attempt standard_name but fall back on long_name. + instance.rename(str(name)) + + return instance + + +def _add_iris_coord(cube, name, points, dim, calendar=None): + """ + Add a Coord or other dimensional metadata to a Cube from a Pandas index or columns array. + """ + # Most functionality has been abstracted to _get_dimensional_metadata, + # allowing re-use in as_cube() and as_cubes(). + coord = _get_dimensional_metadata(name, points, calendar) + + if coord.__class__ == DimCoord: cube.add_dim_coord(coord, dim) else: - coord = AuxCoord(points, units=units) - coord.rename(name) cube.add_aux_coord(coord, dim) -def as_cube(pandas_array, copy=True, calendars=None): +def _series_index_unique(pandas_series: pandas.Series): """ - Convert a Pandas array into an Iris cube. + Find an index grouping of a :class:`pandas.Series` that has just one Series value per group. - Args: + Iterates through grouping single index levels, then combinations of 2 + levels, then 3 etcetera, until single :class:`~pandas.Series` values per + group are found. Returns a ``tuple`` of the index levels that group to + produce single values, as soon as one is found. - * pandas_array - A Pandas Series or DataFrame. + Returns ``None`` if no index level combination produces single values. - Kwargs: + """ + unique_number = pandas_series.nunique() + pandas_index = pandas_series.index + levels_range = range(pandas_index.nlevels) + if unique_number == 1: + # Scalar - identical for all indices. + result = () + else: + result = None + levels_combinations = chain( + *[ + combinations(levels_range, levels + 1) + for levels in levels_range + ] + ) + for lc in levels_combinations: + if pandas_series.groupby(level=lc).nunique().max() == 1: + result = lc + # Escape as early as possible - heavy operation. + break + return result + + +def as_cube( + pandas_array, + copy=True, + calendars=None, +): + """ + Convert a Pandas Series/DataFrame into a 1D/2D Iris Cube. + + .. deprecated:: 3.3.0 + + This function is scheduled for removal in a future release, being + replaced by :func:`iris.pandas.as_cubes`, which offers richer + dimensional intelligence. - * copy - Whether to make a copy of the data. - Defaults to True. + Parameters + ---------- + pandas_array : :class:`pandas.Series` or :class:`pandas.DataFrame` + The Pandas object to convert + copy : bool, default=True + Whether to copy `pandas_array`, or to create array views where + possible. Provided in case of memory limit concerns. + calendars : dict, optional + A dict mapping a dimension to a calendar. Required to convert datetime + indices/columns. - * calendars - A dict mapping a dimension to a calendar. - Required to convert datetime indices/columns. + Notes + ----- + This function will copy your data by default. Example usage:: as_cube(series, calendars={0: cf_units.CALENDAR_360_DAY}) as_cube(data_frame, calendars={1: cf_units.CALENDAR_STANDARD}) - .. note:: This function will copy your data by default. - """ + message = ( + "iris.pandas.as_cube has been deprecated, and will be removed in a " + "future release. Please use iris.pandas.as_cubes instead." + ) + warn_deprecated(message) + calendars = calendars or {} if pandas_array.ndim not in [1, 2]: raise ValueError( @@ -116,6 +195,302 @@ def as_cube(pandas_array, copy=True, calendars=None): return cube +def as_cubes( + pandas_structure, + copy=True, + calendars=None, + aux_coord_cols=None, + cell_measure_cols=None, + ancillary_variable_cols=None, +): + """ + Convert a Pandas Series/DataFrame into n-dimensional Iris Cubes, including dimensional metadata. + + The index of `pandas_structure` will be used for generating the + :class:`~iris.cube.Cube` dimension(s) and :class:`~iris.coords.DimCoord`\\ s. + Other dimensional metadata may span multiple dimensions - based on how the + column values vary with the index values. + + Parameters + ---------- + pandas_structure : :class:`pandas.Series` or :class:`pandas.DataFrame` + The Pandas object to convert + copy : bool, default=True + Whether the Cube :attr:`~iris.cube.Cube.data` is a copy of the + `pandas_structure` column, or a view of the same array. Arrays other than + the data (coords etc.) are always copies. This option is provided to + help with memory size concerns. + calendars : dict, optional + Calendar conversions for individual date-time coordinate + columns/index-levels e.g. ``{"my_column": cf_units.CALENDAR_360_DAY}``. + aux_coord_cols, cell_measure_cols, ancillary_variable_cols : list of str, optional + Names of columns to be converted into :class:`~iris.coords.AuxCoord`, + :class:`~iris.coords.CellMeasure` and + :class:`~iris.coords.AncillaryVariable` objects. + + Returns + -------- + :class:`~iris.cube.CubeList` + One :class:`~iris.cube.Cube` for each column not referenced in + `aux_coord_cols`/`cell_measure_cols`/`ancillary_variable_cols`. + + Notes + ----- + A :class:`~pandas.DataFrame` using columns as a second data dimension will + need to be 'melted' before conversion. See the Examples for how. + + Dask ``DataFrame``\\s are not supported. + + Examples + -------- + >>> from iris.pandas import as_cubes + >>> import numpy as np + >>> from pandas import DataFrame, Series + + Converting a simple :class:`~pandas.Series` : + + >>> my_series = Series([300, 301, 302], name="air_temperature") + >>> converted_cubes = as_cubes(my_series) + >>> print(converted_cubes) + 0: air_temperature / (unknown) (unknown: 3) + >>> print(converted_cubes[0]) + air_temperature / (unknown) (unknown: 3) + Dimension coordinates: + unknown x + + A :class:`~pandas.DataFrame`, with a custom index becoming the + :class:`~iris.coords.DimCoord` : + + >>> my_df = DataFrame({ + ... "air_temperature": [300, 301, 302], + ... "longitude": [30, 40, 50] + ... }) + >>> my_df = my_df.set_index("longitude") + >>> converted_cubes = as_cubes(my_df) + >>> print(converted_cubes[0]) + air_temperature / (unknown) (longitude: 3) + Dimension coordinates: + longitude x + + A :class:`~pandas.DataFrame` representing two 3-dimensional datasets, + including a 2-dimensional :class:`~iris.coords.AuxCoord` : + + >>> my_df = DataFrame({ + ... "air_temperature": np.arange(300, 312, 1), + ... "air_pressure": np.arange(1000, 1012, 1), + ... "longitude": [0, 10] * 6, + ... "latitude": [25, 25, 35, 35] * 3, + ... "height": ([0] * 4) + ([100] * 4) + ([200] * 4), + ... "in_region": [True, False, False, False] * 3 + ... }) + >>> print(my_df) + air_temperature air_pressure longitude latitude height in_region + 0 300 1000 0 25 0 True + 1 301 1001 10 25 0 False + 2 302 1002 0 35 0 False + 3 303 1003 10 35 0 False + 4 304 1004 0 25 100 True + 5 305 1005 10 25 100 False + 6 306 1006 0 35 100 False + 7 307 1007 10 35 100 False + 8 308 1008 0 25 200 True + 9 309 1009 10 25 200 False + 10 310 1010 0 35 200 False + 11 311 1011 10 35 200 False + >>> my_df = my_df.set_index(["longitude", "latitude", "height"]) + >>> my_df = my_df.sort_index() + >>> converted_cubes = as_cubes(my_df, aux_coord_cols=["in_region"]) + >>> print(converted_cubes) + 0: air_temperature / (unknown) (longitude: 2; latitude: 2; height: 3) + 1: air_pressure / (unknown) (longitude: 2; latitude: 2; height: 3) + >>> print(converted_cubes[0]) + air_temperature / (unknown) (longitude: 2; latitude: 2; height: 3) + Dimension coordinates: + longitude x - - + latitude - x - + height - - x + Auxiliary coordinates: + in_region x x - + + Pandas uses ``NaN`` rather than masking data. Converted + :class:`~iris.cube.Cube`\\s can be masked in downstream user code : + + >>> my_series = Series([300, np.NaN, 302], name="air_temperature") + >>> converted_cube = as_cubes(my_series)[0] + >>> print(converted_cube.data) + [300. nan 302.] + >>> converted_cube.data = np.ma.masked_invalid(converted_cube.data) + >>> print(converted_cube.data) + [300.0 -- 302.0] + + If the :class:`~pandas.DataFrame` uses columns as a second dimension, + :func:`pandas.melt` should be used to convert the data to the expected + n-dimensional format : + + >>> my_df = DataFrame({ + ... "latitude": [35, 25], + ... 0: [300, 301], + ... 10: [302, 303], + ... }) + >>> print(my_df) + latitude 0 10 + 0 35 300 302 + 1 25 301 303 + >>> my_df = my_df.melt( + ... id_vars=["latitude"], + ... value_vars=[0, 10], + ... var_name="longitude", + ... value_name="air_temperature" + ... ) + >>> print(my_df) + latitude longitude air_temperature + 0 35 0 300 + 1 25 0 301 + 2 35 10 302 + 3 25 10 303 + >>> my_df = my_df.set_index(["latitude", "longitude"]) + >>> my_df = my_df.sort_index() + >>> converted_cube = as_cubes(my_df)[0] + >>> print(converted_cube) + air_temperature / (unknown) (latitude: 2; longitude: 2) + Dimension coordinates: + latitude x - + longitude - x + + """ + if pandas_structure.empty: + return CubeList() + + calendars = calendars or {} + aux_coord_cols = aux_coord_cols or [] + cell_measure_cols = cell_measure_cols or [] + ancillary_variable_cols = ancillary_variable_cols or [] + + is_series = isinstance(pandas_structure, pandas.Series) + + if copy: + pandas_structure = pandas_structure.copy() + + pandas_index = pandas_structure.index + if not pandas_index.is_unique: + message = ( + f"DataFrame index ({pandas_index.names}) is not unique per " + "row; cannot be used for DimCoords." + ) + raise ValueError(message) + + if not pandas_index.is_monotonic: + # Need monotonic index for use in DimCoord(s). + # This function doesn't sort_index itself since that breaks the + # option to return a data view instead of a copy. + message = ( + "Pandas index is not monotonic. Consider using the " + "sort_index() method before passing in." + ) + raise ValueError(message) + + cube_shape = getattr(pandas_index, "levshape", (pandas_index.nunique(),)) + n_rows = len(pandas_structure) + if np.product(cube_shape) > n_rows: + message = ( + f"Not all index values have a corresponding row - {n_rows} rows " + f"cannot be reshaped into {cube_shape}. Consider padding with NaN " + "rows where needed." + ) + raise ValueError(message) + + cube_kwargs = {} + + def format_dimensional_metadata(dm_class_, values_, name_, dimensions_): + # Common convenience to get the right DM in the right format for + # Cube creation. + calendar = calendars.get(name_) + instance = _get_dimensional_metadata( + name_, values_, calendar, dm_class_ + ) + return (instance, dimensions_) + + # DimCoords. + dim_coord_kwarg = [] + for ix, dim_name in enumerate(pandas_index.names): + if hasattr(pandas_index, "levels"): + coord_points = pandas_index.levels[ix] + else: + coord_points = pandas_index + new_dim_coord = format_dimensional_metadata( + DimCoord, coord_points, dim_name, ix + ) + dim_coord_kwarg.append(new_dim_coord) + cube_kwargs["dim_coords_and_dims"] = dim_coord_kwarg + + # Other dimensional metadata. + class_arg_mapping = [ + (AuxCoord, aux_coord_cols, "aux_coords_and_dims"), + (CellMeasure, cell_measure_cols, "cell_measures_and_dims"), + ( + AncillaryVariable, + ancillary_variable_cols, + "ancillary_variables_and_dims", + ), + ] + + if is_series: + columns_ignored = any([len(t[1]) > 0 for t in class_arg_mapping]) + if columns_ignored: + ignored_args = ", ".join([t[2] for t in class_arg_mapping]) + message = f"The input pandas_structure is a Series; ignoring arguments: {ignored_args} ." + warnings.warn(message) + class_arg_mapping = [] + + non_data_names = [] + for dm_class, column_names, kwarg in class_arg_mapping: + class_kwarg = [] + non_data_names.extend(column_names) + for column_name in column_names: + column = pandas_structure[column_name] + + # Should be impossible for None to be returned - would require a + # non-unique index, which we protect against. + dimensions = _series_index_unique(column) + + content = column.to_numpy() + # Remove duplicate entries to get down to the correct dimensions + # for this object. _series_index_unique should have ensured + # that we are indeed removing the duplicates. + shaped = content.reshape(cube_shape) + indices = [0] * len(cube_shape) + for dim in dimensions: + indices[dim] = slice(None) + collapsed = shaped[tuple(indices)] + + new_dm = format_dimensional_metadata( + dm_class, collapsed, column_name, dimensions + ) + class_kwarg.append(new_dm) + + cube_kwargs[kwarg] = class_kwarg + + # Cube creation. + if is_series: + data_series_list = [pandas_structure] + else: + data_series_list = [ + pandas_structure[column_name] + for column_name in pandas_structure.columns + if column_name not in non_data_names + ] + cubes = CubeList() + for data_series in data_series_list: + cube_data = data_series.to_numpy().reshape(cube_shape) + new_cube = Cube(cube_data, **cube_kwargs) + if data_series.name is not None: + # Use rename() to attempt standard_name but fall back on long_name. + new_cube.rename(str(data_series.name)) + cubes.append(new_cube) + + return cubes + + def _as_pandas_coord(coord): """Convert an Iris Coord into a Pandas index or columns array.""" index = coord.points diff --git a/lib/iris/tests/test_pandas.py b/lib/iris/tests/test_pandas.py index 208f7b944e..f47df75def 100644 --- a/lib/iris/tests/test_pandas.py +++ b/lib/iris/tests/test_pandas.py @@ -10,12 +10,16 @@ import copy import datetime -import unittest +from termios import IXOFF # noqa: F401 import cf_units import cftime import matplotlib.units import numpy as np +import pytest + +import iris +from iris._deprecation import IrisDeprecation # Importing pandas has the side-effect of messing with the formatters # used by matplotlib for handling dates. @@ -27,13 +31,14 @@ pandas = None matplotlib.units.registry = default_units_registry -skip_pandas = unittest.skipIf( - pandas is None, 'Test(s) require "pandas", ' "which is not available." +skip_pandas = pytest.mark.skipif( + pandas is None, + reason='Test(s) require "pandas", ' "which is not available.", ) if pandas is not None: - from iris.coords import DimCoord - from iris.cube import Cube + from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord + from iris.cube import Cube, CubeList import iris.pandas @@ -80,7 +85,7 @@ def test_time_standard(self): ] series = iris.pandas.as_series(cube) self.assertArrayEqual(series, cube.data) - self.assertListEqual(list(series.index), expected_index) + assert list(series.index) == expected_index def test_time_360(self): cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") @@ -107,37 +112,37 @@ def test_copy_true(self): cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") series = iris.pandas.as_series(cube) series[0] = 99 - self.assertEqual(cube.data[0], 0) + assert cube.data[0] == 0 def test_copy_int32_false(self): cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int32), long_name="foo") series = iris.pandas.as_series(cube, copy=False) series[0] = 99 - self.assertEqual(cube.data[0], 99) + assert cube.data[0] == 99 def test_copy_int64_false(self): cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int32), long_name="foo") series = iris.pandas.as_series(cube, copy=False) series[0] = 99 - self.assertEqual(cube.data[0], 99) + assert cube.data[0] == 99 def test_copy_float_false(self): cube = Cube(np.array([0, 1, 2, 3.3, 4]), long_name="foo") series = iris.pandas.as_series(cube, copy=False) series[0] = 99 - self.assertEqual(cube.data[0], 99) + assert cube.data[0] == 99 def test_copy_masked_true(self): data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) cube = Cube(data, long_name="foo") series = iris.pandas.as_series(cube) series[0] = 99 - self.assertEqual(cube.data[0], 0) + assert cube.data[0] == 0 def test_copy_masked_false(self): data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) cube = Cube(data, long_name="foo") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = iris.pandas.as_series(cube, copy=False) @@ -230,8 +235,8 @@ def test_time_standard(self): ) for day_offset in day_offsets ] - self.assertTrue(all(data_frame.columns == timestamps)) - self.assertTrue(all(data_frame.index == [0, 1])) + assert all(data_frame.columns == timestamps) + assert all(data_frame.index == [0, 1]) def test_time_360(self): cube = Cube( @@ -261,7 +266,7 @@ def test_copy_true(self): ) data_frame = iris.pandas.as_data_frame(cube) data_frame[0][0] = 99 - self.assertEqual(cube.data[0, 0], 0) + assert cube.data[0, 0] == 0 def test_copy_int32_false(self): cube = Cube( @@ -270,7 +275,7 @@ def test_copy_int32_false(self): ) data_frame = iris.pandas.as_data_frame(cube, copy=False) data_frame[0][0] = 99 - self.assertEqual(cube.data[0, 0], 99) + assert cube.data[0, 0] == 99 def test_copy_int64_false(self): cube = Cube( @@ -279,7 +284,7 @@ def test_copy_int64_false(self): ) data_frame = iris.pandas.as_data_frame(cube, copy=False) data_frame[0][0] = 99 - self.assertEqual(cube.data[0, 0], 99) + assert cube.data[0, 0] == 99 def test_copy_float_false(self): cube = Cube( @@ -287,7 +292,7 @@ def test_copy_float_false(self): ) data_frame = iris.pandas.as_data_frame(cube, copy=False) data_frame[0][0] = 99 - self.assertEqual(cube.data[0, 0], 99) + assert cube.data[0, 0] == 99 def test_copy_masked_true(self): data = np.ma.MaskedArray( @@ -297,7 +302,7 @@ def test_copy_masked_true(self): cube = Cube(data, long_name="foo") data_frame = iris.pandas.as_data_frame(cube) data_frame[0][0] = 99 - self.assertEqual(cube.data[0, 0], 0) + assert cube.data[0, 0] == 0 def test_copy_masked_false(self): data = np.ma.MaskedArray( @@ -305,7 +310,7 @@ def test_copy_masked_false(self): mask=[[0, 1, 0, 1, 0], [1, 0, 1, 0, 1]], ) cube = Cube(data, long_name="foo") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = iris.pandas.as_data_frame(cube, copy=False) def test_copy_false_with_cube_view(self): @@ -313,10 +318,13 @@ def test_copy_false_with_cube_view(self): cube = Cube(data[:], long_name="foo") data_frame = iris.pandas.as_data_frame(cube, copy=False) data_frame[0][0] = 99 - self.assertEqual(cube.data[0, 0], 99) + assert cube.data[0, 0] == 99 @skip_pandas +@pytest.mark.filterwarnings( + "ignore:.*as_cube has been deprecated.*:iris._deprecation.IrisDeprecation" +) class TestSeriesAsCube(tests.IrisTest): def test_series_simple(self): series = pandas.Series([0, 1, 2, 3, 4], index=[5, 6, 7, 8, 9]) @@ -390,16 +398,19 @@ def test_copy_true(self): series = pandas.Series([0, 1, 2, 3, 4], index=[5, 6, 7, 8, 9]) cube = iris.pandas.as_cube(series) cube.data[0] = 99 - self.assertEqual(series[5], 0) + assert series[5] == 0 def test_copy_false(self): series = pandas.Series([0, 1, 2, 3, 4], index=[5, 6, 7, 8, 9]) cube = iris.pandas.as_cube(series, copy=False) cube.data[0] = 99 - self.assertEqual(series[5], 99) + assert series[5] == 99 @skip_pandas +@pytest.mark.filterwarnings( + "ignore:.*as_cube has been deprecated.*:iris._deprecation.IrisDeprecation" +) class TestDataFrameAsCube(tests.IrisTest): def test_data_frame_simple(self): data_frame = pandas.DataFrame( @@ -491,13 +502,461 @@ def test_copy_true(self): data_frame = pandas.DataFrame([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) cube = iris.pandas.as_cube(data_frame) cube.data[0, 0] = 99 - self.assertEqual(data_frame[0][0], 0) + assert data_frame[0][0] == 0 def test_copy_false(self): data_frame = pandas.DataFrame([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) cube = iris.pandas.as_cube(data_frame, copy=False) cube.data[0, 0] = 99 - self.assertEqual(data_frame[0][0], 99) + assert data_frame[0][0] == 99 + + +@skip_pandas +class TestFutureAndDeprecation(tests.IrisTest): + def test_deprecation_warning(self): + data_frame = pandas.DataFrame([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]) + with pytest.warns( + IrisDeprecation, match="as_cube has been deprecated" + ): + _ = iris.pandas.as_cube(data_frame) + + # Tests for FUTURE are expected when as_dataframe() is made n-dimensional. + + +@skip_pandas +class TestPandasAsCubes(tests.IrisTest): + @staticmethod + def _create_pandas(index_levels=0, is_series=False): + index_length = 3 + + index_names = [f"index_{i}" for i in range(index_levels)] + index_values = [ + np.arange(index_length) * 10 * (i + 1) for i in range(index_levels) + ] + + if index_levels == 1: + index = pandas.Index(index_values[0], name=index_names[0]) + data_length = index_length + elif index_levels > 1: + index = pandas.MultiIndex.from_product( + index_values, names=index_names + ) + data_length = index.nunique() + else: + index = None + data_length = index_length + + data = np.arange(data_length) * 10 + + if is_series: + class_ = pandas.Series + else: + class_ = pandas.DataFrame + + return class_(data, index=index) + + def test_1d_no_index(self): + df = self._create_pandas() + result = iris.pandas.as_cubes(df) + + expected_coord = DimCoord(df.index.values) + expected_cube = Cube( + data=df[0].values, + long_name=str(df[0].name), + dim_coords_and_dims=[(expected_coord, 0)], + ) + assert result == [expected_cube] + + def test_1d_with_index(self): + df = self._create_pandas(index_levels=1) + result = iris.pandas.as_cubes(df) + + expected_coord = DimCoord(df.index.values, long_name=df.index.name) + (result_cube,) = result + assert result_cube.dim_coords == (expected_coord,) + + def test_1d_series_no_index(self): + series = self._create_pandas(is_series=True) + result = iris.pandas.as_cubes(series) + + expected_coord = DimCoord(series.index.values) + expected_cube = Cube( + data=series.values, dim_coords_and_dims=[(expected_coord, 0)] + ) + assert result == [expected_cube] + + def test_1d_series_with_index(self): + series = self._create_pandas(index_levels=1, is_series=True) + result = iris.pandas.as_cubes(series) + + expected_coord = DimCoord( + series.index.values, long_name=series.index.name + ) + (result_cube,) = result + assert result_cube.dim_coords == (expected_coord,) + + def test_3d(self): + df = self._create_pandas(index_levels=3) + result = iris.pandas.as_cubes(df) + + expected_coords = [ + DimCoord(level.values, long_name=level.name) + for level in df.index.levels + ] + (result_cube,) = result + assert result_cube.dim_coords == tuple(expected_coords) + + def test_3d_series(self): + series = self._create_pandas(index_levels=3, is_series=True) + result = iris.pandas.as_cubes(series) + + expected_coords = [ + DimCoord(level.values, long_name=level.name) + for level in series.index.levels + ] + (result_cube,) = result + assert result_cube.dim_coords == tuple(expected_coords) + + def test_non_unique_index(self): + df = self._create_pandas(index_levels=1) + new_index = df.index.values + new_index[1] = new_index[0] + df.set_index(new_index) + + with pytest.raises(ValueError, match="not unique per row"): + _ = iris.pandas.as_cubes(df) + + def test_non_monotonic_index(self): + df = self._create_pandas(index_levels=1) + new_index = df.index.values + new_index[:2] = new_index[1::-1] + df.set_index(new_index) + + with pytest.raises(ValueError, match="not monotonic"): + _ = iris.pandas.as_cubes(df) + + def test_missing_rows(self): + df = self._create_pandas(index_levels=2) + df = df[:-1] + + with pytest.raises( + ValueError, match="Not all index values have a corresponding row" + ): + _ = iris.pandas.as_cubes(df) + + def test_aux_coord(self): + df = self._create_pandas() + coord_name = "foo" + df[coord_name] = df.index.values + result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) + + expected_aux_coord = AuxCoord( + df[coord_name].values, long_name=coord_name + ) + (result_cube,) = result + assert result_cube.aux_coords == (expected_aux_coord,) + + def test_cell_measure(self): + df = self._create_pandas() + coord_name = "foo" + df[coord_name] = df.index.values + result = iris.pandas.as_cubes(df, cell_measure_cols=[coord_name]) + + expected_cm = CellMeasure(df[coord_name].values, long_name=coord_name) + (result_cube,) = result + assert result_cube.cell_measures() == [expected_cm] + + def test_ancillary_variable(self): + df = self._create_pandas() + coord_name = "foo" + df[coord_name] = df.index.values + result = iris.pandas.as_cubes(df, ancillary_variable_cols=[coord_name]) + + expected_av = AncillaryVariable( + df[coord_name].values, long_name=coord_name + ) + (result_cube,) = result + assert result_cube.ancillary_variables() == [expected_av] + + def test_3d_with_2d_coord(self): + df = self._create_pandas(index_levels=3) + coord_shape = df.index.levshape[:2] + coord_values = np.arange(np.product(coord_shape)) + coord_name = "foo" + df[coord_name] = coord_values.repeat(df.index.levshape[-1]) + result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) + + expected_points = coord_values.reshape(coord_shape) + (result_cube,) = result + result_coord = result_cube.coord(coord_name) + self.assertArrayEqual(result_coord.points, expected_points) + assert result_coord.cube_dims(result_cube) == (0, 1) + + def test_coord_varies_all_indices(self): + df = self._create_pandas(index_levels=3) + coord_shape = df.index.levshape + coord_values = np.arange(np.product(coord_shape)) + coord_name = "foo" + df[coord_name] = coord_values + result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) + + expected_points = coord_values.reshape(coord_shape) + (result_cube,) = result + result_coord = result_cube.coord(coord_name) + self.assertArrayEqual(result_coord.points, expected_points) + assert result_coord.cube_dims(result_cube) == (0, 1, 2) + + def test_category_coord(self): + # Something that varies on a dimension, but doesn't change with every + # increment. + df = self._create_pandas(index_levels=2) + coord_shape = df.index.levshape + coord_values = np.arange(np.product(coord_shape)) + coord_name = "foo" + + # Create a repeating value along a dimension. + step = coord_shape[-1] + coord_values[1::step] = coord_values[::step] + + df[coord_name] = coord_values + result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) + + expected_points = coord_values.reshape(coord_shape) + (result_cube,) = result + result_coord = result_cube.coord(coord_name) + self.assertArrayEqual(result_coord.points, expected_points) + assert result_coord.cube_dims(result_cube) == (0, 1) + + def test_scalar_coord(self): + df = self._create_pandas() + coord_values = np.ones(len(df)) + coord_name = "foo" + df[coord_name] = coord_values + result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) + + expected_points = np.unique(coord_values) + (result_cube,) = result + result_coord = result_cube.coord(coord_name) + self.assertArrayEqual(result_coord.points, expected_points) + assert result_coord.cube_dims(result_cube) == tuple() + + def test_multi_phenom(self): + df = self._create_pandas() + new_name = "new_phenom" + df[new_name] = df[0] + result = iris.pandas.as_cubes(df) + + # Note the shared coord object between both Cubes. + expected_coord = DimCoord(df.index.values) + expected_cube_kwargs = dict(dim_coords_and_dims=[(expected_coord, 0)]) + + expected_cube_0 = Cube( + data=df[0].values, + long_name=str(df[0].name), + **expected_cube_kwargs, + ) + expected_cube_1 = Cube( + data=df[new_name].values, + long_name=new_name, + **expected_cube_kwargs, + ) + assert result == [expected_cube_0, expected_cube_1] + + def test_empty_series(self): + series = pandas.Series(dtype=object) + result = iris.pandas.as_cubes(series) + + assert result == CubeList() + + def test_empty_dataframe(self): + df = pandas.DataFrame() + result = iris.pandas.as_cubes(df) + + assert result == CubeList() + + def test_no_phenom(self): + df = self._create_pandas() + # Specify the only column as an AuxCoord. + result = iris.pandas.as_cubes(df, aux_coord_cols=[0]) + + assert result == CubeList() + + def test_standard_name_phenom(self): + # long_name behaviour is tested in test_1d_no_index. + df = self._create_pandas() + new_name = "air_temperature" + df = df.rename(columns={0: new_name}) + result = iris.pandas.as_cubes(df) + + (result_cube,) = result + assert result_cube.standard_name == new_name + + def test_standard_name_coord(self): + # long_name behaviour is tested in test_1d_with_index. + df = self._create_pandas() + new_name = "longitude" + df.index.names = [new_name] + result = iris.pandas.as_cubes(df) + + (result_cube,) = result + result_coord = result_cube.coord(dim_coords=True) + assert result_coord.standard_name == new_name + + def test_dtype_preserved_phenom(self): + df = self._create_pandas() + df = df.astype("int32") + result = iris.pandas.as_cubes(df) + + (result_cube,) = result + assert result_cube.dtype == np.int32 + + def test_preserve_dim_order(self): + new_order = ["index_1", "index_0", "index_2"] + + df = self._create_pandas(index_levels=3) + df = df.reset_index() + df = df.set_index(new_order) + df = df.sort_index() + result = iris.pandas.as_cubes(df) + + (result_cube,) = result + dim_order = [c.name() for c in result_cube.dim_coords] + assert dim_order == new_order + + def test_dtype_preserved_coord(self): + df = self._create_pandas() + new_index = df.index.astype("float64") + df.index = new_index + result = iris.pandas.as_cubes(df) + + (result_cube,) = result + result_coord = result_cube.coord(dim_coords=True) + assert result_coord.dtype == np.float64 + + def test_string_phenom(self): + # Strings can be uniquely troublesome. + df = self._create_pandas() + new_values = [str(v) for v in df[0]] + df[0] = new_values + result = iris.pandas.as_cubes(df) + + (result_cube,) = result + self.assertArrayEqual(result_cube.data, new_values) + + def test_string_coord(self): + # Strings can be uniquely troublesome. + # Must test using an AuxCoord since strings cannot be DimCoords. + df = self._create_pandas() + new_points = [str(v) for v in df.index.values] + coord_name = "foo" + df[coord_name] = new_points + result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) + + (result_cube,) = result + result_coord = result_cube.coord(coord_name) + self.assertArrayEqual(result_coord.points, new_points) + + def test_series_with_col_args(self): + series = self._create_pandas(is_series=True) + with pytest.warns(Warning, match="is a Series; ignoring"): + _ = iris.pandas.as_cubes(series, aux_coord_cols=["some_column"]) + + def test_phenom_view(self): + df = self._create_pandas() + result = iris.pandas.as_cubes(df, copy=False) + + # Modify AFTER creating the Cube(s). + df[0][0] += 1 + + (result_cube,) = result + assert result_cube.data[0] == df[0][0] + + def test_phenom_copy(self): + df = self._create_pandas() + result = iris.pandas.as_cubes(df) + + # Modify AFTER creating the Cube(s). + df[0][0] += 1 + + (result_cube,) = result + assert result_cube.data[0] != df[0][0] + + def test_coord_never_view(self): + # Using AuxCoord - DimCoords and Pandas indices are immutable. + df = self._create_pandas() + coord_name = "foo" + df[coord_name] = df.index.values + result = iris.pandas.as_cubes( + df, copy=False, aux_coord_cols=[coord_name] + ) + + # Modify AFTER creating the Cube(s). + df[coord_name][0] += 1 + + (result_cube,) = result + result_coord = result_cube.coord(coord_name) + assert result_coord.points[0] != df[coord_name][0] + + def _test_dates_common(self, mode=None, alt_calendar=False): + df = self._create_pandas() + kwargs = dict(pandas_structure=df) + coord_name = "dates" + + if alt_calendar: + calendar = cf_units.CALENDAR_360_DAY + # Only pass this when non-default. + kwargs["calendars"] = {coord_name: calendar} + expected_points = [8640, 8641, 8642] + else: + calendar = cf_units.CALENDAR_STANDARD + expected_points = [8760, 8761, 8762] + expected_units = cf_units.Unit( + "hours since 1970-01-01 00:00:00", calendar=calendar + ) + + datetime_args = [(1971, 1, 1, i, 0, 0) for i in df.index.values] + if mode == "index": + values = [datetime.datetime(*a) for a in datetime_args] + df.index = pandas.Index(values, name=coord_name) + elif mode == "numpy": + values = [datetime.datetime(*a) for a in datetime_args] + df[coord_name] = values + kwargs["aux_coord_cols"] = [coord_name] + elif mode == "cftime": + values = [ + cftime.datetime(*a, calendar=calendar) for a in datetime_args + ] + df[coord_name] = values + kwargs["aux_coord_cols"] = [coord_name] + else: + raise ValueError("mode needs to be set") + + result = iris.pandas.as_cubes(**kwargs) + + (result_cube,) = result + result_coord = result_cube.coord(coord_name) + assert result_coord.units == expected_units + self.assertArrayEqual(result_coord.points, expected_points) + + def test_datetime_index(self): + self._test_dates_common(mode="index") + + def test_datetime_index_calendar(self): + self._test_dates_common(mode="index", alt_calendar=True) + + def test_numpy_datetime_coord(self): + # NumPy format is what happens if a Python datetime is assigned to a + # Pandas column. + self._test_dates_common(mode="numpy") + + def test_numpy_datetime_coord_calendar(self): + self._test_dates_common(mode="numpy", alt_calendar=True) + + def test_cftime_coord(self): + self._test_dates_common(mode="cftime") + + def test_cftime_coord_calendar(self): + self._test_dates_common(mode="cftime", alt_calendar=True) if __name__ == "__main__": From 9d66819139fbefd9020c8a587faec008a76517d1 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:30:03 +0100 Subject: [PATCH 197/319] Include shapely pin in setup.cfg (#4917) * Include shapely pin in setup.cfg. * What's new entry. --- docs/src/whatsnew/latest.rst | 5 +++-- setup.cfg | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 13c5f069f0..abce4f520c 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -220,8 +220,9 @@ This document explains the changes made to Iris for this release #. `@trexfeathers`_ updated the install process to work with setuptools `>=v64`, making `v64` the minimum compatible version. (:pull:`4903`) -#. `@stephenworsley`_ introduced the ``shapely < 1.8.3`` maximum pin, avoiding a - bug caused by its interaction with cartopy. (:pull:`4911`) +#. `@stephenworsley`_ and `@trexfeathers`_ introduced the ``shapely < 1.8.3`` + maximum pin, avoiding a bug caused by its interaction with cartopy. + (:pull:`4911`, :pull:`4917`) 📚 Documentation diff --git a/setup.cfg b/setup.cfg index 2fe615e72a..25b0ecca5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,6 +53,7 @@ install_requires = netcdf4 numpy>=1.19 scipy + shapely<1.8.3 xxhash packages = find_namespace: package_dir = From 7b00dbe1b5f4a4c6c9d19915bb3039c02cd39976 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Thu, 18 Aug 2022 10:43:11 +0100 Subject: [PATCH 198/319] update whatsnew (#4914) --- docs/src/whatsnew/latest.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index abce4f520c..7fe9274493 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -217,6 +217,9 @@ This document explains the changes made to Iris for this release alignment of calendar behaviour in the two packages (see Incompatible Changes). (:pull:`4847`) +#. `@bjlittle`_ introduced the ``sphinx-gallery >=0.11.0`` minimum pin. + (:pull:`4885`) + #. `@trexfeathers`_ updated the install process to work with setuptools `>=v64`, making `v64` the minimum compatible version. (:pull:`4903`) From 32317d59555ebc4585d056d8b1cfe100c1fb573f Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 18 Aug 2022 10:44:51 +0100 Subject: [PATCH 199/319] Avoid using mutable default. (#4918) --- lib/iris/util.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/iris/util.py b/lib/iris/util.py index 7f57088bcd..3d82ea68c5 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -1096,7 +1096,7 @@ def format_array(arr): return result -def new_axis(src_cube, scalar_coord=None, expand_extras=[]): +def new_axis(src_cube, scalar_coord=None, expand_extras=()): """ Create a new axis as the leading dimension of the cube, promoting a scalar coordinate if specified. @@ -1111,10 +1111,10 @@ def new_axis(src_cube, scalar_coord=None, expand_extras=[]): * scalar_coord (:class:`iris.coord.Coord` or 'string') Scalar coordinate to promote to a dimension coordinate. - * expand_extras (list) - List of auxiliary coordinates, ancillary variables and cell measures - that will be expanded so that they map to the new dimension as well - as the existing dimensions. + * expand_extras (iterable) + Auxiliary coordinates, ancillary variables and cell measures which will + be expanded so that they map to the new dimension as well as the + existing dimensions. Returns: A new :class:`iris.cube.Cube` instance with one extra leading dimension From 03046658ef0c6d00303fd270badf417cc4c92d6b Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 18 Aug 2022 12:11:23 +0100 Subject: [PATCH 200/319] Fix some @trexfeathers What's New entries (#4920) * Minor What's New fixes. * Missing space. --- docs/src/whatsnew/latest.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 7fe9274493..e43f80034a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -220,8 +220,8 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ introduced the ``sphinx-gallery >=0.11.0`` minimum pin. (:pull:`4885`) -#. `@trexfeathers`_ updated the install process to work with setuptools `>=v64`, - making `v64` the minimum compatible version. (:pull:`4903`) +#. `@trexfeathers`_ updated the install process to work with setuptools + ``>=v64``, making ``v64`` the minimum compatible version. (:pull:`4903`) #. `@stephenworsley`_ and `@trexfeathers`_ introduced the ``shapely < 1.8.3`` maximum pin, avoiding a bug caused by its interaction with cartopy. @@ -248,8 +248,8 @@ This document explains the changes made to Iris for this release numpydoc strings and fixed some API documentation rendering. See :ref:`docstrings`. (:issue:`4657`, :pull:`4689`) -#. `@trexfeathers`_ added a page with examples of converting various mesh - formats into the Iris Mesh Data Model. (:pull:`4739`) +#. `@trexfeathers`_ and `@lbdreyer`_ added a page with examples of converting + various mesh formats into the Iris Mesh Data Model. (:pull:`4739`) #. `@rcomer`_ updated the "Load a Time Series of Data From the NEMO Model" gallery example. (:pull:`4741`) @@ -326,4 +326,4 @@ This document explains the changes made to Iris for this release .. _nose: https://nose.readthedocs.io .. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html .. _pytest: https://docs.pytest.org -.. _setuptools-scm: https://github.com/pypa/setuptools_scm \ No newline at end of file +.. _setuptools-scm: https://github.com/pypa/setuptools_scm From aa2dca38d3d462a5ca9133aa313c3f5285564bdf Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Thu, 18 Aug 2022 14:32:55 +0100 Subject: [PATCH 201/319] Update whatsnew for v3.3.0rc0 (#4921) * update whatsnew * fix index * remove template * address review comments --- docs/src/whatsnew/{latest.rst => 3.3.rst} | 17 +++- docs/src/whatsnew/index.rst | 4 +- docs/src/whatsnew/latest.rst.template | 112 ---------------------- 3 files changed, 15 insertions(+), 118 deletions(-) rename docs/src/whatsnew/{latest.rst => 3.3.rst} (95%) delete mode 100644 docs/src/whatsnew/latest.rst.template diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/3.3.rst similarity index 95% rename from docs/src/whatsnew/latest.rst rename to docs/src/whatsnew/3.3.rst index e43f80034a..7a8e1bc620 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/3.3.rst @@ -1,13 +1,13 @@ .. include:: ../common_links.inc -|iris_version| |build_date| [unreleased] -**************************************** +v3.3 (18 Aug 2022) [release candidate] +************************************** This document explains the changes made to Iris for this release (:doc:`View all changes `.) -.. dropdown:: :opticon:`report` |iris_version| Release Highlights +.. dropdown:: :opticon:`report` v3.3.0 Release Highlights :container: + shadow :title: text-primary text-center font-weight-bold :body: bg-light @@ -16,7 +16,16 @@ This document explains the changes made to Iris for this release The highlights for this minor release of Iris include: - * N/A + * We've added support for datums, loading them from NetCDF when the + :obj:`iris.FUTURE.datum_support` flag is set. + * We've greatly improved the speed of linear interpolation. + * We've added the function :func:`iris.pandas.as_cubes` for richer + conversion from Pandas. + * We've improved the functionality of :func:`iris.util.mask_cube`. + * We've improved the functionality and performance of the + :obj:`iris.analysis.PERCENTILE` aggregator. + * We've completed implementation of our :ref:`contributing.benchmarks` + infrastructure. And finally, get in touch with us on :issue:`GitHub` if you have any issues or feature requests for improving Iris. Enjoy! diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index 78c0134463..5d8734262f 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -5,13 +5,13 @@ What's New in Iris ------------------ -.. include:: latest.rst +.. include:: 3.3.rst .. toctree:: :maxdepth: 1 :hidden: - latest.rst + 3.3.rst 3.2.rst 3.1.rst 3.0.rst diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template deleted file mode 100644 index 1b36d3f0b0..0000000000 --- a/docs/src/whatsnew/latest.rst.template +++ /dev/null @@ -1,112 +0,0 @@ -.. include:: ../common_links.inc - -|iris_version| |build_date| [unreleased] -**************************************** - -This document explains the changes made to Iris for this release -(:doc:`View all changes `.) - - -.. dropdown:: :opticon:`report` |iris_version| Release Highlights - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - :open: - - The highlights for this major/minor release of Iris include: - - * N/A - - And finally, get in touch with us on :issue:`GitHub` if you have - any issues or feature requests for improving Iris. Enjoy! - - -NOTE: section below is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'dev.rst') - -v3.X.X (DD MMM YYYY) -==================== - -.. dropdown:: :opticon:`alert` v3.X.X Patches - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - - The patches in this release of Iris include: - - #. N/A - -NOTE: section above is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'dev.rst') - - - -📢 Announcements -================ - -#. N/A - - -✨ Features -=========== - -#. N/A - - -🐛 Bugs Fixed -============= - -#. N/A - - -💣 Incompatible Changes -======================= - -#. N/A - - -🚀 Performance Enhancements -=========================== - -#. N/A - - -🔥 Deprecations -=============== - -#. N/A - - -🔗 Dependencies -=============== - -#. N/A - - -📚 Documentation -================ - -#. N/A - - -💼 Internal -=========== - -#. N/A - - -.. comment - Whatsnew author names (@github name) in alphabetical order. Note that, - core dev names are automatically included by the common_links.inc: - - - - -.. comment - Whatsnew resources in alphabetical order: - - From bb9e6521d822fed2cf12b5c2312f81ae0920e2c7 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 09:56:02 +0100 Subject: [PATCH 202/319] Updated environment lockfiles (#4923) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 8 ++++---- requirements/ci/nox.lock/py38-linux-64.lock | 8 ++++---- requirements/ci/nox.lock/py39-linux-64.lock | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index b174b21781..9cbf42171d 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -200,7 +200,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.ta https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py310hff52083_0.tar.bz2#fe4e6907626e7d9a6de503c88f5f7b27 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.1.0-py310hff52083_0.tar.bz2#7e961bc719caa7d24d085eda64c62dc3 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 @@ -208,8 +208,8 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py310hff52083 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py310h5764c6d_0.tar.bz2#71d8a7387c733792ee9922357a44232d +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py310h5764c6d_0.tar.bz2#75969f26ba850c17f6ceab6434aa1b0d https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 @@ -254,5 +254,5 @@ https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 7f45d17b06..00798a95ec 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -199,7 +199,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py38h578d9bd_0.tar.bz2#2a01f71dd562cc9fbf2ab93c813b6e8d +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.1.0-py38h578d9bd_0.tar.bz2#cad9b9812978f3c05dfaaf7e9bfbb98f https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 @@ -207,8 +207,8 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py38h578d9bd_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py38h0a891b7_0.tar.bz2#3fe3830d29d4fe9962eb6afe73f9a878 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py38h0a891b7_0.tar.bz2#8e43961af78ba54e8a72b0d8ce80588a https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 @@ -253,5 +253,5 @@ https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 51a0fb2dbd..308d40b4ab 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -200,7 +200,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py39hf3d152e_0.tar.bz2#9065ec0b391ae4d56ff832c3c9d103c2 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.1.0-py39hf3d152e_0.tar.bz2#ad0dfc5178725b4c11ed7e54055a366d https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f @@ -208,8 +208,8 @@ https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py39hf3d152e_ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py39hb9d737c_0.tar.bz2#f883146012e119b78120fc23eeba297c +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py39hb9d737c_0.tar.bz2#a93c3de7835e68d42f9203886b372a8b https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 @@ -254,5 +254,5 @@ https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a From 85bf9fb32dc132edf12dfa18d56c41e58daabe7d Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Mon, 22 Aug 2022 13:59:59 +0100 Subject: [PATCH 203/319] Update whatsnew (#4924) * reset whatsnew * remove bugfix section * address review comments --- docs/src/whatsnew/index.rst | 3 +- docs/src/whatsnew/latest.rst | 89 ++++++++++++++++++++ docs/src/whatsnew/latest.rst.template | 112 ++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 docs/src/whatsnew/latest.rst create mode 100644 docs/src/whatsnew/latest.rst.template diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index 5d8734262f..8cff21f32f 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -5,12 +5,13 @@ What's New in Iris ------------------ -.. include:: 3.3.rst +.. include:: latest.rst .. toctree:: :maxdepth: 1 :hidden: + latest.rst 3.3.rst 3.2.rst 3.1.rst diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst new file mode 100644 index 0000000000..215e7d34f0 --- /dev/null +++ b/docs/src/whatsnew/latest.rst @@ -0,0 +1,89 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`report` |iris_version| Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this major/minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. N/A + + +🐛 Bugs Fixed +============= + +#. N/A + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. N/A + + +📚 Documentation +================ + +#. N/A + + +💼 Internal +=========== + +#. N/A + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: + + diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template new file mode 100644 index 0000000000..661ee47f50 --- /dev/null +++ b/docs/src/whatsnew/latest.rst.template @@ -0,0 +1,112 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`report` |iris_version| Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this major/minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +NOTE: section below is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + +v3.X.X (DD MMM YYYY) +==================== + +.. dropdown:: :opticon:`alert` v3.X.X Patches + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + + The patches in this release of Iris include: + + #. N/A + +NOTE: section above is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. N/A + + +🐛 Bugs Fixed +============= + +#. N/A + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. N/A + + +📚 Documentation +================ + +#. N/A + + +💼 Internal +=========== + +#. N/A + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: + + From 541857575f6bc66b8afd787c98cb60f0fc11aab7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:34:57 +0100 Subject: [PATCH 204/319] Bump peter-evans/create-pull-request from 4.0.4 to 4.1.1 (#4922) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.0.4 to 4.1.1. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/923ad837f191474af6b1721408744feb989a4c27...18f90432bedd2afd6a825469ffd38aa24712a91d) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 72b330c815..4e717be8a1 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -89,7 +89,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27 + uses: peter-evans/create-pull-request@18f90432bedd2afd6a825469ffd38aa24712a91d with: token: ${{ steps.generate-token.outputs.token }} commit-message: Updated environment lockfiles From 4b8b14e77aaea5828635f0b8aee8617cf2d6e965 Mon Sep 17 00:00:00 2001 From: Elias <110238618+ESadek-MO@users.noreply.github.com> Date: Mon, 22 Aug 2022 16:51:31 +0100 Subject: [PATCH 205/319] Tilde's and wildcard recognition within iris.save. (#4913) * Draft: Tilde's and wildcard can now be read by iris.save. Modifications and tests provisionally done, docstrings still need editing, with examples and doctests needing being added/editing. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Docstrings updated for expand_filespecs and save * docstrings for save and expand_filespecs updated to numpy formatting * Reviewed: Added expansion and relative path tests to expand_filespecs. Ensured list indexing returns single item lists. Minor comment changes. * Updated latest What's New, and added ESadek-MO to common links * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed isort conflicts Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/common_links.inc | 1 + docs/src/whatsnew/latest.rst | 6 +- lib/iris/io/__init__.py | 153 ++++++++++-------- .../tests/unit/io/test_expand_filespecs.py | 24 +++ lib/iris/tests/unit/io/test_save.py | 6 + 5 files changed, 117 insertions(+), 73 deletions(-) diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index 7ae2463ca9..ec7e1efd6d 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -53,6 +53,7 @@ .. _@cpelley: https://github.com/cpelley .. _@djkirkham: https://github.com/djkirkham .. _@DPeterK: https://github.com/DPeterK +.. _@ESadek-MO: https://github.com/ESadek-MO .. _@esc24: https://github.com/esc24 .. _@jamesp: https://github.com/jamesp .. _@jonseddon: https://github.com/jonseddon diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 215e7d34f0..b94cd11517 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -25,13 +25,15 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. N/A +#. Welcome to `@ESadek-MO`_ who made their first contribution to Iris 🎉 ✨ Features =========== -#. N/A +#. `@ESadek-MO`_ edited :func:`~iris.io.expand_filespecs` to allow expansion of + non-existing paths, and added expansion functionality to :func:`~iris.io.save`. + (:issue:`4772`, :pull:`4913`) 🐛 Bugs Fixed diff --git a/lib/iris/io/__init__.py b/lib/iris/io/__init__.py index 8d5a2e05d2..4659f70ae3 100644 --- a/lib/iris/io/__init__.py +++ b/lib/iris/io/__init__.py @@ -131,20 +131,26 @@ def decode_uri(uri, default="file"): return scheme, part -def expand_filespecs(file_specs): +def expand_filespecs(file_specs, files_expected=True): """ Find all matching file paths from a list of file-specs. - Args: - - * file_specs (iterable of string): - File paths which may contain '~' elements or wildcards. - - Returns: - A well-ordered list of matching absolute file paths. - If any of the file-specs match no existing files, an - exception is raised. - + Parameters + ---------- + file_specs : iterable of str + File paths which may contain ``~`` elements or wildcards. + files_expected : bool, default=True + Whether file is expected to exist (i.e. for load). + + Returns + ------- + list of str + if files_expected is ``True``: + A well-ordered list of matching absolute file paths. + If any of the file-specs match no existing files, an + exception is raised. + if files_expected is ``False``: + A list of expanded file paths. """ # Remove any hostname component - currently unused filenames = [ @@ -154,26 +160,30 @@ def expand_filespecs(file_specs): for fn in file_specs ] - # Try to expand all filenames as globs - glob_expanded = OrderedDict( - [[fn, sorted(glob.glob(fn))] for fn in filenames] - ) - - # If any of the specs expanded to an empty list then raise an error - all_expanded = glob_expanded.values() - - if not all(all_expanded): - msg = "One or more of the files specified did not exist:" - for pattern, expanded in glob_expanded.items(): - if expanded: - msg += '\n - "{}" matched {} file(s)'.format( - pattern, len(expanded) - ) - else: - msg += '\n * "{}" didn\'t match any files'.format(pattern) - raise IOError(msg) + if files_expected: + # Try to expand all filenames as globs + glob_expanded = OrderedDict( + [[fn, sorted(glob.glob(fn))] for fn in filenames] + ) - return [fname for fnames in all_expanded for fname in fnames] + # If any of the specs expanded to an empty list then raise an error + all_expanded = glob_expanded.values() + if not all(all_expanded): + msg = "One or more of the files specified did not exist:" + for pattern, expanded in glob_expanded.items(): + if expanded: + msg += '\n - "{}" matched {} file(s)'.format( + pattern, len(expanded) + ) + else: + msg += '\n * "{}" didn\'t match any files'.format( + pattern + ) + raise IOError(msg) + result = [fname for fnames in all_expanded for fname in fnames] + else: + result = filenames + return result def load_files(filenames, callback, constraints=None): @@ -356,65 +366,64 @@ def save(source, target, saver=None, **kwargs): A custom saver can be provided to the function to write to a different file format. - Args: - - * source: - :class:`iris.cube.Cube`, :class:`iris.cube.CubeList` or - sequence of cubes. - * target: - A filename (or writeable, depending on file format). + Parameters + ---------- + source : :class:`iris.cube.Cube` or :class:`iris.cube.CubeList` + target : str or pathlib.PurePath or io.TextIOWrapper When given a filename or file, Iris can determine the - file format. Filename can be given as a string or - :class:`pathlib.PurePath`. - - Kwargs: - - * saver: - Optional. Specifies the file format to save. + file format. + saver : str or function, optional + Specifies the file format to save. If omitted, Iris will attempt to determine the format. - If a string, this is the recognised filename extension (where the actual filename may not have it). + Otherwise the value is a saver function, of the form: ``my_saver(cube, target)`` plus any custom keywords. It is assumed that a saver will accept an ``append`` keyword - if it's file format can handle multiple cubes. See also + if its file format can handle multiple cubes. See also :func:`iris.io.add_saver`. + **kwargs : dict, optional + All other keywords are passed through to the saver function; see the + relevant saver documentation for more information on keyword arguments. - All other keywords are passed through to the saver function; see the - relevant saver documentation for more information on keyword arguments. - - Examples:: + Warnings + -------- + Saving a cube whose data has been loaded lazily + (if `cube.has_lazy_data()` returns `True`) to the same file it expects + to load data from will cause both the data in-memory and the data on + disk to be lost. - # Save a cube to PP - iris.save(my_cube, "myfile.pp") + .. code-block:: python - # Save a cube list to a PP file, appending to the contents of the file - # if it already exists - iris.save(my_cube_list, "myfile.pp", append=True) + cube = iris.load_cube("somefile.nc") + # The next line causes data loss in 'somefile.nc' and the cube. + iris.save(cube, "somefile.nc") - # Save a cube to netCDF, defaults to NETCDF4 file format - iris.save(my_cube, "myfile.nc") + In general, overwriting a file which is the source for any lazily loaded + data can result in corruption. Users should proceed with caution when + attempting to overwrite an existing file. - # Save a cube list to netCDF, using the NETCDF3_CLASSIC storage option - iris.save(my_cube_list, "myfile.nc", netcdf_format="NETCDF3_CLASSIC") + Examples + -------- + >>> # Setting up + >>> import iris + >>> my_cube = iris.load_cube(iris.sample_data_path('air_temp.pp')) + >>> my_cube_list = iris.load(iris.sample_data_path('space_weather.nc')) - .. warning:: + >>> # Save a cube to PP + >>> iris.save(my_cube, "myfile.pp") - Saving a cube whose data has been loaded lazily - (if `cube.has_lazy_data()` returns `True`) to the same file it expects - to load data from will cause both the data in-memory and the data on - disk to be lost. + >>> # Save a cube list to a PP file, appending to the contents of the file + >>> # if it already exists + >>> iris.save(my_cube_list, "myfile.pp", append=True) - .. code-block:: python + >>> # Save a cube to netCDF, defaults to NETCDF4 file format + >>> iris.save(my_cube, "myfile.nc") - cube = iris.load_cube("somefile.nc") - # The next line causes data loss in 'somefile.nc' and the cube. - iris.save(cube, "somefile.nc") + >>> # Save a cube list to netCDF, using the NETCDF3_CLASSIC storage option + >>> iris.save(my_cube_list, "myfile.nc", netcdf_format="NETCDF3_CLASSIC") - In general, overwriting a file which is the source for any lazily loaded - data can result in corruption. Users should proceed with caution when - attempting to overwrite an existing file. """ from iris.cube import Cube, CubeList @@ -423,6 +432,8 @@ def save(source, target, saver=None, **kwargs): if isinstance(target, pathlib.PurePath): target = str(target) if isinstance(target, str) and saver is None: + # Converts tilde or wildcards to absolute path + (target,) = expand_filespecs([str(target)], False) saver = find_saver(target) elif hasattr(target, "name") and saver is None: saver = find_saver(target.name) diff --git a/lib/iris/tests/unit/io/test_expand_filespecs.py b/lib/iris/tests/unit/io/test_expand_filespecs.py index 0299a415b4..8720478153 100644 --- a/lib/iris/tests/unit/io/test_expand_filespecs.py +++ b/lib/iris/tests/unit/io/test_expand_filespecs.py @@ -10,6 +10,7 @@ import iris.tests as tests # isort:skip import os +from pathlib import Path import shutil import tempfile import textwrap @@ -96,6 +97,29 @@ def test_files_and_none(self): self.assertMultiLineEqual(str(err.exception), expected) + def test_false_bool_absolute(self): + tempdir = self.tmpdir + msg = os.path.join(tempdir, "no_exist.txt") + (result,) = iio.expand_filespecs([msg], False) + self.assertEqual(result, msg) + + def test_false_bool_home(self): + # ensure that not only does files_expected not error, + # but that the path is still expanded from a ~ + msg = str(Path().home() / "no_exist.txt") + (result,) = iio.expand_filespecs(["~/no_exist.txt"], False) + self.assertEqual(result, msg) + + def test_false_bool_relative(self): + cwd = os.getcwd() + try: + os.chdir(self.tmpdir) + item_out = iio.expand_filespecs(["no_exist.txt"], False) + item_in = [os.path.join(self.tmpdir, "no_exist.txt")] + self.assertEqual(item_out, item_in) + finally: + os.chdir(cwd) + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/io/test_save.py b/lib/iris/tests/unit/io/test_save.py index b92e26f2d1..623cf417f2 100755 --- a/lib/iris/tests/unit/io/test_save.py +++ b/lib/iris/tests/unit/io/test_save.py @@ -26,6 +26,12 @@ def test_pathlib_save(self): "iris.io.find_saver", return_value=(lambda *args, **kwargs: None) ) + def replace_expand(file_specs, files_expected=True): + return file_specs + + # does not expand filepaths due to patch + self.patch("iris.io.expand_filespecs", replace_expand) + test_variants = [ ("string", "string"), (Path("string/string"), "string/string"), From 9ca6ce4c1a28ee681702d885c8e05ac1891df4e9 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:57:39 +0100 Subject: [PATCH 206/319] Unpin shapely (#4925) * unpin shapely * update whatsnew --- docs/src/whatsnew/3.3.rst | 4 +- requirements/ci/nox.lock/py310-linux-64.lock | 102 +++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 22 ++-- requirements/ci/nox.lock/py39-linux-64.lock | 22 ++-- requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.cfg | 2 +- 8 files changed, 79 insertions(+), 79 deletions(-) diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index 7a8e1bc620..4459e92784 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -232,8 +232,8 @@ This document explains the changes made to Iris for this release #. `@trexfeathers`_ updated the install process to work with setuptools ``>=v64``, making ``v64`` the minimum compatible version. (:pull:`4903`) -#. `@stephenworsley`_ and `@trexfeathers`_ introduced the ``shapely < 1.8.3`` - maximum pin, avoiding a bug caused by its interaction with cartopy. +#. `@stephenworsley`_ and `@trexfeathers`_ introduced the ``shapely !=1.8.3`` + pin, avoiding a bug caused by its interaction with cartopy. (:pull:`4911`, :pull:`4917`) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index b174b21781..b29cac464f 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 47e8f4325818ea52101543c35eb782f69a9c59cfd5847e679670a43a8208a383 +# input_hash: e79be0c1a5de550c1c2e192396e0e5cfda2ba23a154e1c4531d893da416f8cee @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -42,7 +42,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_1.tar.bz2#45127206f58f9989f96041c6e87a534a +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -102,58 +102,48 @@ https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openb https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 +https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f -https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 -https://conda.anaconda.org/conda-forge/linux-64/python-3.10.5-h582c2e5_0_cpython.tar.bz2#ccbed83043b9b7b5693164591317f327 -https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f +https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc @@ -174,18 +164,24 @@ https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc +https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py310h53a5b5f_0.tar.bz2#8b3cfad14508018915e88612f5a963cd @@ -194,30 +190,31 @@ https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#a https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310hbd86126_2.tar.bz2#443272de4234f6df4a78f50105edc741 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.tar.bz2#378f2260e871f3ea46c6fa58d9f05277 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py310hff52083_0.tar.bz2#fe4e6907626e7d9a6de503c88f5f7b27 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py310hff52083_0.tar.bz2#fe1a6180b086ddfa214627f00d1c4b58 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py310hff52083_0.tar.bz2#87e141dd6d0304b879d903e63560f73b +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py310hff52083_1.tar.bz2#a91c9f0499e0f0f5912098c3462014b9 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py310h5764c6d_0.tar.bz2#71d8a7387c733792ee9922357a44232d -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py310h5764c6d_0.tar.bz2#75969f26ba850c17f6ceab6434aa1b0d +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 @@ -225,34 +222,37 @@ https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310h https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py310hdfbd76f_0.tar.bz2#e5d21b0cb4161a40221786f2f05b3903 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py310h5e49deb_3.tar.bz2#5305d80a31c7db98765b52ea95521ff6 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py310h5e49deb_0.tar.bz2#2f2c225d04e99ff99d6d3a86692ce968 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py310h5701ce4_1.tar.bz2#36adf7d1b16bee40fcad8bfce54baa85 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_1.tar.bz2#a06eff87c5bbd8ece667e155854fec2b -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_1.tar.bz2#7fa212f8ef62e0ec36f20448267ce95a +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_1.tar.bz2#7fa212f8ef62e0ec36f20448267ce95a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 7f45d17b06..1508ea0f55 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 713876566ea2ac7e2e527b6083261e07b09f96f5113611bd51a12599aa2efe1e +# input_hash: 05da38480edd3a8765d02d305d525d5b0ffd45d84de4a2f81e21421d61cad72c @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -41,7 +41,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_1.tar.bz2#45127206f58f9989f96041c6e87a534a +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -131,7 +131,7 @@ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 @@ -179,7 +179,7 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 @@ -199,16 +199,16 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py38h578d9bd_0.tar.bz2#2a01f71dd562cc9fbf2ab93c813b6e8d +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py38h578d9bd_0.tar.bz2#1daef6b2dad47429ddf747ce04f4fd6e https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py38h578d9bd_0.tar.bz2#b830ca0aaf8992855af9001f0c2d7b2e +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py38h578d9bd_1.tar.bz2#30765568a158c9457d577cc83f0e8307 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py38h0a891b7_0.tar.bz2#3fe3830d29d4fe9962eb6afe73f9a878 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py38h0a891b7_0.tar.bz2#8e43961af78ba54e8a72b0d8ce80588a https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 @@ -224,7 +224,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h7 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py38hea3f02b_0.tar.bz2#d19e23bb56b31d2504a0ff4d46b7aabc https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py38h3b45516_3.tar.bz2#b2415a314858f4232aab437bc27803fc +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py38h3b45516_0.tar.bz2#d8621497bcc7b369ef9cce25d5a58aeb https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 @@ -242,7 +242,7 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.t https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_1.tar.bz2#9e07b83f7d5e70bb48d13938ff93faf9 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae @@ -253,5 +253,5 @@ https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 51a0fb2dbd..ef20dfd33b 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 1306a5e6925617b42f229cf1688af21e43e70dc6fd9a54e44dbec125671aeba8 +# input_hash: babb8284ac9609a6063fb9a97f69a471814cdad98b7e619ba1c902891e884ee2 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -42,7 +42,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_1.tar.bz2#45127206f58f9989f96041c6e87a534a +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 @@ -132,7 +132,7 @@ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c -https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.0-pyhd8ed1ab_0.tar.bz2#abc0453b6e7bfbb87d275d58e333fc98 +https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 @@ -180,7 +180,7 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_2.tar.bz2#fea5dea40592ea943aa56f4935308ee4 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb @@ -200,16 +200,16 @@ https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.0.2-py39hf3d152e_0.tar.bz2#9065ec0b391ae4d56ff832c3c9d103c2 +https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py39hf3d152e_0.tar.bz2#1b778cca1524a5cc4240f277d97dae99 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py39hf3d152e_0.tar.bz2#ce1cedc52c7acb8c691b6204a57d5e80 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py39hf3d152e_1.tar.bz2#baa79a28aa08de404d9deae634b91e03 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.0-pyhd8ed1ab_0.tar.bz2#b097a86c177b459f6c9a68a077cade0e -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.35.0-py39hb9d737c_0.tar.bz2#f883146012e119b78120fc23eeba297c +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py39hb9d737c_0.tar.bz2#a93c3de7835e68d42f9203886b372a8b https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 @@ -225,7 +225,7 @@ https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py39h8ba3f38_0.tar.bz2#b098a256777cb9e2605451f183c78768 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.2-py39h68ae834_3.tar.bz2#2800d4dabd586cf203d8f1271a6c9e9d +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py39h68ae834_0.tar.bz2#e871ee7de5bfa95095256e95e30be2a6 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 @@ -243,7 +243,7 @@ https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.t https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_1.tar.bz2#7264fa97520021d1a01f5be9741493c2 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.0-h5abf519_0.tar.bz2#6b9904a1381a5c598c5fda0cd1adb4b2 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 @@ -254,5 +254,5 @@ https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c -https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.0-pyhd8ed1ab_0.tar.bz2#9fcb2988f0d82b9af2131ef4b4567240 +https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 81c33c494b..16b8afb67a 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -21,7 +21,7 @@ dependencies: - python-xxhash - pyproj - scipy - - shapely < 1.8.3 + - shapely !=1.8.3 # Optional dependencies. - esmpy >=7.0 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 6353cfa3a5..faecbd63ce 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -21,7 +21,7 @@ dependencies: - python-xxhash - pyproj - scipy - - shapely < 1.8.3 + - shapely !=1.8.3 # Optional dependencies. - esmpy >=7.0 diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 14a93f00fd..f26804e92b 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -21,7 +21,7 @@ dependencies: - python-xxhash - pyproj - scipy - - shapely < 1.8.3 + - shapely !=1.8.3 # Optional dependencies. - esmpy >=7.0 diff --git a/setup.cfg b/setup.cfg index 25b0ecca5a..672cfc24d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,7 +53,7 @@ install_requires = netcdf4 numpy>=1.19 scipy - shapely<1.8.3 + shapely!=1.8.3 xxhash packages = find_namespace: package_dir = From 21e1cbfc9fbbca8d28c88fb90d312ef450b06c46 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 24 Aug 2022 12:41:42 +0100 Subject: [PATCH 207/319] SUM, COUNT, PROPORTION: don't use dask on numpy arrays (#4905) * COUNT * SUM * tidy up * increase dask min pin * fix returned weights when masked * typo * docstrings * whatsnew * include reviewer in whatsnew --- docs/src/whatsnew/latest.rst | 16 ++++--- lib/iris/analysis/__init__.py | 53 +++++++++++++++--------- lib/iris/tests/unit/analysis/test_SUM.py | 22 ++++++++++ requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.cfg | 2 +- 7 files changed, 70 insertions(+), 29 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index b94cd11517..1790f51daa 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -32,14 +32,15 @@ This document explains the changes made to Iris for this release =========== #. `@ESadek-MO`_ edited :func:`~iris.io.expand_filespecs` to allow expansion of - non-existing paths, and added expansion functionality to :func:`~iris.io.save`. - (:issue:`4772`, :pull:`4913`) + non-existing paths, and added expansion functionality to :func:`~iris.io.save`. + (:issue:`4772`, :pull:`4913`) 🐛 Bugs Fixed ============= -#. N/A +#. `@rcomer`_ and `@pp-mo`_ (reviewer) factored masking into the returned + sum-of-weights calculation from :obj:`~iris.analysis.SUM`. (:pull:`4905`) 💣 Incompatible Changes @@ -51,7 +52,9 @@ This document explains the changes made to Iris for this release 🚀 Performance Enhancements =========================== -#. N/A +#. `@rcomer`_ and `@pp-mo`_ (reviewer) increased aggregation speed for + :obj:`~iris.analysis.SUM`, :obj:`~iris.analysis.COUNT` and + :obj:`~iris.analysis.PROPORTION` on real data. (:pull:`4905`) 🔥 Deprecations @@ -63,7 +66,8 @@ This document explains the changes made to Iris for this release 🔗 Dependencies =============== -#. N/A +#. `@rcomer`_ introduced the ``dask >=2.26`` minimum pin, so that Iris can benefit + from Dask's support for `NEP13`_ and `NEP18`_. (:pull:`4905`) 📚 Documentation @@ -89,3 +93,5 @@ This document explains the changes made to Iris for this release Whatsnew resources in alphabetical order: +.. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html +.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html \ No newline at end of file diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 9d8392cd95..11810f2901 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -1499,18 +1499,21 @@ def _weighted_percentile( return result -@_build_dask_mdtol_function -def _lazy_count(array, **kwargs): - array = iris._lazy_data.as_lazy_data(array) +def _count(array, **kwargs): + """ + Counts the number of points along the axis that satisfy the condition + specified by ``function``. Uses Dask's support for NEP13/18 to work as + either a lazy or a real function. + + """ func = kwargs.pop("function", None) if not callable(func): emsg = "function must be a callable. Got {}." raise TypeError(emsg.format(type(func))) - return da.sum(func(array), **kwargs) + return np.sum(func(array), **kwargs) def _proportion(array, function, axis, **kwargs): - count = iris._lazy_data.non_lazy(_lazy_count) # if the incoming array is masked use that to count the total number of # values if ma.isMaskedArray(array): @@ -1521,7 +1524,7 @@ def _proportion(array, function, axis, **kwargs): # case pass the array shape instead of the mask: total_non_masked = array.shape[axis] else: - total_non_masked = count( + total_non_masked = _count( array.mask, axis=axis, function=np.logical_not, **kwargs ) total_non_masked = ma.masked_equal(total_non_masked, 0) @@ -1534,7 +1537,7 @@ def _proportion(array, function, axis, **kwargs): # a dtype for its data that is different to the dtype of the fill-value, # which can cause issues outside this function. # Reference - tests/unit/analyis/test_PROPORTION.py Test_masked.test_ma - numerator = count(array, axis=axis, function=function, **kwargs) + numerator = _count(array, axis=axis, function=function, **kwargs) result = ma.asarray(numerator / total_non_masked) return result @@ -1604,23 +1607,33 @@ def _lazy_rms(array, axis, **kwargs): return da.sqrt(da.mean(array**2, axis=axis, **kwargs)) -@_build_dask_mdtol_function -def _lazy_sum(array, **kwargs): - array = iris._lazy_data.as_lazy_data(array) - # weighted or scaled sum +def _sum(array, **kwargs): + """ + Weighted or scaled sum. Uses Dask's support for NEP13/18 to work as either + a lazy or a real function. + + """ axis_in = kwargs.get("axis", None) weights_in = kwargs.pop("weights", None) returned_in = kwargs.pop("returned", False) if weights_in is not None: - wsum = da.sum(weights_in * array, **kwargs) + wsum = np.sum(weights_in * array, **kwargs) else: - wsum = da.sum(array, **kwargs) + wsum = np.sum(array, **kwargs) if returned_in: + al = da if iris._lazy_data.is_lazy_data(array) else np if weights_in is None: - weights = iris._lazy_data.as_lazy_data(np.ones_like(array)) + weights = al.ones_like(array) + if al is da: + # Dask version of ones_like does not preserve masks. See dask#9301. + weights = da.ma.masked_array( + weights, da.ma.getmaskarray(array) + ) else: - weights = weights_in - rvalue = (wsum, da.sum(weights, axis=axis_in)) + weights = al.ma.masked_array( + weights_in, mask=al.ma.getmaskarray(array) + ) + rvalue = (wsum, np.sum(weights, axis=axis_in)) else: rvalue = wsum return rvalue @@ -1740,9 +1753,9 @@ def interp_order(length): # COUNT = Aggregator( "count", - iris._lazy_data.non_lazy(_lazy_count), + _count, units_func=lambda units: 1, - lazy_func=_lazy_count, + lazy_func=_build_dask_mdtol_function(_count), ) """ An :class:`~iris.analysis.Aggregator` instance that counts the number @@ -2114,8 +2127,8 @@ def interp_order(length): SUM = WeightedAggregator( "sum", - iris._lazy_data.non_lazy(_lazy_sum), - lazy_func=_build_dask_mdtol_function(_lazy_sum), + _sum, + lazy_func=_build_dask_mdtol_function(_sum), ) """ An :class:`~iris.analysis.Aggregator` instance that calculates diff --git a/lib/iris/tests/unit/analysis/test_SUM.py b/lib/iris/tests/unit/analysis/test_SUM.py index dd2dcf9f9c..64699b442f 100644 --- a/lib/iris/tests/unit/analysis/test_SUM.py +++ b/lib/iris/tests/unit/analysis/test_SUM.py @@ -9,6 +9,7 @@ # importing anything else. import iris.tests as tests # isort:skip +import dask.array as da import numpy as np import numpy.ma as ma @@ -91,6 +92,16 @@ def test_weights_and_returned(self): self.assertArrayEqual(data, [14, 9, 11, 13, 15]) self.assertArrayEqual(weights, [4, 2, 2, 2, 2]) + def test_masked_weights_and_returned(self): + array = ma.array( + self.cube_2d.data, mask=[[0, 0, 1, 0, 0], [0, 0, 0, 1, 0]] + ) + data, weights = SUM.aggregate( + array, axis=0, weights=self.weights, returned=True + ) + self.assertArrayEqual(data, [14, 9, 8, 4, 15]) + self.assertArrayEqual(weights, [4, 2, 1, 1, 2]) + class Test_lazy_weights_and_returned(tests.IrisTest): def setUp(self): @@ -128,6 +139,17 @@ def test_weights_and_returned(self): self.assertArrayEqual(lazy_data.compute(), [14, 9, 11, 13, 15]) self.assertArrayEqual(weights, [4, 2, 2, 2, 2]) + def test_masked_weights_and_returned(self): + array = da.ma.masked_array( + self.cube_2d.lazy_data(), mask=[[0, 0, 1, 0, 0], [0, 0, 0, 1, 0]] + ) + lazy_data, weights = SUM.lazy_aggregate( + array, axis=0, weights=self.weights, returned=True + ) + self.assertTrue(is_lazy_data(lazy_data)) + self.assertArrayEqual(lazy_data.compute(), [14, 9, 8, 4, 15]) + self.assertArrayEqual(weights, [4, 2, 1, 1, 2]) + class Test_aggregate_shape(tests.IrisTest): def test(self): diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 81c33c494b..795ecdc1ce 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -14,7 +14,7 @@ dependencies: - cartopy >=0.20 - cf-units >=3.1 - cftime >=1.5 - - dask-core >=2 + - dask-core >=2.26 - matplotlib - netcdf4 - numpy >=1.19 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 6353cfa3a5..da5e00cfac 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -14,7 +14,7 @@ dependencies: - cartopy >=0.20 - cf-units >=3.1 - cftime >=1.5 - - dask-core >=2 + - dask-core >=2.26 - matplotlib - netcdf4 - numpy >=1.19 diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 14a93f00fd..c5600c06b4 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -14,7 +14,7 @@ dependencies: - cartopy >=0.20 - cf-units >=3.1 - cftime >=1.5 - - dask-core >=2 + - dask-core >=2.26 - matplotlib - netcdf4 - numpy >=1.19 diff --git a/setup.cfg b/setup.cfg index 25b0ecca5a..142d2114e6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -48,7 +48,7 @@ install_requires = cartopy>=0.20 cf-units>=3.1 cftime>=1.5.0 - dask[array]>=2 + dask[array]>=2.26 matplotlib netcdf4 numpy>=1.19 From 78030525e3425c7e02470a80a6bf28e275e2ed9f Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 24 Aug 2022 13:07:08 +0100 Subject: [PATCH 208/319] Remove `setUpClass` from Iris tests. (#4927) * remove IrisTest.setUpClass * whatsnew --- docs/src/whatsnew/latest.rst | 3 ++- lib/iris/tests/__init__.py | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 1790f51daa..c388c5fb7b 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -79,7 +79,8 @@ This document explains the changes made to Iris for this release 💼 Internal =========== -#. N/A +#. `@rcomer`_ removed the obsolete ``setUpClass`` method from Iris testing. + (:pull:`4927`) .. comment diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py index a556cb6231..4840de8cdb 100644 --- a/lib/iris/tests/__init__.py +++ b/lib/iris/tests/__init__.py @@ -213,11 +213,6 @@ class IrisTest_nometa(unittest.TestCase): _assertion_counts = collections.defaultdict(int) - @classmethod - def setUpClass(cls): - # Ensure that the CF profile if turned-off for testing. - iris.site_configuration["cf_profile"] = None - def _assert_str_same( self, reference_str, From 4a7304f54e3928f4d246a0cc09f3be2eacd26a3d Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 24 Aug 2022 17:27:33 +0100 Subject: [PATCH 209/319] Better handle `setuptools_scm` versioning (#4926) * Use setuptools_scm release-branch-semver version_scheme. * Properly handle all possible scm-rtd combinations. * What's new entry. * Use git stash to protect setuptools_scm from RTD. * Better version badges. * Better use of git stash. * Substitute commit SHAs in place of versions where appropriate. * Make rtd_version safe for use in shields.io badges. * Make rtd_version safe for use in shields.io badges. --- .readthedocs.yml | 6 ++++++ .../custom_sidebar_logo_version.html | 18 +++++++++++------ docs/src/conf.py | 20 ++++++++++++++++--- docs/src/whatsnew/3.3.rst | 3 +++ pyproject.toml | 1 + 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 58d5b26769..95f828a873 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -11,6 +11,12 @@ build: # to allow setuptools-scm to correctly auto-discover the version. - git fetch --unshallow - git fetch --all + # Need to stash the local changes that Read the Docs makes so that + # setuptools_scm can generate the correct Iris version. + pre_install: + - git stash + post_install: + - git stash pop conda: environment: requirements/ci/readthedocs.yml diff --git a/docs/src/_templates/custom_sidebar_logo_version.html b/docs/src/_templates/custom_sidebar_logo_version.html index 48bbe604a0..c9d9ac6e2e 100644 --- a/docs/src/_templates/custom_sidebar_logo_version.html +++ b/docs/src/_templates/custom_sidebar_logo_version.html @@ -1,20 +1,26 @@ {% if on_rtd %} {% if rtd_version == 'latest' %} - + {% elif rtd_version == 'stable' %} - + + + {% elif rtd_version_type == 'tag' %} + {# Covers builds for specific tags, including RC's. #} + + {% else %} - - + {# Anything else build by RTD will be the HEAD of an activated branch #} + + {% endif %} {%- else %} {# not on rtd #} - - + + {%- endif %} diff --git a/docs/src/conf.py b/docs/src/conf.py index 7f0c517e05..33864c4658 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -25,7 +25,9 @@ import os from pathlib import Path import re +from subprocess import run import sys +from urllib.parse import quote import warnings @@ -42,11 +44,21 @@ def autolog(message): # This is the rtd reference to the version, such as: latest, stable, v3.0.1 etc rtd_version = os.environ.get("READTHEDOCS_VERSION") +if rtd_version is not None: + # Make rtd_version safe for use in shields.io badges. + rtd_version = rtd_version.replace("_", "__") + rtd_version = rtd_version.replace("-", "--") + rtd_version = quote(rtd_version) + +# branch, tag, external (for pull request builds), or unknown. +rtd_version_type = os.environ.get("READTHEDOCS_VERSION_TYPE") # For local testing purposes we can force being on RTD and the version # on_rtd = True # useful for testing # rtd_version = "latest" # useful for testing # rtd_version = "stable" # useful for testing +# rtd_version_type = "tag" # useful for testing +# rtd_version = "my_branch" # useful for testing if on_rtd: autolog("Build running on READTHEDOCS server") @@ -85,10 +97,7 @@ def autolog(message): # The version info for the project you're documenting, acts as replacement for # |version|, also used in various other places throughout the built documents. - version = get_version("scitools-iris") -if version.endswith("+dirty"): - version = version[: -len("+dirty")] release = version autolog(f"Iris Version = {version}") autolog(f"Iris Release = {release}") @@ -303,6 +312,9 @@ def _dotv(version): "show_toc_level": 1, } +rev_parse = run(["git", "rev-parse", "--short", "HEAD"], capture_output=True) +commit_sha = rev_parse.stdout.decode().strip() + html_context = { # pydata_theme "github_repo": "iris", @@ -312,9 +324,11 @@ def _dotv(version): # custom "on_rtd": on_rtd, "rtd_version": rtd_version, + "rtd_version_type": rtd_version_type, "version": version, "copyright_years": copyright_years, "python_version": build_python_version, + "commit_sha": commit_sha, } # Add any paths that contain custom static files (such as style sheets) here, diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index 4459e92784..57478c9645 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -318,6 +318,9 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ and `@wjbenfold`_ (reviewer) used ``pytest`` parametrization to streamline the gallery test code. (:pull:`4792`) +#. `@trexfeathers`_ improved settings to better working with + ``setuptools_scm``. (:pull:`4925`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/pyproject.toml b/pyproject.toml index 5f3071bcc4..bdb8a431e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] write_to = "lib/iris/_version.py" local_scheme = "dirty-tag" +version_scheme = "release-branch-semver" [tool.black] line-length = 79 From 5ceaa52b33e96ac93da9b7af3beba8d6cdcf2cd3 Mon Sep 17 00:00:00 2001 From: Manuel Schlund <32543114+schlunma@users.noreply.github.com> Date: Thu, 25 Aug 2022 11:43:21 +0200 Subject: [PATCH 210/319] Allowed collapsing coordinates with `nbounds != (0, 2)` (#4870) * Allowed collapsing over coordinates with nbounds!=0,2 * Simplified test * Added further tests for other warnings * pre-commit * Used unittest's assertWarnsRegex * Added What's new? entry --- docs/src/whatsnew/latest.rst | 7 +- lib/iris/coords.py | 24 ++++-- lib/iris/tests/unit/coords/test_Coord.py | 95 +++++++++++++++++++++++- lib/iris/tests/unit/cube/test_Cube.py | 61 +++++++++++++++ 4 files changed, 179 insertions(+), 8 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index c388c5fb7b..d0b03d9304 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -42,6 +42,11 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ and `@pp-mo`_ (reviewer) factored masking into the returned sum-of-weights calculation from :obj:`~iris.analysis.SUM`. (:pull:`4905`) +#. `@schlunma`_ fixed a bug which prevented using + :meth:`iris.cube.Cube.collapsed` on coordinates whose number of bounds + differs from 0 or 2. This enables the use of this method on mesh + coordinates. (:issue:`4672`, :pull:`4870`) + 💣 Incompatible Changes ======================= @@ -95,4 +100,4 @@ This document explains the changes made to Iris for this release .. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html -.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html \ No newline at end of file +.. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html diff --git a/lib/iris/coords.py b/lib/iris/coords.py index 0a1aecb983..d0d471a634 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -2215,12 +2215,24 @@ def serialize(x): "Metadata may not be fully descriptive for {!r}." ) warnings.warn(msg.format(self.name())) - elif not self.is_contiguous(): - msg = ( - "Collapsing a non-contiguous coordinate. " - "Metadata may not be fully descriptive for {!r}." - ) - warnings.warn(msg.format(self.name())) + else: + try: + self._sanity_check_bounds() + except ValueError as exc: + msg = ( + "Cannot check if coordinate is contiguous: {} " + "Metadata may not be fully descriptive for {!r}. " + "Ignoring bounds." + ) + warnings.warn(msg.format(str(exc), self.name())) + self.bounds = None + else: + if not self.is_contiguous(): + msg = ( + "Collapsing a non-contiguous coordinate. " + "Metadata may not be fully descriptive for {!r}." + ) + warnings.warn(msg.format(self.name())) if self.has_bounds(): item = self.core_bounds() diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index 08ed8d55e5..dca6ed3c1b 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -332,7 +332,8 @@ def test_dim_1d(self): ) for units in ["unknown", "no_unit", 1, "K"]: coord.units = units - collapsed_coord = coord.collapsed() + with self.assertNoWarningsRegexp(): + collapsed_coord = coord.collapsed() self.assertArrayEqual( collapsed_coord.points, np.mean(coord.points) ) @@ -474,6 +475,98 @@ def test_lazy_nd_points_and_bounds(self): self.assertArrayEqual(collapsed_coord.points, da.array([55])) self.assertArrayEqual(collapsed_coord.bounds, da.array([[-2, 112]])) + def test_numeric_nd_multidim_bounds_warning(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y") + + msg = ( + "Collapsing a multi-dimensional coordinate. " + "Metadata may not be fully descriptive for 'y'." + ) + with self.assertWarnsRegex(UserWarning, msg): + coord.collapsed() + + def test_lazy_nd_multidim_bounds_warning(self): + self.setupTestArrays((3, 4)) + coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y") + + msg = ( + "Collapsing a multi-dimensional coordinate. " + "Metadata may not be fully descriptive for 'y'." + ) + with self.assertWarnsRegex(UserWarning, msg): + coord.collapsed() + + def test_numeric_nd_noncontiguous_bounds_warning(self): + self.setupTestArrays((3)) + coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y") + + msg = ( + "Collapsing a non-contiguous coordinate. " + "Metadata may not be fully descriptive for 'y'." + ) + with self.assertWarnsRegex(UserWarning, msg): + coord.collapsed() + + def test_lazy_nd_noncontiguous_bounds_warning(self): + self.setupTestArrays((3)) + coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y") + + msg = ( + "Collapsing a non-contiguous coordinate. " + "Metadata may not be fully descriptive for 'y'." + ) + with self.assertWarnsRegex(UserWarning, msg): + coord.collapsed() + + def test_numeric_3_bounds(self): + + points = np.array([2.0, 6.0, 4.0]) + bounds = np.array([[1.0, 0.0, 3.0], [5.0, 4.0, 7.0], [3.0, 2.0, 5.0]]) + + coord = AuxCoord(points, bounds=bounds, long_name="x") + + msg = ( + r"Cannot check if coordinate is contiguous: Invalid operation for " + r"'x', with 3 bound\(s\). Contiguous bounds are only defined for " + r"1D coordinates with 2 bounds. Metadata may not be fully " + r"descriptive for 'x'. Ignoring bounds." + ) + with self.assertWarnsRegex(UserWarning, msg): + collapsed_coord = coord.collapsed() + + self.assertFalse(collapsed_coord.has_lazy_points()) + self.assertFalse(collapsed_coord.has_lazy_bounds()) + + self.assertArrayAlmostEqual(collapsed_coord.points, np.array([4.0])) + self.assertArrayAlmostEqual( + collapsed_coord.bounds, np.array([[2.0, 6.0]]) + ) + + def test_lazy_3_bounds(self): + + points = da.arange(3) * 2.0 + bounds = da.arange(3 * 3).reshape(3, 3) + + coord = AuxCoord(points, bounds=bounds, long_name="x") + + msg = ( + r"Cannot check if coordinate is contiguous: Invalid operation for " + r"'x', with 3 bound\(s\). Contiguous bounds are only defined for " + r"1D coordinates with 2 bounds. Metadata may not be fully " + r"descriptive for 'x'. Ignoring bounds." + ) + with self.assertWarnsRegex(UserWarning, msg): + collapsed_coord = coord.collapsed() + + self.assertTrue(collapsed_coord.has_lazy_points()) + self.assertTrue(collapsed_coord.has_lazy_bounds()) + + self.assertArrayAlmostEqual(collapsed_coord.points, da.array([2.0])) + self.assertArrayAlmostEqual( + collapsed_coord.bounds, da.array([[0.0, 4.0]]) + ) + class Test_is_compatible(tests.IrisTest): def setUp(self): diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 944d216a30..f38d6ef35d 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -565,6 +565,67 @@ def test_no_lat_weighted_aggregator_mixed(self): self._assert_nowarn_collapse_without_weight(coords, warn) +class Test_collapsed_coord_with_3_bounds(tests.IrisTest): + def setUp(self): + self.cube = Cube([1, 2]) + + bounds = [[0.0, 1.0, 2.0], [2.0, 3.0, 4.0]] + lat = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="latitude") + lon = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="longitude") + + self.cube.add_aux_coord(lat, 0) + self.cube.add_aux_coord(lon, 0) + + def _assert_warn_cannot_check_contiguity(self, warn): + # Ensure that warning is raised. + for coord in ["latitude", "longitude"]: + msg = ( + f"Cannot check if coordinate is contiguous: Invalid " + f"operation for '{coord}', with 3 bound(s). Contiguous " + f"bounds are only defined for 1D coordinates with 2 " + f"bounds. Metadata may not be fully descriptive for " + f"'{coord}'. Ignoring bounds." + ) + self.assertIn(mock.call(msg), warn.call_args_list) + + def _assert_cube_as_expected(self, cube): + """Ensure that cube data and coordiantes are as expected.""" + self.assertArrayEqual(cube.data, np.array(3)) + + lat = cube.coord("latitude") + self.assertArrayAlmostEqual(lat.points, np.array([1.5])) + self.assertArrayAlmostEqual(lat.bounds, np.array([[1.0, 2.0]])) + + lon = cube.coord("longitude") + self.assertArrayAlmostEqual(lon.points, np.array([1.5])) + self.assertArrayAlmostEqual(lon.bounds, np.array([[1.0, 2.0]])) + + def test_collapsed_lat_with_3_bounds(self): + """Collapse latitude with 3 bounds.""" + with mock.patch("warnings.warn") as warn: + collapsed_cube = self.cube.collapsed("latitude", iris.analysis.SUM) + self._assert_warn_cannot_check_contiguity(warn) + self._assert_cube_as_expected(collapsed_cube) + + def test_collapsed_lon_with_3_bounds(self): + """Collapse longitude with 3 bounds.""" + with mock.patch("warnings.warn") as warn: + collapsed_cube = self.cube.collapsed( + "longitude", iris.analysis.SUM + ) + self._assert_warn_cannot_check_contiguity(warn) + self._assert_cube_as_expected(collapsed_cube) + + def test_collapsed_lat_lon_with_3_bounds(self): + """Collapse latitude and longitude with 3 bounds.""" + with mock.patch("warnings.warn") as warn: + collapsed_cube = self.cube.collapsed( + ["latitude", "longitude"], iris.analysis.SUM + ) + self._assert_warn_cannot_check_contiguity(warn) + self._assert_cube_as_expected(collapsed_cube) + + class Test_summary(tests.IrisTest): def setUp(self): self.cube = Cube(0) From 690f69f5bd4fff41a952a0066e520e18b0dca240 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Thu, 25 Aug 2022 17:02:52 +0100 Subject: [PATCH 211/319] fix whatsnew (#4932) --- docs/src/whatsnew/3.3.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index 57478c9645..fc582fa7a5 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -201,7 +201,7 @@ This document explains the changes made to Iris for this release #. NumPy ``v1.23`` behaviour changes mean that :func:`iris.experimental.ugrid.utils.recombine_submeshes` now uses ~3x as much memory; testing shows a ~16-million point mesh will now use ~600MB. - Investigated by `@pp-mo` and `@trexfeathers`. (:issue:`4845`) + Investigated by `@pp-mo`_ and `@trexfeathers`_. (:issue:`4845`) 🔥 Deprecations From b79c61de05ed20763544d710651644700fd935b0 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Tue, 30 Aug 2022 12:34:24 +0100 Subject: [PATCH 212/319] Updated environment lockfiles (#4936) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 20 +++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 22 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 22 ++++++++++---------- 3 files changed, 32 insertions(+), 32 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index b29cac464f..d88fd19a29 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e79be0c1a5de550c1c2e192396e0e5cfda2ba23a154e1c4531d893da416f8cee +# input_hash: 043088e81c1e979eac04ac622e72d5d9f2c559c9059eae30112aafa081dffa6d @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -113,6 +113,7 @@ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.6.15-pyhd8ed1ab_1.tar.bz2#97349c8d67627cbf8f48d7e7e1773ea5 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -150,6 +151,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -172,7 +174,6 @@ https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 -https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 @@ -184,19 +185,21 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py310h53a5b5f_0.tar.bz2#8b3cfad14508018915e88612f5a963cd https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310hbd86126_2.tar.bz2#443272de4234f6df4a78f50105edc741 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.tar.bz2#378f2260e871f3ea46c6fa58d9f05277 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py310hff52083_0.tar.bz2#fe1a6180b086ddfa214627f00d1c4b58 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 @@ -205,17 +208,14 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_100 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py310h5764c6d_0.tar.bz2#75969f26ba850c17f6ceab6434aa1b0d +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.1-py310h5764c6d_0.tar.bz2#3dda361cb1fa5da73a75c4089d2ed338 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 @@ -230,14 +230,14 @@ https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.t https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py310h5701ce4_1.tar.bz2#36adf7d1b16bee40fcad8bfce54baa85 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py310h8d5ebf3_2.tar.bz2#760bc53cc184c9d6eeca9a38099e5fa8 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_1.tar.bz2#a06eff87c5bbd8ece667e155854fec2b +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_2.tar.bz2#9212ffec588998a9b3ac573bba2e597e https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a @@ -250,7 +250,7 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c8 https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_1.tar.bz2#7fa212f8ef62e0ec36f20448267ce95a +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_2.tar.bz2#46fb1538bf92de6d807feb81b462aa0f https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 1508ea0f55..af62d7e5b1 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 05da38480edd3a8765d02d305d525d5b0ffd45d84de4a2f81e21421d61cad72c +# input_hash: 40cbe959a02aa488bdf70e6b6968135b05b560f9b9ed8768ccf1780314c0e219 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -130,6 +130,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.6.15-pyhd8ed1ab_1.tar.bz2#97349c8d67627cbf8f48d7e7e1773ea5 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -159,6 +160,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -177,7 +179,6 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 @@ -187,19 +188,22 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0. https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_2.tar.bz2#fccce86e5fc8183bf2658ac9bfc535b4 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py38h3a7f9d9_0.tar.bz2#a7579626c41b3975da213c0b53aefa29 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38ha3b2c9c_2.tar.bz2#a077cc2bb9d854074b1cf4607252da7a +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py38h578d9bd_0.tar.bz2#1daef6b2dad47429ddf747ce04f4fd6e https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 @@ -208,17 +212,13 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py38h0a891b7_0.tar.bz2#8e43961af78ba54e8a72b0d8ce80588a +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.1-py38h0a891b7_0.tar.bz2#369c805e42d0244be7c097b39c38ebb4 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe @@ -234,13 +234,13 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py38h826bfd8_1.tar.bz2#454fd59caae7913651f08e3856c4c2e9 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py38h38b5ce0_2.tar.bz2#0db5b110946be87a04643c1ba95c6ef9 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h32db9c8_101.tar.bz2#d1451d40c8204594cdcf156363128000 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_1.tar.bz2#9e07b83f7d5e70bb48d13938ff93faf9 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_2.tar.bz2#0beb44c3333518cdbb4ccbf7913ff38a https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 @@ -248,7 +248,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py38h578d9bd_1.tar.bz2#6b6a356f4ff4a8b26b74add4ba8c0c22 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py38h578d9bd_2.tar.bz2#3b6f187bade8a47d05c8a74c6385a900 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index ef20dfd33b..3cfe2b9a29 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: babb8284ac9609a6063fb9a97f69a471814cdad98b7e619ba1c902891e884ee2 +# input_hash: 87d5bb40e4218219f5c768306688f703396cce4593f26c2ff46d8adb198c9ae9 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 @@ -131,6 +131,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.6.15-pyhd8ed1ab_1.tar.bz2#97349c8d67627cbf8f48d7e7e1773ea5 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 @@ -160,6 +161,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -178,7 +180,6 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 @@ -188,19 +189,22 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0. https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_2.tar.bz2#b809706525f081610469169b671b2600 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py39hba7629e_0.tar.bz2#25285f960f9c7f4e8ef56171af5e2a22 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hd5dbb17_2.tar.bz2#3b74a959f6a8008f5901de60b3572c09 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar.bz2#d34b97a2386932b97c7cb80916a673e7 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py39hf3d152e_0.tar.bz2#1b778cca1524a5cc4240f277d97dae99 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f @@ -209,17 +213,13 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py39hb9d737c_0.tar.bz2#a93c3de7835e68d42f9203886b372a8b +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.1-py39hb9d737c_0.tar.bz2#b006086e249cf6d88758bff9b462f971 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 @@ -235,13 +235,13 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py39h700656a_1.tar.bz2#43191b76750399349519f53feac83094 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py39h19d6b11_2.tar.bz2#dc400bb297d8425b8b05367a21854b0b https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h71b8e10_101.tar.bz2#91e01aa93a2bcca96c9d64d2ce4f65f0 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_1.tar.bz2#7264fa97520021d1a01f5be9741493c2 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_2.tar.bz2#12964abb0bdcb4abb3c680b359560c1b https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 @@ -249,7 +249,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py39hf3d152e_1.tar.bz2#bed9a319c4a3ac57376f5d95ba923d78 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py39hf3d152e_2.tar.bz2#98bf9bdfbac2ac73bbd1dc12a61519eb https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba From 36a7e81fe71463f6aecc6127635c303f8cf76354 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Wed, 31 Aug 2022 16:33:30 +0100 Subject: [PATCH 213/319] update whatsnew title for v3.3 release (#4939) --- docs/src/whatsnew/3.3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index fc582fa7a5..5812b79860 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -1,7 +1,7 @@ .. include:: ../common_links.inc -v3.3 (18 Aug 2022) [release candidate] -************************************** +v3.3 (1 Sep 2022) +***************** This document explains the changes made to Iris for this release (:doc:`View all changes `.) From 324a492c5de0a75a67157d59bb5c3cb09c6b2d3b Mon Sep 17 00:00:00 2001 From: Elias <110238618+ESadek-MO@users.noreply.github.com> Date: Tue, 6 Sep 2022 13:01:31 +0100 Subject: [PATCH 214/319] Added zonal mean gallery example, continued from @TTV-Intrepid (#4935) * Added zonal mean gallery example, continued from @TTV-Intrepid * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added TTVIntrepid to common links for What's New functionality * Fixed zonal mean plotting, add axh * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Minor visual changes, ax3 plotting fix, removed warning suppression. * updated imagerepo for zonal means * updated ci-tests yml to latest iris test data version * updated what's new * updated announcements to one line Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/ci-tests.yml | 2 +- docs/gallery_code/general/plot_zonal_means.py | 89 +++++++++++++++++++ docs/src/whatsnew/latest.rst | 7 +- lib/iris/tests/results/imagerepo.json | 1 + 4 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 docs/gallery_code/general/plot_zonal_means.py diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index aae1ce2970..43bacd3ec5 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -46,7 +46,7 @@ jobs: session: "tests" env: - IRIS_TEST_DATA_VERSION: "2.15" + IRIS_TEST_DATA_VERSION: "2.16" ENV_NAME: "ci-tests" steps: diff --git a/docs/gallery_code/general/plot_zonal_means.py b/docs/gallery_code/general/plot_zonal_means.py new file mode 100644 index 0000000000..08a9578e63 --- /dev/null +++ b/docs/gallery_code/general/plot_zonal_means.py @@ -0,0 +1,89 @@ +""" +Zonal Mean Diagram of Air Temperature +===================================== +This example demonstrates aligning a linear plot and a cartographic plot using Matplotlib. +""" + +import cartopy.crs as ccrs +import matplotlib.pyplot as plt +from mpl_toolkits.axes_grid1 import make_axes_locatable +import numpy as np + +import iris +from iris.analysis import MEAN +import iris.plot as iplt +import iris.quickplot as qplt + + +def main(): + + # Loads air_temp.pp and "collapses" longitude into a single, average value. + fname = iris.sample_data_path("air_temp.pp") + temperature = iris.load_cube(fname) + collapsed_temp = temperature.collapsed("longitude", MEAN) + + # Set y-axes with -90 and 90 limits and steps of 15 per tick. + start, stop, step = -90, 90, 15 + yticks = np.arange(start, stop + step, step) + ylim = [start, stop] + + # Plot "temperature" on a cartographic plot and set the ticks and titles + # on the axes. + fig = plt.figure(figsize=[12, 4]) + + ax1 = fig.add_subplot(111, projection=ccrs.PlateCarree()) + im = iplt.contourf(temperature, cmap="RdYlBu_r") + ax1.coastlines() + ax1.gridlines() + ax1.set_xticks([-180, -90, 0, 90, 180]) + ax1.set_yticks(yticks) + ax1.set_title("Air Temperature") + ax1.set_ylabel(f"Latitude / {temperature.coord('latitude').units}") + ax1.set_xlabel(f"Longitude / {temperature.coord('longitude').units}") + ax1.set_ylim(*ylim) + + # Create a Matplotlib AxesDivider object to allow alignment of other + # Axes objects. + divider = make_axes_locatable(ax1) + + # Gives the air temperature bar size, colour and a title. + ax2 = divider.new_vertical( + size="5%", pad=0.5, axes_class=plt.Axes, pack_start=True + ) # creates 2nd axis + fig.add_axes(ax2) + cbar = plt.colorbar( + im, cax=ax2, orientation="horizontal" + ) # puts colour bar on second axis + cbar.ax.set_xlabel(f"{temperature.units}") # labels colour bar + + # Plot "collapsed_temp" on the mean graph and set the ticks and titles + # on the axes. + ax3 = divider.new_horizontal( + size="30%", pad=0.4, axes_class=plt.Axes + ) # create 3rd axis + fig.add_axes(ax3) + qplt.plot( + collapsed_temp, collapsed_temp.coord("latitude") + ) # plots temperature collapsed over longitude against latitude + ax3.axhline(0, color="k", linewidth=0.5) + + # Creates zonal mean details + ax3.set_title("Zonal Mean") + ax3.yaxis.set_label_position("right") + ax3.yaxis.tick_right() + ax3.set_yticks(yticks) + ax3.grid() + + # Round each tick for the third ax to the nearest 20 (ready for use). + data_max = collapsed_temp.data.max() + x_max = data_max - data_max % -20 + data_min = collapsed_temp.data.min() + x_min = data_min - data_min % 20 + ax3.set_xlim(x_min, x_max) + ax3.set_ylim(*ylim) + + plt.show() + + +if __name__ == "__main__": + main() diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d0b03d9304..a42a5a64be 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -25,7 +25,7 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. Welcome to `@ESadek-MO`_ who made their first contribution to Iris 🎉 +#. Welcome to `@ESadek-MO`_ and `@TTV-Intrepid`_ who made their first contributions to Iris 🎉 ✨ Features @@ -78,7 +78,8 @@ This document explains the changes made to Iris for this release 📚 Documentation ================ -#. N/A +#. `@ESadek-MO`_, `@TTV-Intrepid`_ and `@trexfeathers`_ added a gallery example for zonal + means plotted parallel to a cartographic plot. (:pull:`4871`) 💼 Internal @@ -92,6 +93,8 @@ This document explains the changes made to Iris for this release Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: +.. _@TTV-Intrepid: https://github.com/TTV-Intrepid + diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index 342428ca67..e5c2ad863a 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -34,6 +34,7 @@ "gallery_tests.test_plot_wind_barbs.0": "e9e161e996169316c1fe9e96c29e36739e13c07c3d61c07f39813929c07f3f01", "gallery_tests.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e03c06c3d61c07f3da139e1c07f3f01", "gallery_tests.test_plot_wind_speed.1": "e9e960e996169306c1ee9f96c29e36739653c06c3d61c07f39a139e1c07f3f01", + "gallery_tests.test_plot_zonal_means.0": "b45b3071c9a4c9a6c69c363cc327cbb3cb9634d8c9e63cf336738c6634d8c384", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f", From a6331cfd8f9e9d66b20b8fdfcdf3c7aa9eda2cd7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Sep 2022 13:14:02 +0100 Subject: [PATCH 215/319] [pre-commit.ci] pre-commit autoupdate (#4942) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.6.0 → 22.8.0](https://github.com/psf/black/compare/22.6.0...22.8.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ab7e64b6a0..22746cb0ee 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: no-commit-to-branch - repo: https://github.com/psf/black - rev: 22.6.0 + rev: 22.8.0 hooks: - id: black pass_filenames: false From a13eb9701596fa81bede3a905f3f98e7ed7a154b Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 8 Sep 2022 10:48:59 +0100 Subject: [PATCH 216/319] Remove deprecated 'python setup.py test' support (#4948) * Remove deprecated 'python setup.py test' support * add whatsnew entry --- docs/src/whatsnew/latest.rst | 7 +++++-- setup.py | 26 -------------------------- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a42a5a64be..df267dc89e 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -88,6 +88,10 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ removed the obsolete ``setUpClass`` method from Iris testing. (:pull:`4927`) +#. `@bjlittle`_ and `@lbdreyer`_ (reviewer) removed support for + ``python setup.py test``, which is a deprecated approach to executing + package tests, see `pypa/setuptools#1684`_. (:pull:`4948`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, @@ -97,10 +101,9 @@ This document explains the changes made to Iris for this release - .. comment Whatsnew resources in alphabetical order: - .. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html .. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html +.. _pypa/setuptools#1684: https://github.com/pypa/setuptools/issues/1684 \ No newline at end of file diff --git a/setup.py b/setup.py index c7438ee698..089d33cb7b 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -from contextlib import contextmanager import os from shutil import copyfile import sys @@ -8,30 +7,6 @@ from setuptools.command.develop import develop as develop_cmd -@contextmanager -def temporary_path(directory): - """ - Context manager that adds and subsequently removes the given directory - to sys.path - - """ - sys.path.insert(0, directory) - try: - yield - finally: - del sys.path[0] - - -# Add full path so Python doesn't load any __init__.py in the intervening -# directories, thereby saving setup.py from additional dependencies. -with temporary_path("lib/iris/tests/runner"): - from _runner import TestRunner # noqa: - - -class SetupTestRunner(TestRunner, Command): - pass - - class BaseCommand(Command): """A valid no-op command for setuptools & distutils.""" @@ -108,7 +83,6 @@ def run(self): custom_commands = { - "test": SetupTestRunner, "develop": custom_cmd(develop_cmd, [build_std_names]), "build_py": custom_cmd(build_py, [build_std_names, copy_copyright]), "std_names": custom_cmd( From 90bf9285d8f2f85dba39df5c0029826b9a585180 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 8 Sep 2022 11:54:46 +0100 Subject: [PATCH 217/319] purge unused setup clean_source custom command (#4949) --- setup.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/setup.py b/setup.py index 089d33cb7b..2409f63912 100644 --- a/setup.py +++ b/setup.py @@ -23,20 +23,6 @@ def run(self): pass -class CleanSource(BaseCommand): - description = "clean orphaned pyc/pyo files from the source directory" - - def run(self): - for root_path, dir_names, file_names in os.walk("lib"): - for file_name in file_names: - if file_name.endswith("pyc") or file_name.endswith("pyo"): - compiled_path = os.path.join(root_path, file_name) - source_path = compiled_path[:-1] - if not os.path.exists(source_path): - print("Cleaning", compiled_path) - os.remove(compiled_path) - - def copy_copyright(cmd, directory): # Copy the COPYRIGHT information into the package root iris_build_dir = os.path.join(directory, "iris") @@ -90,7 +76,6 @@ def run(self): [build_std_names], help_doc="generate CF standard name module", ), - "clean_source": CleanSource, } From 108ea8d649874797ebcc9e2414da42a4237f468a Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 8 Sep 2022 14:22:31 +0100 Subject: [PATCH 218/319] purge unnecessary setup.py copy_copyright function (#4950) --- setup.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/setup.py b/setup.py index 2409f63912..ae8a1cd8b4 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ import os -from shutil import copyfile import sys from setuptools import Command, setup @@ -23,13 +22,6 @@ def run(self): pass -def copy_copyright(cmd, directory): - # Copy the COPYRIGHT information into the package root - iris_build_dir = os.path.join(directory, "iris") - for fname in ["COPYING", "COPYING.LESSER"]: - copyfile(fname, os.path.join(iris_build_dir, fname)) - - def build_std_names(cmd, directory): # Call out to tools/generate_std_names.py to build std_names module. @@ -70,7 +62,7 @@ def run(self): custom_commands = { "develop": custom_cmd(develop_cmd, [build_std_names]), - "build_py": custom_cmd(build_py, [build_std_names, copy_copyright]), + "build_py": custom_cmd(build_py, [build_std_names]), "std_names": custom_cmd( BaseCommand, [build_std_names], From e4ed9509261d444d596ad89138570d11a2cbb4af Mon Sep 17 00:00:00 2001 From: Bill Little Date: Thu, 8 Sep 2022 18:28:45 +0100 Subject: [PATCH 219/319] fix setup.py std_names building (#4952) * fix setup.py std_names building * refactor --- setup.py | 77 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/setup.py b/setup.py index ae8a1cd8b4..061b35c262 100644 --- a/setup.py +++ b/setup.py @@ -3,14 +3,14 @@ from setuptools import Command, setup from setuptools.command.build_py import build_py -from setuptools.command.develop import develop as develop_cmd +from setuptools.command.develop import develop class BaseCommand(Command): - """A valid no-op command for setuptools & distutils.""" + """A minimal no-op setuptools command.""" - description = "A no-op command." - user_options = [] + description: str = "A no-op command." + user_options: list = [] def initialize_options(self): pass @@ -22,51 +22,64 @@ def run(self): pass -def build_std_names(cmd, directory): - # Call out to tools/generate_std_names.py to build std_names module. +def custom_command(cmd, help=""): + """ + Factory function to generate a custom command that adds additional + behaviour to build the CF standard names module. - script_path = os.path.join("tools", "generate_std_names.py") - xml_path = os.path.join("etc", "cf-standard-name-table.xml") - module_path = os.path.join(directory, "iris", "std_names.py") - args = (sys.executable, script_path, xml_path, module_path) - cmd.spawn(args) + """ + class CustomCommand(cmd): + description = help or cmd.description -def custom_cmd(command_to_override, functions, help_doc=""): - """ - Allows command specialisation to include calls to the given functions. + def _build_std_names(self, directory): + # Call out to tools/generate_std_names.py to build std_names module. - """ + script_path = os.path.join("tools", "generate_std_names.py") + xml_path = os.path.join("etc", "cf-standard-name-table.xml") + module_path = os.path.join(directory, "iris", "std_names.py") + args = (sys.executable, script_path, xml_path, module_path) + self.spawn(args) + + def finalize_options(self): + # Execute the parent "cmd" class method. + cmd.finalize_options(self) - class ExtendedCommand(command_to_override): - description = help_doc or command_to_override.description + if ( + not hasattr(self, "editable_mode") + or self.editable_mode is None + ): + # Default to editable i.e., applicable to "std_names" and + # and "develop" commands. + self.editable_mode = True def run(self): - # Run the original command first to make sure all the target - # directories are in place. - command_to_override.run(self) + # Execute the parent "cmd" class method. + cmd.run(self) + # Determine the target root directory if self.editable_mode: - print(" [Running in-place]") # Pick the source dir instead (currently in the sub-dir "lib"). - dest = "lib" + target = "lib" + msg = "in-place" else: # Not editable - must be building. - dest = self.build_lib + target = self.build_lib + msg = "as-build" + + print(f"\n[Running {msg}]") - for func in functions: - func(self, dest) + # Build the CF standard names. + self._build_std_names(target) - return ExtendedCommand + return CustomCommand custom_commands = { - "develop": custom_cmd(develop_cmd, [build_std_names]), - "build_py": custom_cmd(build_py, [build_std_names]), - "std_names": custom_cmd( - BaseCommand, - [build_std_names], - help_doc="generate CF standard name module", + "develop": custom_command(develop), + "build_py": custom_command(build_py), + "std_names": custom_command( + BaseCommand, help="generate CF standard names" ), } From 60c8b34f1c0561b292b6713d13f91785dfbc2854 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 9 Sep 2022 09:39:11 +0100 Subject: [PATCH 220/319] docs whatsnew entry updates (#4953) --- docs/src/whatsnew/latest.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index df267dc89e..feebc39d16 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -47,6 +47,10 @@ This document explains the changes made to Iris for this release differs from 0 or 2. This enables the use of this method on mesh coordinates. (:issue:`4672`, :pull:`4870`) +#. `@bjlittle`_ and `@lbdreyer`_ (reviewer) fixed the building of the CF + Standard Names module ``iris.std_names`` for the ``setup.py`` commands + ``develop`` and ``std_names``. (:issue:`4951`, :pull:`4952`) + 💣 Incompatible Changes ======================= @@ -90,7 +94,8 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ and `@lbdreyer`_ (reviewer) removed support for ``python setup.py test``, which is a deprecated approach to executing - package tests, see `pypa/setuptools#1684`_. (:pull:`4948`) + package tests, see `pypa/setuptools#1684`_. Also performed assorted + ``setup.py`` script hygiene. (:pull:`4948`, :pull:`4949`, :pull:`4950`) .. comment From 9c09b416f6f0b795b59a5ebe1d88ffd2e92eb8fb Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 9 Sep 2022 14:40:16 +0100 Subject: [PATCH 221/319] Fix cube repr for scalar anc_var and cell measures (#4945) * Fix cube repr for scalar anc_var and cell measures * Add whatsnew * Fix cube summary tests * Review actions: reword whats new, reorder scalar sections in cube printout * Fix cube summary test, update whatsnew * Fix code comment --- docs/src/whatsnew/latest.rst | 6 +++ lib/iris/_representation/cube_printout.py | 5 ++- lib/iris/_representation/cube_summary.py | 38 ++++++++++++++----- .../cube_printout/test_CubePrintout.py | 32 +++++++++++++++- .../cube_summary/test_CubeSummary.py | 3 +- 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index feebc39d16..a420494157 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -50,6 +50,12 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ and `@lbdreyer`_ (reviewer) fixed the building of the CF Standard Names module ``iris.std_names`` for the ``setup.py`` commands ``develop`` and ``std_names``. (:issue:`4951`, :pull:`4952`) + +#. `@lbdreyer`_ and `@pp-mo`_ (reviewer) fixed the cube print out such that + scalar ancillary variables are displayed in a dedicated section rather than + being added to the vector ancillary variables section. Further, ancillary + variables and cell measures that map to a cube dimension of length 1 are now + included in the respective vector sections. (:pull:`4945`) 💣 Incompatible Changes diff --git a/lib/iris/_representation/cube_printout.py b/lib/iris/_representation/cube_printout.py index b0b569512d..ea32fc5126 100644 --- a/lib/iris/_representation/cube_printout.py +++ b/lib/iris/_representation/cube_printout.py @@ -260,7 +260,10 @@ def add_scalar_row(name, value=""): elif title in ("attributes:", "cell methods:", "mesh:"): for title, value in zip(sect.names, sect.values): add_scalar_row(title, value) - elif title == "scalar cell measures:": + elif title in ( + "scalar ancillary variables:", + "scalar cell measures:", + ): # These are just strings: nothing in the 'value' column. for name in sect.contents: add_scalar_row(name) diff --git a/lib/iris/_representation/cube_summary.py b/lib/iris/_representation/cube_summary.py index 885de9acf3..6b0d4cf0f3 100644 --- a/lib/iris/_representation/cube_summary.py +++ b/lib/iris/_representation/cube_summary.py @@ -219,6 +219,12 @@ def __init__(self, title, cell_measures): self.contents = [cm.name() for cm in cell_measures] +class ScalarAncillaryVariableSection(Section): + def __init__(self, title, ancillary_variables): + self.title = title + self.contents = [av.name() for av in ancillary_variables] + + class AttributeSection(Section): def __init__(self, title, attributes): self.title = title @@ -274,7 +280,7 @@ class CubeSummary: """ - def __init__(self, cube, shorten=False, name_padding=35): + def __init__(self, cube, name_padding=35): self.header = FullHeader(cube, name_padding) # Cache the derived coords so we can rely on consistent @@ -314,13 +320,23 @@ def __init__(self, cube, shorten=False, name_padding=35): if id(coord) not in scalar_coord_ids ] - # cell measures - vector_cell_measures = [ - cm for cm in cube.cell_measures() if cm.shape != (1,) - ] - # Ancillary Variables - vector_ancillary_variables = [av for av in cube.ancillary_variables()] + vector_ancillary_variables = [] + scalar_ancillary_variables = [] + for av, av_dims in cube._ancillary_variables_and_dims: + if av_dims: + vector_ancillary_variables.append(av) + else: + scalar_ancillary_variables.append(av) + + # Cell Measures + vector_cell_measures = [] + scalar_cell_measures = [] + for cm, cm_dims in cube._cell_measures_and_dims: + if cm_dims: + vector_cell_measures.append(cm) + else: + scalar_cell_measures.append(cm) # Sort scalar coordinates by name. scalar_coords.sort(key=lambda coord: coord.name()) @@ -334,9 +350,6 @@ def __init__(self, cube, shorten=False, name_padding=35): vector_derived_coords.sort( key=lambda coord: (cube.coord_dims(coord), coord.name()) ) - scalar_cell_measures = [ - cm for cm in cube.cell_measures() if cm.shape == (1,) - ] self.vector_sections = {} @@ -369,6 +382,11 @@ def add_scalar_section(section_class, title, *args): "Scalar cell measures:", scalar_cell_measures, ) + add_scalar_section( + ScalarAncillaryVariableSection, + "Scalar ancillary variables:", + scalar_ancillary_variables, + ) add_scalar_section( CellMethodSection, "Cell methods:", cube.cell_methods ) diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py index ff42acf566..21fc8efa73 100644 --- a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py +++ b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py @@ -349,6 +349,20 @@ def test_section_vector_ancils(self): ] self.assertEqual(rep, expected) + def test_section_vector_ancils_length_1(self): + # Check ancillary variables that map to a cube dimension of length 1 + # are not interpreted as scalar ancillary variables. + cube = Cube(np.zeros((1, 3)), long_name="name", units=1) + cube.add_ancillary_variable(AncillaryVariable([0], long_name="av1"), 0) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 1; -- : 3)", + " Ancillary variables:", + " av1 x -", + ] + self.assertEqual(rep, expected) + def test_section_vector_cell_measures(self): cube = Cube(np.zeros((2, 3)), long_name="name", units=1) cube.add_cell_measure(CellMeasure([0, 1, 2], long_name="cm"), 1) @@ -361,6 +375,20 @@ def test_section_vector_cell_measures(self): ] self.assertEqual(rep, expected) + def test_section_vector_cell_measures_length_1(self): + # Check cell measures that map to a cube dimension of length 1 are not + # interpreted as scalar cell measures. + cube = Cube(np.zeros((2, 1)), long_name="name", units=1) + cube.add_cell_measure(CellMeasure([0], long_name="cm"), 1) + + rep = cube_replines(cube) + expected = [ + "name / (1) (-- : 2; -- : 1)", + " Cell measures:", + " cm - x", + ] + self.assertEqual(rep, expected) + def test_section_scalar_coords(self): # incl points + bounds # TODO: ought to incorporate coord-based summary @@ -424,8 +452,8 @@ def test_section_scalar_ancillaries(self): rep = cube_replines(cube) expected = [ "name / (1) (-- : 2; -- : 3)", - " Ancillary variables:", - " av - -", + " Scalar ancillary variables:", + " av", ] self.assertEqual(rep, expected) diff --git a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py index 8314c5c9ae..bcf31a016f 100644 --- a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py +++ b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py @@ -75,6 +75,7 @@ def test_blank_cube(self): "Mesh:", "Scalar coordinates:", "Scalar cell measures:", + "Scalar ancillary variables:", "Cell methods:", "Attributes:", ] @@ -222,7 +223,7 @@ def test_scalar_cube(self): self.assertTrue( all(sect.is_empty() for sect in rep.vector_sections.values()) ) - self.assertEqual(len(rep.scalar_sections), 5) + self.assertEqual(len(rep.scalar_sections), 6) self.assertEqual( len(rep.scalar_sections["Scalar coordinates:"].contents), 1 ) From ae2b59ba3dc68fe54893f4e8f4246e5c19c1a5bf Mon Sep 17 00:00:00 2001 From: Bill Little Date: Fri, 9 Sep 2022 14:55:23 +0100 Subject: [PATCH 222/319] update pypi trove classifiers (#4954) --- setup.cfg | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.cfg b/setup.cfg index 8166f88f59..5613d25998 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,6 +12,8 @@ classifiers = Programming Language :: Python Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Programming Language :: Python :: Implementation :: CPython Topic :: Scientific/Engineering Topic :: Scientific/Engineering :: Atmospheric Science From 8fd4e612930f4fe0856363c27a8a3ae154241f27 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 14 Sep 2022 14:51:39 +0100 Subject: [PATCH 223/319] Add new Q+A issue, update author email (#4962) --- .github/ISSUE_TEMPLATE/config.yml | 3 +++ setup.cfg | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 500a2183d2..b5f75cc6f8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -4,3 +4,6 @@ contact_links: - name: 💬 Iris GitHub Discussions url: https://github.com/SciTools/iris/discussions about: Engage with the Iris community to discuss your issue + - name: ❓ Usage Question + url: https://github.com/SciTools/iris/discussions/categories/q-a + About: Raise a question about how to use Iris in the Q&A section of Discussions. diff --git a/setup.cfg b/setup.cfg index 5613d25998..e5f0bc5b46 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] author = SciTools Developers -author_email = scitools-iris-dev@googlegroups.com +author_email = scitools.pub@gmail.com classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Science/Research From a3e0348f6257720ff3271941d2b2c89e72bb51a7 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 14 Sep 2022 15:25:15 +0100 Subject: [PATCH 224/319] Typo in issue template (#4964) --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b5f75cc6f8..84af305034 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -6,4 +6,4 @@ contact_links: about: Engage with the Iris community to discuss your issue - name: ❓ Usage Question url: https://github.com/SciTools/iris/discussions/categories/q-a - About: Raise a question about how to use Iris in the Q&A section of Discussions. + about: Raise a question about how to use Iris in the Q&A section of Discussions From 06c8bc6cad17a30f939fab8be771381a99d226b1 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 15 Sep 2022 13:50:20 +0100 Subject: [PATCH 225/319] Use Mamba for refreshing lock-files (#4966) * Temporarily enable lockfile refresh on push - for testing. * Temporarily enable lockfile refresh on push - for testing. * Temporarily enable lockfile refresh on push - for testing. * Use mamba for lock-file generation. * Simpler package installation. * Add Mamba warning to update_lockfiles.py. * Undo temporary testing changes. --- .github/workflows/ci-wheels.yml | 2 +- .github/workflows/refresh-lockfiles.yml | 6 ++++-- tools/update_lockfiles.py | 15 ++++++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-wheels.yml b/.github/workflows/ci-wheels.yml index 265489883f..a00833b118 100644 --- a/.github/workflows/ci-wheels.yml +++ b/.github/workflows/ci-wheels.yml @@ -163,4 +163,4 @@ jobs: with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} - print_hash: true \ No newline at end of file + print_hash: true diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 4e717be8a1..95b65acb8b 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -48,11 +48,13 @@ jobs: steps: - uses: actions/checkout@v3 - - name: install conda-lock + - name: install requirements run: | source $CONDA/bin/activate base - conda install -y -c conda-forge conda-lock + conda install -y -c conda-forge conda-libmamba-solver conda-lock - name: generate lockfile + env: + CONDA_EXPERIMENTAL_SOLVER: libmamba run: | $CONDA/bin/conda-lock lock -k explicit -p linux-64 -f requirements/ci/${{matrix.python}}.yml mv conda-linux-64.lock ${{matrix.python}}-linux-64.lock diff --git a/tools/update_lockfiles.py b/tools/update_lockfiles.py index f05210be87..dc898784ae 100755 --- a/tools/update_lockfiles.py +++ b/tools/update_lockfiles.py @@ -17,6 +17,15 @@ from pathlib import Path import subprocess import sys +from warnings import warn + + +message = ( + "Iris' large requirements may require Mamba to successfully solve. If you " + "don't want to install Mamba, consider using the workflow_dispatch on " + "Iris' GitHub action." +) +warn(message) try: @@ -29,9 +38,9 @@ "Iris Lockfile Generator", ) -parser.add_argument('files', nargs='+', +parser.add_argument('files', nargs='+', help="List of environment.yml files to lock") -parser.add_argument('--output-dir', '-o', default='.', +parser.add_argument('--output-dir', '-o', default='.', help="Directory to save output lock files") args = parser.parse_args() @@ -43,7 +52,7 @@ ftype = fname.split('.')[-1] if ftype.lower() in ('yaml', 'yml'): fname = '.'.join(fname.split('.')[:-1]) - + # conda-lock --filename-template expects a string with a "...{platform}..." # placeholder in it, so we have to build the .lock filname without # using .format From de911ce55da50172b51c662c44a7d1adff148dca Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 16 Sep 2022 14:23:32 +0100 Subject: [PATCH 226/319] Lockfile updates (#4968) * Updated environment lockfiles * Adjustments for Cartopy v0.21.0 (SciTools/cartopy@fcb784d). * Cartopy >=0.21 pin. * What's New entry. * WIP try netCDF4 pin. * Try pip installing netcdf4==1.6.1 as requested by @ocefpaf. * Revert "Try pip installing netcdf4==1.6.1 as requested by @ocefpaf." This reverts commit ce9f890980e32d7af9ef88c747e81b05fd5dea09. * netcdf4!=1.6.1 * Align Conda YAML formatting. Co-authored-by: Lockfile bot --- docs/src/whatsnew/latest.rst | 13 ++- lib/iris/analysis/cartography.py | 4 +- .../analysis/cartography/test_rotate_winds.py | 25 +++--- requirements/ci/nox.lock/py310-linux-64.lock | 84 +++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 86 +++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 86 +++++++++---------- requirements/ci/py310.yml | 4 +- requirements/ci/py38.yml | 4 +- requirements/ci/py39.yml | 4 +- setup.cfg | 4 +- 10 files changed, 164 insertions(+), 150 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a420494157..73f7e60351 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -50,7 +50,7 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ and `@lbdreyer`_ (reviewer) fixed the building of the CF Standard Names module ``iris.std_names`` for the ``setup.py`` commands ``develop`` and ``std_names``. (:issue:`4951`, :pull:`4952`) - + #. `@lbdreyer`_ and `@pp-mo`_ (reviewer) fixed the cube print out such that scalar ancillary variables are displayed in a dedicated section rather than being added to the vector ancillary variables section. Further, ancillary @@ -83,6 +83,13 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ introduced the ``dask >=2.26`` minimum pin, so that Iris can benefit from Dask's support for `NEP13`_ and `NEP18`_. (:pull:`4905`) +#. `@trexfeathers`_ advanced the Cartopy pin to ``>=0.21``, as Cartopy's + change to default Transverse Mercator projection affects an Iris test. + See `SciTools/cartopy@fcb784d`_ and `SciTools/cartopy@8860a81`_ for more + details. + (:pull:`4968`) +#. `@trexfeathers`_ introduced the ``netcdf4!=1.6.1`` pin to avoid a problem + with segfaults. (:pull:`4968`) 📚 Documentation @@ -117,4 +124,6 @@ This document explains the changes made to Iris for this release .. _NEP13: https://numpy.org/neps/nep-0013-ufunc-overrides.html .. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html -.. _pypa/setuptools#1684: https://github.com/pypa/setuptools/issues/1684 \ No newline at end of file +.. _pypa/setuptools#1684: https://github.com/pypa/setuptools/issues/1684 +.. _SciTools/cartopy@fcb784d: https://github.com/SciTools/cartopy/commit/fcb784daa65d95ed9a74b02ca292801c02bc4108 +.. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e \ No newline at end of file diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index 44129ff175..f38e48354d 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -1008,8 +1008,8 @@ def _transform_distance_vectors_tolerance_mask( u_one_t, v_zero_t = _transform_distance_vectors(ones, zeros, ds, dx2, dy2) u_zero_t, v_one_t = _transform_distance_vectors(zeros, ones, ds, dx2, dy2) # Squared magnitudes should be equal to one within acceptable tolerance. - # A value of atol=2e-3 is used, which corresponds to a change in magnitude - # of approximately 0.1%. + # A value of atol=2e-3 is used, which masks any magnitude changes >0.5% + # (approx percentage - based on experimenting). sqmag_1_0 = u_one_t**2 + v_zero_t**2 sqmag_0_1 = u_zero_t**2 + v_one_t**2 mask = np.logical_not( diff --git a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py index 7bd8fdb597..7952b3bb46 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py +++ b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py @@ -16,6 +16,7 @@ import cartopy.crs as ccrs import numpy as np import numpy.ma as ma +import pytest from iris.analysis.cartography import rotate_winds, unrotate_pole import iris.coord_systems @@ -410,7 +411,11 @@ def test_transposed(self): class TestMasking(tests.IrisTest): def test_rotated_to_osgb(self): # Rotated Pole data with large extent. - x = np.linspace(311.9, 391.1, 10) + # A 'correct' answer is not known for this test; it is therefore + # written as a 'benchmark' style test - a change in behaviour will + # cause a test failure, requiring developers to approve/reject the + # new behaviour. + x = np.linspace(221.9, 301.1, 10) y = np.linspace(-23.6, 24.8, 8) u, v = uv_cubes(x, y) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) @@ -422,14 +427,14 @@ def test_rotated_to_osgb(self): # Snapshot of mask with fixed tolerance of atol=2e-3 expected_mask = np.array( [ - [1, 1, 1, 0, 0, 0, 0, 0, 0, 1], - [1, 1, 1, 0, 0, 0, 0, 0, 0, 1], - [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], - [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], - [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 0, 0, 0, 1], + [0, 0, 0, 0, 1, 1, 1, 0, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 1, 0, 0, 0, 0, 1, 1, 1, 0], ], np.bool_, ) @@ -443,7 +448,7 @@ def test_rotated_to_osgb(self): # Calculate percentage error (note there are no zero magnitudes # so we can divide safely). anom = 100.0 * np.abs(res_mag - expected_mag) / expected_mag - self.assertTrue(anom[~ut.data.mask].max() < 0.1) + assert anom[~ut.data.mask].max() == pytest.approx(0.3227935) def test_rotated_to_unrotated(self): # Suffiently accurate so that no mask is introduced. diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index d88fd19a29..5a18284896 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 043088e81c1e979eac04ac622e72d5d9f2c559c9059eae30112aafa081dffa6d +# input_hash: 9d180dce74d2d2af2a3e377c05d03d2a00c603b6d46a9f39b9c92bfbc71e7d3e @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -19,12 +19,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.b https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -35,20 +35,20 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -71,25 +71,25 @@ https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#d https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce @@ -99,11 +99,11 @@ https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -113,18 +113,18 @@ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.6.15-pyhd8ed1ab_1.tar.bz2#97349c8d67627cbf8f48d7e7e1773ea5 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -149,6 +149,7 @@ https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b46 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da @@ -173,83 +174,82 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1013.tar.bz2#148e1893454972ac8c595c98c7b8ed5c https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py310h53a5b5f_0.tar.bz2#8b3cfad14508018915e88612f5a963cd +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py310h53a5b5f_0.tar.bz2#0a60ccaed9ad236cc7463322fe742eb6 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310hbd86126_2.tar.bz2#443272de4234f6df4a78f50105edc741 https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py310h5764c6d_0.tar.bz2#6ac13c26fe4f9d8d6b38657664c37fd3 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.tar.bz2#378f2260e871f3ea46c6fa58d9f05277 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py310hff52083_1.tar.bz2#a91c9f0499e0f0f5912098c3462014b9 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_0.tar.bz2#e572565848d8d19e74983f4d122734a8 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.1-py310h5764c6d_0.tar.bz2#3dda361cb1fa5da73a75c4089d2ed338 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.0-pyhd8ed1ab_0.tar.bz2#859607d7a80dc90039b3d94cfa2b1931 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py310h5764c6d_0.tar.bz2#4a195cef7a7649b3c44efff3853e162c +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_1.tar.bz2#34c6484852afee76e334c1f593dd8282 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.4-py310h769672d_0.tar.bz2#e04e98ab8bb134d7799fdac51b35e923 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_1.tar.bz2#0ad6207e9d553c67984a5b0b06bbd2a3 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py310hff52083_0.tar.bz2#18ef27d620d67af2feef22acfd42cf4a https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py310hdfbd76f_0.tar.bz2#e5d21b0cb4161a40221786f2f05b3903 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py310hdfbd76f_0.tar.bz2#bfb55d07ad9d15d2f2f8e59afcbcf578 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py310h5e49deb_0.tar.bz2#2f2c225d04e99ff99d6d3a86692ce968 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py310h8d5ebf3_2.tar.bz2#760bc53cc184c9d6eeca9a38099e5fa8 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_2.tar.bz2#9212ffec588998a9b3ac573bba2e597e +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_0.tar.bz2#3e81d6afa50895d6dee115ac5d34c2ea https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_2.tar.bz2#46fb1538bf92de6d807feb81b462aa0f https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index af62d7e5b1..f916efefc8 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 40cbe959a02aa488bdf70e6b6968135b05b560f9b9ed8768ccf1780314c0e219 +# input_hash: 22508264922e791fc7a682c11aec4c066a925d74d292b7f7d868e2efd0023bdf @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -18,12 +18,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.b https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -34,20 +34,20 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -70,25 +70,25 @@ https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#d https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce @@ -98,10 +98,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -110,7 +110,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -129,35 +129,36 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.ta https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.6.15-pyhd8ed1ab_1.tar.bz2#97349c8d67627cbf8f48d7e7e1773ea5 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1013.tar.bz2#148e1893454972ac8c595c98c7b8ed5c +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da @@ -180,8 +181,8 @@ https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_1.tar.bz2#34c6484852afee76e334c1f593dd8282 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258 @@ -189,40 +190,39 @@ https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_2.tar.bz2#fccce86e5fc8183bf2658ac9bfc535b4 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py38h3a7f9d9_0.tar.bz2#a7579626c41b3975da213c0b53aefa29 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py38h3a7f9d9_0.tar.bz2#83ba913fc1174925d4e862eccb53db59 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38ha3b2c9c_2.tar.bz2#a077cc2bb9d854074b1cf4607252da7a https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py38h0a891b7_0.tar.bz2#907a39b6d7443f770ed755885694f864 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hd7890fc_1.tar.bz2#f851bb08c85122fd0e1f66d2072ebf0b https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py38h578d9bd_1.tar.bz2#30765568a158c9457d577cc83f0e8307 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_0.tar.bz2#b2247bb2492e261c25fabbbb2c7a23b5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.1-py38h0a891b7_0.tar.bz2#369c805e42d0244be7c097b39c38ebb4 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.0-pyhd8ed1ab_0.tar.bz2#859607d7a80dc90039b3d94cfa2b1931 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py38h0a891b7_0.tar.bz2#5be9368f14f462705b69da737b11159d +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.4-py38h47df419_0.tar.bz2#2d32bac7cbadc044a7008452adee8c4e https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py38h578d9bd_0.tar.bz2#1fdabff56623511910fef3b418ff07a2 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py38hea3f02b_0.tar.bz2#d19e23bb56b31d2504a0ff4d46b7aabc +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py38hea3f02b_0.tar.bz2#b232edb409c6a79e5921b3591c56b716 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py38h3b45516_0.tar.bz2#d8621497bcc7b369ef9cce25d5a58aeb https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 @@ -231,25 +231,25 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py38h38b5ce0_2.tar.bz2#0db5b110946be87a04643c1ba95c6ef9 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h32db9c8_101.tar.bz2#d1451d40c8204594cdcf156363128000 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_2.tar.bz2#0beb44c3333518cdbb4ccbf7913ff38a +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38h606536b_0.tar.bz2#38fc3704565e44fb9fcdfaded03eee76 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py38h578d9bd_2.tar.bz2#3b6f187bade8a47d05c8a74c6385a900 -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 3cfe2b9a29..e0faf9028b 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 87d5bb40e4218219f5c768306688f703396cce4593f26c2ff46d8adb198c9ae9 +# input_hash: 1e66fc8b152ee34808f52142e576752894895cd3d1c29a3a408d42ee4062ba51 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -19,12 +19,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.b https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d @@ -35,20 +35,20 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -71,25 +71,25 @@ https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#d https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce @@ -99,10 +99,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -111,7 +111,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 @@ -130,35 +130,36 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.ta https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.6.15-pyhd8ed1ab_1.tar.bz2#97349c8d67627cbf8f48d7e7e1773ea5 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1013.tar.bz2#148e1893454972ac8c595c98c7b8ed5c +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da @@ -181,8 +182,8 @@ https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39h https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_2.tar.bz2#fea5dea40592ea943aa56f4935308ee4 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_1.tar.bz2#34c6484852afee76e334c1f593dd8282 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe @@ -190,40 +191,39 @@ https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_2.tar.bz2#b809706525f081610469169b671b2600 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py39hba7629e_0.tar.bz2#25285f960f9c7f4e8ef56171af5e2a22 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py39hba7629e_0.tar.bz2#320e25179733ec4a2ecffcebc8abbc80 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hd5dbb17_2.tar.bz2#3b74a959f6a8008f5901de60b3572c09 https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py39hb9d737c_0.tar.bz2#1e7ffe59e21862559e06b981817e5058 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar.bz2#d34b97a2386932b97c7cb80916a673e7 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h2c22827_1.tar.bz2#a1ca42c2a746601d42f27bbcb7f6acfc https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py39hf3d152e_1.tar.bz2#baa79a28aa08de404d9deae634b91e03 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_0.tar.bz2#165e71a44187ac22e2e1669fd3ca2392 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.1-py39hb9d737c_0.tar.bz2#b006086e249cf6d88758bff9b462f971 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.0-pyhd8ed1ab_0.tar.bz2#859607d7a80dc90039b3d94cfa2b1931 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py39hb9d737c_0.tar.bz2#b47e528ae5d9e0a786896a21aa0b0307 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.4-py39h1832856_0.tar.bz2#6ef85649798519bd47dfc24e399b8dcd https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py39hf3d152e_0.tar.bz2#b807481ba94ec32bc742f2fe775d0bff https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py39h8ba3f38_0.tar.bz2#b098a256777cb9e2605451f183c78768 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py39h8ba3f38_0.tar.bz2#beed054d4979cd70690aea2b257a6d55 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py39h68ae834_0.tar.bz2#e871ee7de5bfa95095256e95e30be2a6 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 @@ -232,25 +232,25 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py39h19d6b11_2.tar.bz2#dc400bb297d8425b8b05367a21854b0b -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h71b8e10_101.tar.bz2#91e01aa93a2bcca96c9d64d2ce4f65f0 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_2.tar.bz2#12964abb0bdcb4abb3c680b359560c1b +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39hf5d525c_0.tar.bz2#b99ba7383d1c9dd18445dfff08439c48 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py39hf3d152e_2.tar.bz2#98bf9bdfbac2ac73bbd1dc12a61519eb -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 8f730729b7..76ca9e4f58 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -11,12 +11,12 @@ dependencies: - setuptools-scm >=7 # Core dependencies. - - cartopy >=0.20 + - cartopy >=0.21 - cf-units >=3.1 - cftime >=1.5 - dask-core >=2.26 - matplotlib - - netcdf4 + - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index d92a68076c..5a8c878ee1 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -11,12 +11,12 @@ dependencies: - setuptools-scm >=7 # Core dependencies. - - cartopy >=0.20 + - cartopy >=0.21 - cf-units >=3.1 - cftime >=1.5 - dask-core >=2.26 - matplotlib - - netcdf4 + - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 001d3565d5..7931e20336 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -11,12 +11,12 @@ dependencies: - setuptools-scm >=7 # Core dependencies. - - cartopy >=0.20 + - cartopy >=0.21 - cf-units >=3.1 - cftime >=1.5 - dask-core >=2.26 - matplotlib - - netcdf4 + - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/setup.cfg b/setup.cfg index e5f0bc5b46..92cbe4747c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,12 +47,12 @@ version = attr: iris.__version__ [options] include_package_data = True install_requires = - cartopy>=0.20 + cartopy>=0.21 cf-units>=3.1 cftime>=1.5.0 dask[array]>=2.26 matplotlib - netcdf4 + netcdf4!=1.6.1 numpy>=1.19 scipy shapely!=1.8.3 From 0dea89741f6fbd979380312fec02f4ad916ec875 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:51:03 +0100 Subject: [PATCH 227/319] Updated environment lockfiles (#4971) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 11 ++++++----- requirements/ci/nox.lock/py38-linux-64.lock | 11 ++++++----- requirements/ci/nox.lock/py39-linux-64.lock | 11 ++++++----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 5a18284896..4d1b238179 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 9d180dce74d2d2af2a3e377c05d03d2a00c603b6d46a9f39b9c92bfbc71e7d3e +# input_hash: 9bcbc5c76124fc238f88ac16184aebeb8fac11fe9d4df03e70a7f50e2d24aa9f @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c @@ -76,7 +76,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2. https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -206,8 +206,9 @@ https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_0.tar.bz2#e572565848d8d19e74983f4d122734a8 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py310hbf28c38_0.tar.bz2#85565efb2bf44e8a5782e7c418d30cfe https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.0-pyhd8ed1ab_0.tar.bz2#859607d7a80dc90039b3d94cfa2b1931 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py310h5764c6d_0.tar.bz2#4a195cef7a7649b3c44efff3853e162c https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_1.tar.bz2#34c6484852afee76e334c1f593dd8282 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc @@ -230,7 +231,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.t https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py310h8d5ebf3_2.tar.bz2#760bc53cc184c9d6eeca9a38099e5fa8 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py310h8d5ebf3_0.tar.bz2#001fdef689e7cbcbbce6d5a6ebee90b6 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 @@ -250,7 +251,7 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c8 https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_2.tar.bz2#46fb1538bf92de6d807feb81b462aa0f +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py310hff52083_0.tar.bz2#2db9d22cc226ef79d9cd87fc958c2b04 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index f916efefc8..a485b37ede 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 22508264922e791fc7a682c11aec4c066a925d74d292b7f7d868e2efd0023bdf +# input_hash: 34099f3b69d60b791c26fcde2961739ff7cb0f9c144a37335b9f2183abe0dda3 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c @@ -75,7 +75,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2. https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -210,8 +210,9 @@ https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_0.tar.bz2#b2247bb2492e261c25fabbbb2c7a23b5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py38h43d8883_0.tar.bz2#0650a251fd701bbe5ac44e74cf632af8 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.0-pyhd8ed1ab_0.tar.bz2#859607d7a80dc90039b3d94cfa2b1931 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py38h0a891b7_0.tar.bz2#5be9368f14f462705b69da737b11159d https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -234,7 +235,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py38h38b5ce0_2.tar.bz2#0db5b110946be87a04643c1ba95c6ef9 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py38hb021067_0.tar.bz2#315ee5c0fbee508e739ddfac2bf8f600 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 @@ -248,7 +249,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py38h578d9bd_2.tar.bz2#3b6f187bade8a47d05c8a74c6385a900 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py38h578d9bd_0.tar.bz2#602eb908e81892115c1405c9d99abd56 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index e0faf9028b..ecab388f39 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 1e66fc8b152ee34808f52142e576752894895cd3d1c29a3a408d42ee4062ba51 +# input_hash: 44cf413042165b62fe105f738e80b926629f61c1763d74df419910081521225b @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c @@ -76,7 +76,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2. https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -211,8 +211,9 @@ https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_0.tar.bz2#165e71a44187ac22e2e1669fd3ca2392 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py39hf939315_0.tar.bz2#c9ff0dfb602033b1f1aaf323b58e04fa https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.0-pyhd8ed1ab_0.tar.bz2#859607d7a80dc90039b3d94cfa2b1931 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py39hb9d737c_0.tar.bz2#b47e528ae5d9e0a786896a21aa0b0307 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -235,7 +236,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py39h19d6b11_2.tar.bz2#dc400bb297d8425b8b05367a21854b0b +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py39hf9fd14e_0.tar.bz2#bdc55b4069ab9d2f938525c4cf90def0 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc @@ -249,7 +250,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py39hf3d152e_2.tar.bz2#98bf9bdfbac2ac73bbd1dc12a61519eb +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py39hf3d152e_0.tar.bz2#93f29e4d6f852de18384412b0e0d03b5 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba From 46755facbffc025c16e8932633f42f6c18c3ea5d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:47:03 +0100 Subject: [PATCH 228/319] Bump peter-evans/create-pull-request from 4.1.1 to 4.1.2 (#4974) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/18f90432bedd2afd6a825469ffd38aa24712a91d...171dd555b9ab6b18fa02519fdfacbb8bf671e1b4) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 95b65acb8b..0f08743421 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -91,7 +91,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@18f90432bedd2afd6a825469ffd38aa24712a91d + uses: peter-evans/create-pull-request@171dd555b9ab6b18fa02519fdfacbb8bf671e1b4 with: token: ${{ steps.generate-token.outputs.token }} commit-message: Updated environment lockfiles From d18b84c021ecb87d87700565bd47629114523c02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Sep 2022 11:05:41 +0100 Subject: [PATCH 229/319] Bump actions/stale from 5 to 6 (#4977) Bumps [actions/stale](https://github.com/actions/stale) from 5 to 6. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 008fe56deb..c65f37284f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -14,7 +14,7 @@ jobs: if: "github.repository == 'SciTools/iris'" runs-on: ubuntu-latest steps: - - uses: actions/stale@v5 + - uses: actions/stale@v6 with: repo-token: ${{ secrets.GITHUB_TOKEN }} From a131d86d4bb18fc1b484f380259f6af5efe959da Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Thu, 22 Sep 2022 15:31:52 +0100 Subject: [PATCH 230/319] Update release guide (#4963) * update release guide * adress review comments --- docs/src/developers_guide/release.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index 25a426e20b..37a17ed791 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -224,17 +224,14 @@ These steps assume a release for ``1.9.0`` is to be created. Release Steps ~~~~~~~~~~~~~ -#. Create the release feature branch ``v1.9.x`` on `SciTools/iris`_. - The only exception is for a point/bugfix release, as it should already exist -#. Update the ``iris.__init__.py`` version string e.g., to ``1.9.0`` #. Update the ``whatsnew`` for the release: * Use ``git`` to rename ``docs/src/whatsnew/latest.rst`` to the release version file ``v1.9.rst`` - * Update ``docs/src/whatsnews/index.rst`` to rename ``latest.rst`` in the - include statement and toctree. * Use ``git`` to delete the ``docs/src/whatsnew/latest.rst.template`` file * In ``v1.9.rst`` remove the ``[unreleased]`` caption from the page title. + Replace this with ``[release candidate]`` for the release candidate and + remove this for the actual release. Note that, the Iris version and release date are updated automatically when the documentation is built * Review the file for correctness @@ -253,6 +250,9 @@ Release Steps #. Once all the above steps are complete, the release is cut, using the :guilabel:`Draft a new release` button on the `Iris release page `_ + and targeting the release branch if it exists +#. Create the release feature branch ``v1.9.x`` on `SciTools/iris`_ if it doesn't + already exist. For point/bugfix releases use the branch which already exists Post Release Steps @@ -260,17 +260,18 @@ Post Release Steps #. Check the documentation has built on `Read The Docs`_. The build is triggered by any commit to ``main``. Additionally check that the versions - available in the pop out menu in the bottom left corner include the new + available in the pop out menu in the bottom right corner include the new release version. If it is not present you will need to configure the versions available in the **admin** dashboard in `Read The Docs`_. #. Review the `Active Versions`_ for the ``scitools-iris`` project on `Read The Docs`_ to ensure that the appropriate versions are ``Active`` and/or ``Hidden``. To do this ``Edit`` the appropriate version e.g., see `Editing v3.0.0rc0`_ (must be logged into Read the Docs). -#. Make a new ``latest.rst`` from ``latest.rst.template`` and update the include - statement and the toctree in ``index.rst`` to point at the new +#. Merge back to ``main``. This should be done after all releases, including + the release candidate, and also after major changes to the release branch. +#. On main, make a new ``latest.rst`` from ``latest.rst.template`` and update + the include statement and the toctree in ``index.rst`` to point at the new ``latest.rst``. -#. Merge back to ``main`` .. _SciTools/iris: https://github.com/SciTools/iris @@ -285,4 +286,4 @@ Post Release Steps .. _Generating Distribution Archives: https://packaging.python.org/tutorials/packaging-projects/#generating-distribution-archives .. _Packaging Your Project: https://packaging.python.org/guides/distributing-packages-using-setuptools/#packaging-your-project .. _latest CF standard names: http://cfconventions.org/standard-names.html -.. _setuptools-scm: https://github.com/pypa/setuptools_scm \ No newline at end of file +.. _setuptools-scm: https://github.com/pypa/setuptools_scm From 831816e73fb6edb76c43df16a6e9c0e0e18f3bcf Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Fri, 23 Sep 2022 11:12:00 +0100 Subject: [PATCH 231/319] Add short entry to release guide about what to do with the whats new for point releases. (#4980) --- docs/src/developers_guide/release.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index 37a17ed791..de7aa6c719 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -209,6 +209,11 @@ branch, and then released by tagging ``v1.9.1``. New features shall not be included in a point release, these are for bug fixes. +``whatsnew`` entries should be added to the existing +``docs/src/whatsnew/v1.9.rst`` file in a new ``v1.9.1`` section. A template for +this bugfix patches section can be found in the +``docs/src/whatsnew/latest.rst.template`` file. + A point release does not require a release candidate, but the rest of the release process is to be followed, including the merge back of changes into ``main``. From 1d6d9796f988f44b87e53fbdf290530de2369538 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Fri, 23 Sep 2022 15:17:14 +0100 Subject: [PATCH 232/319] Reinstate CubeList._repr_html_() (#4976) * Test the calls implementing Cube and Cubelist printing, and repr_html(). * Reinstate CubeList._repr_html_() method. * Review changes. * Add whatsnew. * Typo in whatsnew. * Update docs/src/whatsnew/3.3.rst Co-authored-by: lbdreyer Co-authored-by: lbdreyer --- docs/src/whatsnew/3.3.rst | 15 ++ docs/src/whatsnew/latest.rst.template | 112 +++++++++++++ lib/iris/cube.py | 6 + lib/iris/tests/unit/cube/test_Cube.py | 188 +++++++++++++++++----- lib/iris/tests/unit/cube/test_CubeList.py | 31 ++++ 5 files changed, 314 insertions(+), 38 deletions(-) create mode 100644 docs/src/whatsnew/latest.rst.template diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index 5812b79860..3670b96ff0 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -31,6 +31,21 @@ This document explains the changes made to Iris for this release any issues or feature requests for improving Iris. Enjoy! +v3.3.1 |build_date| [unreleased] +================================ + +.. dropdown:: :opticon:`alert` v3.3.1 Patches + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + + The patches in this release of Iris include: + +#. `@pp-mo`_ fixed the Jupyter notebook display of :class:`~iris.cube.CubeList`. + (:issue:`4973`, :pull:`4976`) + + 📢 Announcements ================ diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template new file mode 100644 index 0000000000..661ee47f50 --- /dev/null +++ b/docs/src/whatsnew/latest.rst.template @@ -0,0 +1,112 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`report` |iris_version| Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this major/minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +NOTE: section below is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + +v3.X.X (DD MMM YYYY) +==================== + +.. dropdown:: :opticon:`alert` v3.X.X Patches + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + + The patches in this release of Iris include: + + #. N/A + +NOTE: section above is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. N/A + + +🐛 Bugs Fixed +============= + +#. N/A + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. N/A + + +📚 Documentation +================ + +#. N/A + + +💼 Internal +=========== + +#. N/A + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: + + diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 8879ade621..9e49b26ac5 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -185,6 +185,12 @@ def _assert_is_cube(obj): ) raise ValueError(msg) + def _repr_html_(self): + from iris.experimental.representation import CubeListRepresentation + + representer = CubeListRepresentation(self) + return representer.repr_html() + # TODO #370 Which operators need overloads? def __add__(self, other): diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 944d216a30..26ab4afd11 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -2876,64 +2876,176 @@ def test_cell_method_correct_order(self): self.assertTrue(cube1 == cube2) +@pytest.fixture +def simplecube(): + return stock.simple_2d_w_cell_measure_ancil_var() + + class Test__dimensional_metadata: - @pytest.fixture - def cube(self): - return stock.simple_2d_w_cell_measure_ancil_var() + """ + Tests for the "Cube._dimensional_data" method. - def test_not_found(self, cube): + NOTE: test could all be static methods, but that adds a line to each definition. + """ + + def test_not_found(self, simplecube): with pytest.raises(KeyError, match="was not found in"): - cube._dimensional_metadata("grid_latitude") + simplecube._dimensional_metadata("grid_latitude") - def test_dim_coord_name_found(self, cube): - res = cube._dimensional_metadata("bar") - assert res == cube.coord("bar") + def test_dim_coord_name_found(self, simplecube): + res = simplecube._dimensional_metadata("bar") + assert res == simplecube.coord("bar") - def test_dim_coord_instance_found(self, cube): - res = cube._dimensional_metadata(cube.coord("bar")) - assert res == cube.coord("bar") + def test_dim_coord_instance_found(self, simplecube): + res = simplecube._dimensional_metadata(simplecube.coord("bar")) + assert res == simplecube.coord("bar") - def test_aux_coord_name_found(self, cube): - res = cube._dimensional_metadata("wibble") - assert res == cube.coord("wibble") + def test_aux_coord_name_found(self, simplecube): + res = simplecube._dimensional_metadata("wibble") + assert res == simplecube.coord("wibble") - def test_aux_coord_instance_found(self, cube): - res = cube._dimensional_metadata(cube.coord("wibble")) - assert res == cube.coord("wibble") + def test_aux_coord_instance_found(self, simplecube): + res = simplecube._dimensional_metadata(simplecube.coord("wibble")) + assert res == simplecube.coord("wibble") - def test_cell_measure_name_found(self, cube): - res = cube._dimensional_metadata("cell_area") - assert res == cube.cell_measure("cell_area") + def test_cell_measure_name_found(self, simplecube): + res = simplecube._dimensional_metadata("cell_area") + assert res == simplecube.cell_measure("cell_area") - def test_cell_measure_instance_found(self, cube): - res = cube._dimensional_metadata(cube.cell_measure("cell_area")) - assert res == cube.cell_measure("cell_area") + def test_cell_measure_instance_found(self, simplecube): + res = simplecube._dimensional_metadata( + simplecube.cell_measure("cell_area") + ) + assert res == simplecube.cell_measure("cell_area") - def test_ancillary_var_name_found(self, cube): - res = cube._dimensional_metadata("quality_flag") - assert res == cube.ancillary_variable("quality_flag") + def test_ancillary_var_name_found(self, simplecube): + res = simplecube._dimensional_metadata("quality_flag") + assert res == simplecube.ancillary_variable("quality_flag") - def test_ancillary_var_instance_found(self, cube): - res = cube._dimensional_metadata( - cube.ancillary_variable("quality_flag") + def test_ancillary_var_instance_found(self, simplecube): + res = simplecube._dimensional_metadata( + simplecube.ancillary_variable("quality_flag") ) - assert res == cube.ancillary_variable("quality_flag") + assert res == simplecube.ancillary_variable("quality_flag") - def test_two_with_same_name(self, cube): + def test_two_with_same_name(self, simplecube): # If a cube has two _DimensionalMetadata objects with the same name, the # current behaviour results in _dimensional_metadata returning the first # one it finds. - cube.cell_measure("cell_area").rename("wibble") - res = cube._dimensional_metadata("wibble") - assert res == cube.coord("wibble") + simplecube.cell_measure("cell_area").rename("wibble") + res = simplecube._dimensional_metadata("wibble") + assert res == simplecube.coord("wibble") - def test_two_with_same_name_specify_instance(self, cube): + def test_two_with_same_name_specify_instance(self, simplecube): # The cube has two _DimensionalMetadata objects with the same name so # we specify the _DimensionalMetadata instance to ensure it returns the # correct one. - cube.cell_measure("cell_area").rename("wibble") - res = cube._dimensional_metadata(cube.cell_measure("wibble")) - assert res == cube.cell_measure("wibble") + simplecube.cell_measure("cell_area").rename("wibble") + res = simplecube._dimensional_metadata( + simplecube.cell_measure("wibble") + ) + assert res == simplecube.cell_measure("wibble") + + +class TestReprs: + """ + Confirm that str(cube), repr(cube) and cube.summary() work by creating a fresh + :class:`iris._representation.cube_printout.CubePrinter` object, and using it + in the expected ways. + + Notes + ----- + This only tests code connectivity. The functionality is tested elsewhere, in + `iris.tests.unit._representation.cube_printout.test_CubePrintout`. + """ + + # Note: logically this could be a staticmethod, but that seems to upset Pytest + @pytest.fixture + def patched_cubeprinter(self): + target = "iris._representation.cube_printout.CubePrinter" + instance_mock = mock.MagicMock( + to_string=mock.MagicMock( + return_value="" + ) # NB this must return a string + ) + with mock.patch(target, return_value=instance_mock) as class_mock: + yield class_mock, instance_mock + + @staticmethod + def _check_expected_effects( + simplecube, patched_cubeprinter, oneline, padding + ): + class_mock, instance_mock = patched_cubeprinter + assert class_mock.call_args_list == [ + # "CubePrinter()" was called exactly once, with the cube as arg + mock.call(simplecube) + ] + assert instance_mock.to_string.call_args_list == [ + # "CubePrinter(cube).to_string()" was called exactly once, with these args + mock.call(oneline=oneline, name_padding=padding) + ] + + def test_str_effects(self, simplecube, patched_cubeprinter): + str(simplecube) + self._check_expected_effects( + simplecube, patched_cubeprinter, oneline=False, padding=35 + ) + + def test_repr_effects(self, simplecube, patched_cubeprinter): + repr(simplecube) + self._check_expected_effects( + simplecube, patched_cubeprinter, oneline=True, padding=1 + ) + + def test_summary_effects(self, simplecube, patched_cubeprinter): + simplecube.summary( + shorten=mock.sentinel.oneliner, name_padding=mock.sentinel.padding + ) + self._check_expected_effects( + simplecube, + patched_cubeprinter, + oneline=mock.sentinel.oneliner, + padding=mock.sentinel.padding, + ) + + +class TestHtmlRepr: + """ + Confirm that Cube._repr_html_() creates a fresh + :class:`iris.experimental.representation.CubeRepresentation` object, and uses it + in the expected way. + + Notes + ----- + This only tests code connectivity. The functionality is tested elsewhere, in + `iris.tests.unit.experimental.representation.test_CubeRepresentation`. + """ + + # Note: logically this could be a staticmethod, but that seems to upset Pytest + @pytest.fixture + def patched_cubehtml(self): + target = "iris.experimental.representation.CubeRepresentation" + instance_mock = mock.MagicMock( + repr_html=mock.MagicMock( + return_value="" + ) # NB this must return a string + ) + with mock.patch(target, return_value=instance_mock) as class_mock: + yield class_mock, instance_mock + + @staticmethod + def test__repr_html__effects(simplecube, patched_cubehtml): + simplecube._repr_html_() + + class_mock, instance_mock = patched_cubehtml + assert class_mock.call_args_list == [ + # "CubeRepresentation()" was called exactly once, with the cube as arg + mock.call(simplecube) + ] + assert instance_mock.repr_html.call_args_list == [ + # "CubeRepresentation(cube).repr_html()" was called exactly once, with no args + mock.call() + ] if __name__ == "__main__": diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py index 1ebfe57773..86457d3888 100644 --- a/lib/iris/tests/unit/cube/test_CubeList.py +++ b/lib/iris/tests/unit/cube/test_CubeList.py @@ -735,5 +735,36 @@ def test_copy(self): self.assertIsInstance(self.copied_cube_list, iris.cube.CubeList) +class TestHtmlRepr: + """ + Confirm that Cubelist._repr_html_() creates a fresh + :class:`iris.experimental.representation.CubeListRepresentation` object, and uses + it in the expected way. + + Notes + ----- + This only tests code connectivity. The functionality is tested elsewhere, at + `iris.tests.unit.experimental.representation.test_CubeListRepresentation` + """ + + @staticmethod + def test__repr_html_(): + test_cubelist = CubeList([]) + + target = "iris.experimental.representation.CubeListRepresentation" + with mock.patch(target) as class_mock: + # Exercise the function-under-test. + test_cubelist._repr_html_() + + assert class_mock.call_args_list == [ + # "CubeListRepresentation()" was called exactly once, with the cubelist as arg + mock.call(test_cubelist) + ] + assert class_mock.return_value.repr_html.call_args_list == [ + # "CubeListRepresentation(cubelist).repr_html()" was called exactly once, with no args + mock.call() + ] + + if __name__ == "__main__": tests.main() From 04e592006dbdc27d647782e151a2b36847b1311d Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 08:55:50 +0100 Subject: [PATCH 233/319] Updated environment lockfiles (#4991) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 52 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 44 ++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 44 ++++++++--------- 3 files changed, 70 insertions(+), 70 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 4d1b238179..b73b8af3da 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -23,10 +23,11 @@ https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar. https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -66,7 +67,6 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 @@ -74,6 +74,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.ta https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c @@ -91,14 +93,17 @@ https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 @@ -110,7 +115,6 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bd https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 @@ -119,25 +123,23 @@ https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1a https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 @@ -174,14 +176,13 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1013.tar.bz2#148e1893454972ac8c595c98c7b8ed5c +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 @@ -196,6 +197,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py310h5764c6d_0.tar.bz2#6ac13c26fe4f9d8d6b38657664c37fd3 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a @@ -205,18 +207,17 @@ https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_0.tar.bz2#e572565848d8d19e74983f4d122734a8 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_0.tar.bz2#6290f1bc763ed75a42aaea29384f9858 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py310hbf28c38_0.tar.bz2#85565efb2bf44e8a5782e7c418d30cfe https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py310h5764c6d_0.tar.bz2#4a195cef7a7649b3c44efff3853e162c -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_1.tar.bz2#34c6484852afee76e334c1f593dd8282 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py310h5764c6d_0.tar.bz2#e12fa8a9fee03765d98a93234ef5a901 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.4-py310h769672d_0.tar.bz2#e04e98ab8bb134d7799fdac51b35e923 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py310h769672d_0.tar.bz2#06efc4b5f4b418b78de14d1db4a65cad https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_1.tar.bz2#0ad6207e9d553c67984a5b0b06bbd2a3 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py310hff52083_0.tar.bz2#18ef27d620d67af2feef22acfd42cf4a https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 @@ -228,30 +229,29 @@ https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py310h8d5ebf3_0.tar.bz2#001fdef689e7cbcbbce6d5a6ebee90b6 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_0.tar.bz2#3e81d6afa50895d6dee115ac5d34c2ea https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py310hff52083_0.tar.bz2#2db9d22cc226ef79d9cd87fc958c2b04 +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index a485b37ede..096162793f 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -22,10 +22,11 @@ https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar. https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -65,7 +66,6 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 @@ -73,6 +73,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.ta https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c @@ -90,14 +92,17 @@ https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 @@ -107,19 +112,15 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 @@ -129,7 +130,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.ta https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1013.tar.bz2#148e1893454972ac8c595c98c7b8ed5c +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e @@ -143,17 +144,17 @@ https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc @@ -182,8 +183,8 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_1.tar.bz2#34c6484852afee76e334c1f593dd8282 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 @@ -198,7 +199,6 @@ https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py38h0a891b7_0.tar.bz2#907a39b6d7443f770ed755885694f864 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hd7890fc_1.tar.bz2#f851bb08c85122fd0e1f66d2072ebf0b https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 @@ -209,17 +209,17 @@ https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_0.tar.bz2#b2247bb2492e261c25fabbbb2c7a23b5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_0.tar.bz2#df081ec90a13f53fe522c8e876d3f0cf https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py38h43d8883_0.tar.bz2#0650a251fd701bbe5ac44e74cf632af8 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py38h0a891b7_0.tar.bz2#5be9368f14f462705b69da737b11159d -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py38h0a891b7_0.tar.bz2#ff4c112a78161241ca8a7af74de6a50b +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.4-py38h47df419_0.tar.bz2#2d32bac7cbadc044a7008452adee8c4e -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py38h8f669ce_0.tar.bz2#f91da48c62c91659da28bd95559c75ff +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py38h578d9bd_0.tar.bz2#1fdabff56623511910fef3b418ff07a2 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe @@ -237,7 +237,7 @@ https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py38hb021067_0.tar.bz2#315ee5c0fbee508e739ddfac2bf8f600 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index ecab388f39..9d454a2569 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -23,10 +23,11 @@ https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar. https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -66,7 +67,6 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 @@ -74,6 +74,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.ta https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c @@ -91,14 +93,17 @@ https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 @@ -108,19 +113,15 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 @@ -130,7 +131,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.ta https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1013.tar.bz2#148e1893454972ac8c595c98c7b8ed5c +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e @@ -144,17 +145,17 @@ https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc @@ -183,8 +184,8 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_2.tar.bz2#fea5dea40592ea943aa56f4935308ee4 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_1.tar.bz2#34c6484852afee76e334c1f593dd8282 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 @@ -199,7 +200,6 @@ https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py39hb9d737c_0.tar.bz2#1e7ffe59e21862559e06b981817e5058 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h2c22827_1.tar.bz2#a1ca42c2a746601d42f27bbcb7f6acfc https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 @@ -210,17 +210,17 @@ https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_0.tar.bz2#165e71a44187ac22e2e1669fd3ca2392 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_0.tar.bz2#4b108127973b66b36edd6449aa6afde0 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py39hf939315_0.tar.bz2#c9ff0dfb602033b1f1aaf323b58e04fa https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.2-py39hb9d737c_0.tar.bz2#b47e528ae5d9e0a786896a21aa0b0307 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_1.tar.bz2#4078c60fba5867ce6349b90c387170a2 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py39hb9d737c_0.tar.bz2#21622fe576fcce5b861036e8d7282470 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.4-py39h1832856_0.tar.bz2#6ef85649798519bd47dfc24e399b8dcd -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py39h4661b88_0.tar.bz2#ae807099430cd22b09b869b0536425b7 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py39hf3d152e_0.tar.bz2#b807481ba94ec32bc742f2fe775d0bff https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 @@ -238,7 +238,7 @@ https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py39hf9fd14e_0.tar.bz2#bdc55b4069ab9d2f938525c4cf90def0 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 From d38c50420fc2a0219cb36df91fb57fc2ee59ca4d Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Mon, 26 Sep 2022 14:50:52 +0100 Subject: [PATCH 234/319] Fix name loader problem (#4933) * Fix name loader problem; enforce expected type of all cube cell_methods. * Review change: fix import order. * Update imagerepo.json for skipped changes from test-iris-imagehash v0.16. --- .github/workflows/ci-tests.yml | 2 +- docs/src/whatsnew/3.3.rst | 11 ++- lib/iris/cube.py | 21 ++++- lib/iris/fileformats/name_loaders.py | 6 +- lib/iris/tests/results/imagerepo.json | 1 + .../name/NAMEII_field__no_time_averaging.cml | 47 +++++++++++ .../NAMEII_field__no_time_averaging_0.cml | 47 +++++++++++ lib/iris/tests/test_name.py | 31 +++++++- lib/iris/tests/unit/cube/test_Cube.py | 79 +++++++++++++++++++ 9 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 lib/iris/tests/results/name/NAMEII_field__no_time_averaging.cml create mode 100644 lib/iris/tests/results/name/NAMEII_field__no_time_averaging_0.cml diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index aae1ce2970..270046164e 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -46,7 +46,7 @@ jobs: session: "tests" env: - IRIS_TEST_DATA_VERSION: "2.15" + IRIS_TEST_DATA_VERSION: "2.17" ENV_NAME: "ci-tests" steps: diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index 3670b96ff0..47674b7272 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -42,8 +42,15 @@ v3.3.1 |build_date| [unreleased] The patches in this release of Iris include: -#. `@pp-mo`_ fixed the Jupyter notebook display of :class:`~iris.cube.CubeList`. - (:issue:`4973`, :pull:`4976`) + #. `@pp-mo`_ fixed the Jupyter notebook display of :class:`~iris.cube.CubeList`. + (:issue:`4973`, :pull:`4976`) + + #. `@pp-mo`_ fixed a bug in NAME loaders where data with no associated statistic would + load as a cube with invalid cell-methods, which cannot be printed or saved to netcdf. + (:issue:`3288`, :pull:`4933`) + + #. `@pp-mo`_ ensured that :data:`iris.cube.Cube.cell_methods` must always be an iterable + of :class:`iris.coords.CellMethod` objects (:pull:`4933`). 📢 Announcements diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 9e49b26ac5..9779558506 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -2299,10 +2299,23 @@ def cell_methods(self): return self._metadata_manager.cell_methods @cell_methods.setter - def cell_methods(self, cell_methods): - self._metadata_manager.cell_methods = ( - tuple(cell_methods) if cell_methods else tuple() - ) + def cell_methods(self, cell_methods: Iterable): + if not cell_methods: + # For backwards compatibility: Empty or null value is equivalent to (). + cell_methods = () + else: + # Can supply any iterable, which is converted (copied) to a tuple. + cell_methods = tuple(cell_methods) + for cell_method in cell_methods: + # All contents should be CellMethods. Requiring class membership is + # somewhat non-Pythonic, but simple, and not a problem for now. + if not isinstance(cell_method, iris.coords.CellMethod): + msg = ( + f"Cube.cell_methods assigned value includes {cell_method}, " + "which is not an iris.coords.CellMethod." + ) + raise ValueError(msg) + self._metadata_manager.cell_methods = cell_methods def core_data(self): """ diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py index 3aaba3679e..d15a3717d0 100644 --- a/lib/iris/fileformats/name_loaders.py +++ b/lib/iris/fileformats/name_loaders.py @@ -571,7 +571,9 @@ def _generate_cubes( cube.attributes[key] = value if cell_methods is not None: - cube.add_cell_method(cell_methods[i]) + cell_method = cell_methods[i] + if cell_method is not None: + cube.add_cell_method(cell_method) yield cube @@ -610,7 +612,7 @@ def _build_cell_methods(av_or_ints, coord): cell_method = None msg = "Unknown {} statistic: {!r}. Unable to create cell method." warnings.warn(msg.format(coord, av_or_int)) - cell_methods.append(cell_method) + cell_methods.append(cell_method) # NOTE: this can be a None return cell_methods diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index 342428ca67..e5c2ad863a 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -34,6 +34,7 @@ "gallery_tests.test_plot_wind_barbs.0": "e9e161e996169316c1fe9e96c29e36739e13c07c3d61c07f39813929c07f3f01", "gallery_tests.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e03c06c3d61c07f3da139e1c07f3f01", "gallery_tests.test_plot_wind_speed.1": "e9e960e996169306c1ee9f96c29e36739653c06c3d61c07f39a139e1c07f3f01", + "gallery_tests.test_plot_zonal_means.0": "b45b3071c9a4c9a6c69c363cc327cbb3cb9634d8c9e63cf336738c6634d8c384", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e", "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f", diff --git a/lib/iris/tests/results/name/NAMEII_field__no_time_averaging.cml b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging.cml new file mode 100644 index 0000000000..9bc2c0d1ac --- /dev/null +++ b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging.cml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/results/name/NAMEII_field__no_time_averaging_0.cml b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging_0.cml new file mode 100644 index 0000000000..8d1ad620d0 --- /dev/null +++ b/lib/iris/tests/results/name/NAMEII_field__no_time_averaging_0.cml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/iris/tests/test_name.py b/lib/iris/tests/test_name.py index 2843673da8..b4e91bafd7 100644 --- a/lib/iris/tests/test_name.py +++ b/lib/iris/tests/test_name.py @@ -8,6 +8,9 @@ # import iris tests first so that some things can be initialised before # importing anything else import iris.tests as tests # isort:skip + +import tempfile + import iris @@ -39,7 +42,7 @@ def test_NAMEIII_version2(self): ) self.assertCMLApproxData(cubes, ("name", "NAMEIII_version2.cml")) - def test_NAMEII_trajectory(self): + def test_NAMEIII_trajectory(self): cubes = iris.load( tests.get_data_path(("NAME", "NAMEIII_trajectory.txt")) ) @@ -48,6 +51,32 @@ def test_NAMEII_trajectory(self): cubes, ("name", "NAMEIII_trajectory.cml"), checksum=False ) + def test_NAMEII__no_time_averaging(self): + cubes = iris.load( + tests.get_data_path(("NAME", "NAMEII_no_time_averaging.txt")) + ) + + # Also check that it saves without error. + # This was previously failing, see https://github.com/SciTools/iris/issues/3288 + with tempfile.TemporaryDirectory() as temp_dirpath: + iris.save(cubes, temp_dirpath + "/tmp.nc") + + self.assertCML( + cubes[0], + ( + "name", + "NAMEII_field__no_time_averaging_0.cml", + ), + ) + self.assertCML( + cubes, + ( + "name", + "NAMEII_field__no_time_averaging.cml", + ), + checksum=False, + ) + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 26ab4afd11..df94ad29f0 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -9,6 +9,7 @@ # importing anything else. import iris.tests as tests # isort:skip +from collections import namedtuple from itertools import permutations from unittest import mock @@ -3048,5 +3049,83 @@ def test__repr_html__effects(simplecube, patched_cubehtml): ] +class Test__cell_methods: + @pytest.fixture(autouse=True) + def cell_measures_testdata(self): + self.cube = Cube([0]) + self.cm = CellMethod("mean", "time", "6hr") + self.cm2 = CellMethod("max", "latitude", "4hr") + + def test_none(self): + assert self.cube.cell_methods == () + + def test_one(self): + cube = Cube([0], cell_methods=[self.cm]) + expected = (self.cm,) + assert expected == cube.cell_methods + + def test_empty_assigns(self): + testargs = [(), [], {}, 0, 0.0, False, None] + results = [] + for arg in testargs: + cube = self.cube.copy() + cube.cell_methods = arg # assign test object + results.append(cube.cell_methods) # capture what is read back + expected_results = [()] * len(testargs) + assert expected_results == results + + def test_single_assigns(self): + cms = (self.cm, self.cm2) + # Any type of iterable ought to work + # But N.B. *not* testing sets, as order is not stable + testargs = [cms, list(cms), {cm: 1 for cm in cms}] + results = [] + for arg in testargs: + cube = self.cube.copy() + cube.cell_methods = arg # assign test object + results.append(cube.cell_methods) # capture what is read back + expected_results = [cms] * len(testargs) + assert expected_results == results + + def test_fail_assign_noniterable(self): + test_object = object() + with pytest.raises(TypeError, match="not iterable"): + self.cube.cell_methods = test_object + + def test_fail_create_noniterable(self): + test_object = object() + with pytest.raises(TypeError, match="not iterable"): + Cube([0], cell_methods=test_object) + + def test_fail_assign_noncellmethod(self): + test_object = object() + with pytest.raises(ValueError, match="not an iris.coords.CellMethod"): + self.cube.cell_methods = (test_object,) + + def test_fail_create_noncellmethod(self): + test_object = object() + with pytest.raises(ValueError, match="not an iris.coords.CellMethod"): + Cube([0], cell_methods=[test_object]) + + def test_assign_derivedcellmethod(self): + class DerivedCellMethod(CellMethod): + pass + + test_object = DerivedCellMethod("mean", "time", "6hr") + cms = (test_object,) + self.cube.cell_methods = (test_object,) + assert cms == self.cube.cell_methods + + def test_fail_assign_duckcellmethod(self): + # Can't currently assign a "duck-typed" CellMethod replacement, since + # implementation requires class membership (boo!) + DuckCellMethod = namedtuple("DuckCellMethod", CellMethod._names) + test_object = DuckCellMethod( + *CellMethod._names + ) # fill props with value==name + with pytest.raises(ValueError, match="not an iris.coords.CellMethod"): + self.cube.cell_methods = (test_object,) + + if __name__ == "__main__": tests.main() From 2a41be222b00e3a3342582934b7d5a93e87ca116 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Mon, 26 Sep 2022 20:02:45 +0100 Subject: [PATCH 235/319] Port dependency fixes to `v3.3.x`. (#4992) * Lockfile updates (#4968) * Updated environment lockfiles * Adjustments for Cartopy v0.21.0 (SciTools/cartopy@fcb784d). * Cartopy >=0.21 pin. * What's New entry. * WIP try netCDF4 pin. * Try pip installing netcdf4==1.6.1 as requested by @ocefpaf. * Revert "Try pip installing netcdf4==1.6.1 as requested by @ocefpaf." This reverts commit ce9f890980e32d7af9ef88c747e81b05fd5dea09. * netcdf4!=1.6.1 * Align Conda YAML formatting. Co-authored-by: Lockfile bot * New lockfiles. * What's New entry. * What's New correction. Co-authored-by: Lockfile bot --- docs/src/whatsnew/3.3.rst | 10 ++ lib/iris/analysis/cartography.py | 4 +- .../analysis/cartography/test_rotate_winds.py | 25 ++-- requirements/ci/nox.lock/py310-linux-64.lock | 133 ++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 133 ++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 135 +++++++++--------- requirements/ci/py310.yml | 4 +- requirements/ci/py38.yml | 4 +- requirements/ci/py39.yml | 4 +- setup.cfg | 4 +- 10 files changed, 237 insertions(+), 219 deletions(-) diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index 47674b7272..49e222a494 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -52,6 +52,14 @@ v3.3.1 |build_date| [unreleased] #. `@pp-mo`_ ensured that :data:`iris.cube.Cube.cell_methods` must always be an iterable of :class:`iris.coords.CellMethod` objects (:pull:`4933`). + #. `@trexfeathers`_ advanced the Cartopy pin to ``>=0.21``, as Cartopy's + change to default Transverse Mercator projection affects an Iris test. + See `SciTools/cartopy@fcb784d`_ and `SciTools/cartopy@8860a81`_ for more + details. (:pull:`4992`) + + #. `@trexfeathers`_ introduced the ``netcdf4!=1.6.1`` pin to avoid a + problem with segfaults. (:pull:`4992`) + 📢 Announcements ================ @@ -361,3 +369,5 @@ v3.3.1 |build_date| [unreleased] .. _PyData Sphinx Theme: https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html .. _pytest: https://docs.pytest.org .. _setuptools-scm: https://github.com/pypa/setuptools_scm +.. _SciTools/cartopy@fcb784d: https://github.com/SciTools/cartopy/commit/fcb784daa65d95ed9a74b02ca292801c02bc4108 +.. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index 44129ff175..f38e48354d 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -1008,8 +1008,8 @@ def _transform_distance_vectors_tolerance_mask( u_one_t, v_zero_t = _transform_distance_vectors(ones, zeros, ds, dx2, dy2) u_zero_t, v_one_t = _transform_distance_vectors(zeros, ones, ds, dx2, dy2) # Squared magnitudes should be equal to one within acceptable tolerance. - # A value of atol=2e-3 is used, which corresponds to a change in magnitude - # of approximately 0.1%. + # A value of atol=2e-3 is used, which masks any magnitude changes >0.5% + # (approx percentage - based on experimenting). sqmag_1_0 = u_one_t**2 + v_zero_t**2 sqmag_0_1 = u_zero_t**2 + v_one_t**2 mask = np.logical_not( diff --git a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py index 7bd8fdb597..7952b3bb46 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py +++ b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py @@ -16,6 +16,7 @@ import cartopy.crs as ccrs import numpy as np import numpy.ma as ma +import pytest from iris.analysis.cartography import rotate_winds, unrotate_pole import iris.coord_systems @@ -410,7 +411,11 @@ def test_transposed(self): class TestMasking(tests.IrisTest): def test_rotated_to_osgb(self): # Rotated Pole data with large extent. - x = np.linspace(311.9, 391.1, 10) + # A 'correct' answer is not known for this test; it is therefore + # written as a 'benchmark' style test - a change in behaviour will + # cause a test failure, requiring developers to approve/reject the + # new behaviour. + x = np.linspace(221.9, 301.1, 10) y = np.linspace(-23.6, 24.8, 8) u, v = uv_cubes(x, y) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) @@ -422,14 +427,14 @@ def test_rotated_to_osgb(self): # Snapshot of mask with fixed tolerance of atol=2e-3 expected_mask = np.array( [ - [1, 1, 1, 0, 0, 0, 0, 0, 0, 1], - [1, 1, 1, 0, 0, 0, 0, 0, 0, 1], - [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], - [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], - [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1, 0, 0, 0, 1], + [0, 0, 0, 0, 1, 1, 1, 0, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], + [0, 0, 0, 0, 1, 1, 1, 0, 0, 0], + [0, 0, 0, 1, 1, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 1, 1, 0, 0], + [0, 1, 0, 0, 0, 0, 1, 1, 1, 0], ], np.bool_, ) @@ -443,7 +448,7 @@ def test_rotated_to_osgb(self): # Calculate percentage error (note there are no zero magnitudes # so we can divide safely). anom = 100.0 * np.abs(res_mag - expected_mag) / expected_mag - self.assertTrue(anom[~ut.data.mask].max() < 0.1) + assert anom[~ut.data.mask].max() == pytest.approx(0.3227935) def test_rotated_to_unrotated(self): # Suffiently accurate so that no mask is introduced. diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index b29cac464f..ae15d4f1c2 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e79be0c1a5de550c1c2e192396e0e5cfda2ba23a154e1c4531d893da416f8cee +# input_hash: 661b2aefa2ce2746dbc82fa542204349290bb7a1180eea0d9424f51ef38f3899 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -12,21 +12,22 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022d-h191b570_0.tar.bz2#456b5b1d99e7a9654b331bcd82e71042 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -35,25 +36,24 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -66,44 +66,45 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -113,30 +114,31 @@ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 @@ -148,8 +150,10 @@ https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b46 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -171,86 +175,83 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 -https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py310hff52083_0.tar.bz2#a5087d46181f812a662fbe20352961ee +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py310h53a5b5f_0.tar.bz2#8b3cfad14508018915e88612f5a963cd +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py310h53a5b5f_0.tar.bz2#0a60ccaed9ad236cc7463322fe742eb6 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310hbd86126_2.tar.bz2#443272de4234f6df4a78f50105edc741 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py310h5764c6d_0.tar.bz2#eb3be71bc11a51ff49b6a0af9968f0ed -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py310hff52083_5.tar.bz2#378f2260e871f3ea46c6fa58d9f05277 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py310h5764c6d_0.tar.bz2#6ac13c26fe4f9d8d6b38657664c37fd3 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py310hff52083_0.tar.bz2#fe1a6180b086ddfa214627f00d1c4b58 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py310hff52083_1.tar.bz2#a91c9f0499e0f0f5912098c3462014b9 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_0.tar.bz2#e572565848d8d19e74983f4d122734a8 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py310hde88566_0.tar.bz2#1f84cf065287d73aa0233d432d3a1ba9 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_0.tar.bz2#6290f1bc763ed75a42aaea29384f9858 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py310hbf28c38_0.tar.bz2#85565efb2bf44e8a5782e7c418d30cfe https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py310h5764c6d_0.tar.bz2#75969f26ba850c17f6ceab6434aa1b0d -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py310h5764c6d_0.tar.bz2#e12fa8a9fee03765d98a93234ef5a901 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py310h769672d_0.tar.bz2#e48c810453df0f03bb8fcdff5e1d9e9d -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py310hf94497c_1.tar.bz2#aaa559c22c09139a504796bd453fd535 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py310hff52083_0.tar.bz2#5d44c6ab93d445b6c433914753390e86 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py310h769672d_0.tar.bz2#06efc4b5f4b418b78de14d1db4a65cad +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_1.tar.bz2#0ad6207e9d553c67984a5b0b06bbd2a3 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py310hff52083_0.tar.bz2#18ef27d620d67af2feef22acfd42cf4a https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py310hdfbd76f_0.tar.bz2#e5d21b0cb4161a40221786f2f05b3903 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py310hdfbd76f_0.tar.bz2#bfb55d07ad9d15d2f2f8e59afcbcf578 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py310h5e49deb_0.tar.bz2#2f2c225d04e99ff99d6d3a86692ce968 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py310h5701ce4_1.tar.bz2#36adf7d1b16bee40fcad8bfce54baa85 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py310h8d5ebf3_0.tar.bz2#001fdef689e7cbcbbce6d5a6ebee90b6 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h9fd08d4_101.tar.bz2#0c7d82a8e4a32c1231036eb8530f31b2 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py310he7eef42_1.tar.bz2#a06eff87c5bbd8ece667e155854fec2b +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_0.tar.bz2#3e81d6afa50895d6dee115ac5d34c2ea https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py310hff52083_1.tar.bz2#7fa212f8ef62e0ec36f20448267ce95a +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py310hff52083_0.tar.bz2#2db9d22cc226ef79d9cd87fc958c2b04 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 1508ea0f55..a0c6a16c70 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 05da38480edd3a8765d02d305d525d5b0ffd45d84de4a2f81e21421d61cad72c +# input_hash: accba76141f130ac5ecec2b0497bb7643998124920fcdd4c6d635380d98104bb @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -18,14 +18,15 @@ https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.b https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -34,25 +35,24 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -65,43 +65,44 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -110,16 +111,16 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 @@ -129,36 +130,39 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.ta https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -177,52 +181,49 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py38h578d9bd_0.tar.bz2#1f4339b25d1030cfbf4ee0b06690bbce https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_2.tar.bz2#fccce86e5fc8183bf2658ac9bfc535b4 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py38h3a7f9d9_0.tar.bz2#a7579626c41b3975da213c0b53aefa29 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py38h3a7f9d9_0.tar.bz2#83ba913fc1174925d4e862eccb53db59 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38ha3b2c9c_2.tar.bz2#a077cc2bb9d854074b1cf4607252da7a +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py38h0a891b7_0.tar.bz2#e3908bd184030e7f4a3d837959ebf6d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py38h578d9bd_5.tar.bz2#11113c7e50bb81f30762fe8325f305e1 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py38h0a891b7_0.tar.bz2#907a39b6d7443f770ed755885694f864 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hd7890fc_1.tar.bz2#f851bb08c85122fd0e1f66d2072ebf0b https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py38h578d9bd_0.tar.bz2#1daef6b2dad47429ddf747ce04f4fd6e https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py38h578d9bd_1.tar.bz2#30765568a158c9457d577cc83f0e8307 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_0.tar.bz2#b2247bb2492e261c25fabbbb2c7a23b5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py38h71d37f0_0.tar.bz2#acf7ef1f057459e9e707142a4b92e481 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_0.tar.bz2#df081ec90a13f53fe522c8e876d3f0cf +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py38h43d8883_0.tar.bz2#0650a251fd701bbe5ac44e74cf632af8 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py38h0a891b7_0.tar.bz2#8e43961af78ba54e8a72b0d8ce80588a -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py38h0a891b7_0.tar.bz2#ff4c112a78161241ca8a7af74de6a50b +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py38h47df419_0.tar.bz2#91c5ac3f8f0e55a946be7b9ce489abfe -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py38he1635e7_1.tar.bz2#3907607e23c3e18202960fc4217baa0a -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py38h578d9bd_0.tar.bz2#626d2b8f96c8c3d20198e6bd84d1cfb7 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py38h8f669ce_0.tar.bz2#f91da48c62c91659da28bd95559c75ff +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py38h578d9bd_0.tar.bz2#1fdabff56623511910fef3b418ff07a2 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py38hea3f02b_0.tar.bz2#d19e23bb56b31d2504a0ff4d46b7aabc +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py38hea3f02b_0.tar.bz2#b232edb409c6a79e5921b3591c56b716 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py38h3b45516_0.tar.bz2#d8621497bcc7b369ef9cce25d5a58aeb https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 @@ -231,25 +232,25 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py38h826bfd8_1.tar.bz2#454fd59caae7913651f08e3856c4c2e9 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h32db9c8_101.tar.bz2#d1451d40c8204594cdcf156363128000 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py38hb021067_0.tar.bz2#315ee5c0fbee508e739ddfac2bf8f600 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py38h1816dc1_1.tar.bz2#9e07b83f7d5e70bb48d13938ff93faf9 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38h606536b_0.tar.bz2#38fc3704565e44fb9fcdfaded03eee76 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py38h578d9bd_1.tar.bz2#6b6a356f4ff4a8b26b74add4ba8c0c22 -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py38h578d9bd_0.tar.bz2#602eb908e81892115c1405c9d99abd56 +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index ef20dfd33b..4a5eecb4f2 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: babb8284ac9609a6063fb9a97f69a471814cdad98b7e619ba1c902891e884ee2 +# input_hash: 74897fb3a84b5ce31dd22bb2c2de5b25aac891b5398acc3e5773b55f8807565b @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.6.15-ha878542_0.tar.bz2#c320890f77fd1d617fa876e0982002c2 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -12,21 +12,22 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022d-h191b570_0.tar.bz2#456b5b1d99e7a9654b331bcd82e71042 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.6.1-h7f98852_0.tar.bz2#0347ce6a34f8b55b544b141432c6d4c7 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.8-h27087fc_0.tar.bz2#e1b07832504eeba765d648389cc387a9 -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_ha7695d1_103.tar.bz2#a56c5033619bdf56a22a1f0a0fd286aa +https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed @@ -35,25 +36,24 @@ https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.13-h166bdaf_0.tar.bz2#4b5bee2e957570197327d0b20a718891 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 -https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_2.tar.bz2#839776c4e967bc881c21da197127a3ae +https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_2.tar.bz2#8302381297332ea50532cf2c67961080 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -66,43 +66,44 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h73d1719_1008.tar.bz2#af49250eca8e139378f8ff0ae9e57251 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.64-ha37c62d_0.tar.bz2#5896fbd58d0376df8556a4aba1ce4f71 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.37-h753d276_4.tar.bz2#6b611734b73d639c084ac4be2fcd996a -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.2-h753d276_1.tar.bz2#90136dc0a305db4e1df24945d431457b +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_0.tar.bz2#9d3e24b1157af09abe5a2589119c7b1d -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h57a0ea0_5.tar.bz2#5469312a373f481c05c380897fd7c923 +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 +https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_2.tar.bz2#4533821485cde83ab12ff3d8bda83768 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h0e0dad5_3.tar.bz2#5627d42c13a9b117ae1701c6e195624f +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_0.tar.bz2#77f98ec0b224fd5ca8e7043e167efb83 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.2-h4ff8645_1.tar.bz2#2676ec698ce91567fca50654ac1b18ba +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -111,16 +112,16 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-h8e229c2_0.tar.bz2#f314f79031fec74adc9bff50fbaffd89 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_0.tar.bz2#908fc30f89e27817d835b45f865536d7 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 @@ -130,36 +131,39 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.ta https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1012.tar.bz2#9604a7c93dd37bcb6d6cc8d6b64223a4 +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.1.0-pyhd8ed1ab_0.tar.bz2#f7551a8a008dfad2b7ac9662dd124614 +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.7.1-pyhd8ed1ab_0.tar.bz2#984db277dfb9ea04a584aea39c6a34e4 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e -https://conda.anaconda.org/conda-forge/noarch/idna-3.3-pyhd8ed1ab_0.tar.bz2#40b50b8b030f5f2f22085c062ed013dd +https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1002.tar.bz2#7b3f287fcb7683f67b3d953b79f412ea https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.0.1-h93bde94_1.tar.bz2#8259528ea471b0963a91ce174f002e55 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 +https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -178,52 +182,49 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/certifi-2022.6.15-py39hf3d152e_0.tar.bz2#cf0efee4ef53a6d3ea4dce06ac360f14 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_0.tar.bz2#94cb81ffdce328f80c87ac9b01244632 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.1.0-hf9f4e7c_0.tar.bz2#7c1f73a8f7864a202b126d82e88ddffc +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_2.tar.bz2#fea5dea40592ea943aa56f4935308ee4 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_2.tar.bz2#b809706525f081610469169b671b2600 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.2-py39hba7629e_0.tar.bz2#25285f960f9c7f4e8ef56171af5e2a22 +https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py39hba7629e_0.tar.bz2#320e25179733ec4a2ecffcebc8abbc80 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hd5dbb17_2.tar.bz2#3b74a959f6a8008f5901de60b3572c09 +https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.1-py39hb9d737c_0.tar.bz2#5852c69cad74811dc3c95f9ab6a184ef -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h7f54b18_8.tar.bz2#f9dbcfbb942ec9a3c0249cb71da5c7d1 -https://conda.anaconda.org/conda-forge/linux-64/pysocks-1.7.1-py39hf3d152e_5.tar.bz2#d34b97a2386932b97c7cb80916a673e7 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py39hb9d737c_0.tar.bz2#1e7ffe59e21862559e06b981817e5058 +https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h2c22827_1.tar.bz2#a1ca42c2a746601d42f27bbcb7f6acfc https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/setuptools-65.2.0-py39hf3d152e_0.tar.bz2#1b778cca1524a5cc4240f277d97dae99 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.3-py39hf3d152e_1.tar.bz2#baa79a28aa08de404d9deae634b91e03 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_0.tar.bz2#165e71a44187ac22e2e1669fd3ca2392 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.1-py39hd257fcd_0.tar.bz2#0911339f31c5fa644c312e4b3af95ea5 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_0.tar.bz2#4b108127973b66b36edd6449aa6afde0 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py39hf939315_0.tar.bz2#c9ff0dfb602033b1f1aaf323b58e04fa https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.8.1-pyhd8ed1ab_0.tar.bz2#df5026dbf551bb992cdf247b08e11078 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.36.0-py39hb9d737c_0.tar.bz2#a93c3de7835e68d42f9203886b372a8b -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-hf6a322e_0.tar.bz2#6ea2ce6265c3207876ef2369b7479f08 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py39hb9d737c_0.tar.bz2#21622fe576fcce5b861036e8d7282470 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 -https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.4.3-py39h1832856_0.tar.bz2#74e00961703972cf33b44a6fca7c3d51 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.9-hc4f8a73_0.tar.bz2#b8e090dce29a036357552a009c770187 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.3.1-py39hdcf6798_1.tar.bz2#4edc329e5d60c4a1c1299cea60608d00 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.2-py39hf3d152e_0.tar.bz2#a6bcf633d12aabdfc4cb32a09ebc0f31 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py39h4661b88_0.tar.bz2#ae807099430cd22b09b869b0536425b7 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 +https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py39hf3d152e_0.tar.bz2#b807481ba94ec32bc742f2fe775d0bff https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.0-py39h8ba3f38_0.tar.bz2#b098a256777cb9e2605451f183c78768 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py39h8ba3f38_0.tar.bz2#beed054d4979cd70690aea2b257a6d55 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py39h68ae834_0.tar.bz2#e871ee7de5bfa95095256e95e30be2a6 https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 @@ -232,25 +233,25 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.3-pyhd8ed1ab_0.tar.bz2#682f05a8e4b047ce4bdcec9d69c12551 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.2.1-pyhd8ed1ab_0.tar.bz2#01cc8698b6e1a124dc4f585516c27643 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.5.3-py39h700656a_1.tar.bz2#43191b76750399349519f53feac83094 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h71b8e10_101.tar.bz2#91e01aa93a2bcca96c9d64d2ce4f65f0 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_0.tar.bz2#1d7e241dfaf5475e893d4b824bb71b44 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py39hf9fd14e_0.tar.bz2#bdc55b4069ab9d2f938525c4cf90def0 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.4-ha5833f6_2.tar.bz2#dd3aa6715b9e9efaf842febf18ce4261 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.20.3-py39hed214b2_1.tar.bz2#7264fa97520021d1a01f5be9741493c2 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39hf5d525c_0.tar.bz2#b99ba7383d1c9dd18445dfff08439c48 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf -https://conda.anaconda.org/conda-forge/linux-64/graphviz-5.0.1-h5abf519_0.tar.bz2#03f22ca50fcff4bbee39da0943ab8475 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.5.3-py39hf3d152e_1.tar.bz2#bed9a319c4a3ac57376f5d95ba923d78 -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_0.tar.bz2#70d6e72856de9551f83ae0f2de689a7a +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py39hf3d152e_0.tar.bz2#93f29e4d6f852de18384412b0e0d03b5 +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 16b8afb67a..98426d3fff 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -11,12 +11,12 @@ dependencies: - setuptools-scm >=7 # Core dependencies. - - cartopy >=0.20 + - cartopy >=0.21 - cf-units >=3.1 - cftime >=1.5 - dask-core >=2 - matplotlib - - netcdf4 + - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index faecbd63ce..0c49fd1094 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -11,12 +11,12 @@ dependencies: - setuptools-scm >=7 # Core dependencies. - - cartopy >=0.20 + - cartopy >=0.21 - cf-units >=3.1 - cftime >=1.5 - dask-core >=2 - matplotlib - - netcdf4 + - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index f26804e92b..42d47c0d8d 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -11,12 +11,12 @@ dependencies: - setuptools-scm >=7 # Core dependencies. - - cartopy >=0.20 + - cartopy >=0.21 - cf-units >=3.1 - cftime >=1.5 - dask-core >=2 - matplotlib - - netcdf4 + - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/setup.cfg b/setup.cfg index 672cfc24d2..ddbb245183 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,12 +45,12 @@ version = attr: iris.__version__ [options] include_package_data = True install_requires = - cartopy>=0.20 + cartopy>=0.21 cf-units>=3.1 cftime>=1.5.0 dask[array]>=2 matplotlib - netcdf4 + netcdf4!=1.6.1 numpy>=1.19 scipy shapely!=1.8.3 From e96a0536af4ce40b4cd385108ae824565236b6a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Sep 2022 09:01:35 +0100 Subject: [PATCH 236/319] Bump peter-evans/create-pull-request from 4.1.2 to 4.1.3 (#4995) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.1.2 to 4.1.3. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/171dd555b9ab6b18fa02519fdfacbb8bf671e1b4...671dc9c9e0c2d73f07fa45a3eb0220e1622f0c5f) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 0f08743421..a45cccfeab 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -91,7 +91,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@171dd555b9ab6b18fa02519fdfacbb8bf671e1b4 + uses: peter-evans/create-pull-request@671dc9c9e0c2d73f07fa45a3eb0220e1622f0c5f with: token: ${{ steps.generate-token.outputs.token }} commit-message: Updated environment lockfiles From d8f8dac7c69f765c75690ab540d8c961adbb921c Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Wed, 28 Sep 2022 14:02:26 +0100 Subject: [PATCH 237/319] Netcdf separate loadsave (#4803) * Split iris.fileformats.netcdf into separate load+save submodules. * Fix mutable default. * Fixes. * Fix iris.fileformats.netdf.__all__. * Fix tests. * Fix license header. * Move unit tests for sub-module APIs. * Review changes. * Add whatsnew; slightly improved module docstrings. --- docs/src/whatsnew/latest.rst | 4 + lib/iris/experimental/ugrid/load.py | 13 +- lib/iris/experimental/ugrid/save.py | 3 +- .../fileformats/_nc_load_rules/helpers.py | 2 +- lib/iris/fileformats/netcdf/__init__.py | 49 ++ lib/iris/fileformats/netcdf/loader.py | 594 ++++++++++++++++++ .../{netcdf.py => netcdf/saver.py} | 590 +---------------- .../nc_load_rules/actions/__init__.py | 6 +- .../fileformats/netcdf/loader/__init__.py | 6 + .../{ => loader}/test__get_cf_var_data.py | 2 +- .../{ => loader}/test__load_aux_factory.py | 2 +- .../netcdf/{ => loader}/test__load_cube.py | 6 +- ...__translate_constraints_to_var_callback.py | 4 +- .../unit/fileformats/netcdf/saver/__init__.py | 6 + .../test__FillValueMaskCheckAndStoreTarget.py | 2 +- .../unit/fileformats/netcdf/test_Saver.py | 2 +- .../unit/fileformats/netcdf/test_save.py | 10 +- 17 files changed, 702 insertions(+), 599 deletions(-) create mode 100644 lib/iris/fileformats/netcdf/__init__.py create mode 100644 lib/iris/fileformats/netcdf/loader.py rename lib/iris/fileformats/{netcdf.py => netcdf/saver.py} (83%) create mode 100644 lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py rename lib/iris/tests/unit/fileformats/netcdf/{ => loader}/test__get_cf_var_data.py (97%) rename lib/iris/tests/unit/fileformats/netcdf/{ => loader}/test__load_aux_factory.py (99%) rename lib/iris/tests/unit/fileformats/netcdf/{ => loader}/test__load_cube.py (96%) rename lib/iris/tests/unit/fileformats/netcdf/{ => loader}/test__translate_constraints_to_var_callback.py (97%) create mode 100644 lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py rename lib/iris/tests/unit/fileformats/netcdf/{ => saver}/test__FillValueMaskCheckAndStoreTarget.py (97%) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 73f7e60351..d2eadb17d6 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -110,6 +110,10 @@ This document explains the changes made to Iris for this release package tests, see `pypa/setuptools#1684`_. Also performed assorted ``setup.py`` script hygiene. (:pull:`4948`, :pull:`4949`, :pull:`4950`) +#. `@pp-mo`_ split the module :mod:`iris.fileformats.netcdf` into separate + :mod:`~iris.fileformats.netcdf.loader` and :mod:`~iris.fileformats.netcdf.saver` + submodules, just to make the code easier to handle. + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index 6c802e00d4..a522d91313 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -8,8 +8,7 @@ Extensions to Iris' NetCDF loading to allow the construction of :class:`~iris.experimental.ugrid.mesh.Mesh`\\ es from UGRID data in the file. -Eventual destination: :mod:`iris.fileformats.netcdf` (plan to split that module -into ``load`` and ``save`` in future). +Eventual destination: :mod:`iris.fileformats.netcdf`. """ from contextlib import contextmanager @@ -19,8 +18,8 @@ from ...config import get_logger from ...coords import AuxCoord -from ...fileformats import netcdf from ...fileformats._nc_load_rules.helpers import get_attr_units, get_names +from ...fileformats.netcdf import loader as nc_loader from ...io import decode_uri, expand_filespecs from ...util import guess_coord_axis from .cf import ( @@ -202,7 +201,7 @@ def load_meshes(uris, var_name=None): else: handling_format_spec = FORMAT_AGENT.get_spec(source, None) - if handling_format_spec.handler == netcdf.load_cubes: + if handling_format_spec.handler == nc_loader.load_cubes: valid_sources.append(source) else: message = f"Ignoring non-NetCDF file: {source}" @@ -239,7 +238,7 @@ def _build_aux_coord(coord_var, file_path): assert isinstance(coord_var, CFUGridAuxiliaryCoordinateVariable) attributes = {} attr_units = get_attr_units(coord_var, attributes) - points_data = netcdf._get_cf_var_data(coord_var, file_path) + points_data = nc_loader._get_cf_var_data(coord_var, file_path) # Bounds will not be loaded: # Bounds may be present, but the UGRID conventions state this would @@ -293,7 +292,7 @@ def _build_connectivity(connectivity_var, file_path, element_dims): assert isinstance(connectivity_var, CFUGridConnectivityVariable) attributes = {} attr_units = get_attr_units(connectivity_var, attributes) - indices_data = netcdf._get_cf_var_data(connectivity_var, file_path) + indices_data = nc_loader._get_cf_var_data(connectivity_var, file_path) cf_role = connectivity_var.cf_role start_index = connectivity_var.start_index @@ -462,7 +461,7 @@ def _build_mesh(cf, mesh_var, file_path): ) mesh_elements = filter(None, mesh_elements) for iris_object in mesh_elements: - netcdf._add_unused_attributes( + nc_loader._add_unused_attributes( iris_object, cf.cf_group[iris_object.var_name] ) diff --git a/lib/iris/experimental/ugrid/save.py b/lib/iris/experimental/ugrid/save.py index 8a5934b939..3c42137905 100644 --- a/lib/iris/experimental/ugrid/save.py +++ b/lib/iris/experimental/ugrid/save.py @@ -8,8 +8,7 @@ Extensions to Iris' NetCDF saving to allow :class:`~iris.experimental.ugrid.mesh.Mesh` saving in UGRID format. -Eventual destination: :mod:`iris.fileformats.netcdf` (plan to split that module -into ``load`` and ``save`` in future). +Eventual destination: :mod:`iris.fileformats.netcdf`. """ from collections.abc import Iterable diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index d50d3f324a..c075a659ac 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -31,9 +31,9 @@ import iris.fileformats.netcdf from iris.fileformats.netcdf import ( UnknownCellMethodWarning, - _get_cf_var_data, parse_cell_methods, ) +from iris.fileformats.netcdf.loader import _get_cf_var_data import iris.std_names import iris.util diff --git a/lib/iris/fileformats/netcdf/__init__.py b/lib/iris/fileformats/netcdf/__init__.py new file mode 100644 index 0000000000..505e173b0b --- /dev/null +++ b/lib/iris/fileformats/netcdf/__init__.py @@ -0,0 +1,49 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Module to support the loading and saving of NetCDF files, also using the CF conventions +for metadata interpretation. + +See : `NetCDF User's Guide `_ +and `netCDF4 python module `_. + +Also : `CF Conventions `_. + +""" +import iris.config + +# Note: *must* be done before importing from submodules, as they also use this ! +logger = iris.config.get_logger(__name__) + +from .loader import DEBUG, NetCDFDataProxy, load_cubes +from .saver import ( + CF_CONVENTIONS_VERSION, + MESH_ELEMENTS, + SPATIO_TEMPORAL_AXES, + CFNameCoordMap, + Saver, + UnknownCellMethodWarning, + parse_cell_methods, + save, +) + +# Export all public elements from the loader and saver submodules. +# NOTE: the separation is purely for neatness and developer convenience; from +# the user point of view, it is still all one module. +__all__ = ( + "CFNameCoordMap", + "CF_CONVENTIONS_VERSION", + "DEBUG", + "MESH_ELEMENTS", + "NetCDFDataProxy", + "SPATIO_TEMPORAL_AXES", + "Saver", + "UnknownCellMethodWarning", + "load_cubes", + "logger", + "parse_cell_methods", + "save", +) diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py new file mode 100644 index 0000000000..95f394c70d --- /dev/null +++ b/lib/iris/fileformats/netcdf/loader.py @@ -0,0 +1,594 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Module to support the loading of Iris cubes from NetCDF files, also using the CF +conventions for metadata interpretation. + +See : `NetCDF User's Guide `_ +and `netCDF4 python module `_. + +Also : `CF Conventions `_. + +""" +import warnings + +import netCDF4 +import numpy as np + +from iris._lazy_data import as_lazy_data +from iris.aux_factory import ( + AtmosphereSigmaFactory, + HybridHeightFactory, + HybridPressureFactory, + OceanSFactory, + OceanSg1Factory, + OceanSg2Factory, + OceanSigmaFactory, + OceanSigmaZFactory, +) +import iris.config +import iris.coord_systems +import iris.coords +import iris.exceptions +import iris.fileformats.cf +from iris.fileformats.netcdf.saver import _CF_ATTRS +import iris.io +import iris.util + +# Show actions activation statistics. +DEBUG = False + +# Get the logger : shared logger for all in 'iris.fileformats.netcdf'. +from . import logger + + +def _actions_engine(): + # Return an 'actions engine', which provides a pyke-rules-like interface to + # the core cf translation code. + # Deferred import to avoid circularity. + import iris.fileformats._nc_load_rules.engine as nc_actions_engine + + engine = nc_actions_engine.Engine() + return engine + + +class NetCDFDataProxy: + """A reference to the data payload of a single NetCDF file variable.""" + + __slots__ = ("shape", "dtype", "path", "variable_name", "fill_value") + + def __init__(self, shape, dtype, path, variable_name, fill_value): + self.shape = shape + self.dtype = dtype + self.path = path + self.variable_name = variable_name + self.fill_value = fill_value + + @property + def ndim(self): + return len(self.shape) + + def __getitem__(self, keys): + dataset = netCDF4.Dataset(self.path) + try: + variable = dataset.variables[self.variable_name] + # Get the NetCDF variable data and slice. + var = variable[keys] + finally: + dataset.close() + return np.asanyarray(var) + + def __repr__(self): + fmt = ( + "<{self.__class__.__name__} shape={self.shape}" + " dtype={self.dtype!r} path={self.path!r}" + " variable_name={self.variable_name!r}>" + ) + return fmt.format(self=self) + + def __getstate__(self): + return {attr: getattr(self, attr) for attr in self.__slots__} + + def __setstate__(self, state): + for key, value in state.items(): + setattr(self, key, value) + + +def _assert_case_specific_facts(engine, cf, cf_group): + # Initialise a data store for built cube elements. + # This is used to patch element attributes *not* setup by the actions + # process, after the actions code has run. + engine.cube_parts["coordinates"] = [] + engine.cube_parts["cell_measures"] = [] + engine.cube_parts["ancillary_variables"] = [] + + # Assert facts for CF coordinates. + for cf_name in cf_group.coordinates.keys(): + engine.add_case_specific_fact("coordinate", (cf_name,)) + + # Assert facts for CF auxiliary coordinates. + for cf_name in cf_group.auxiliary_coordinates.keys(): + engine.add_case_specific_fact("auxiliary_coordinate", (cf_name,)) + + # Assert facts for CF cell measures. + for cf_name in cf_group.cell_measures.keys(): + engine.add_case_specific_fact("cell_measure", (cf_name,)) + + # Assert facts for CF ancillary variables. + for cf_name in cf_group.ancillary_variables.keys(): + engine.add_case_specific_fact("ancillary_variable", (cf_name,)) + + # Assert facts for CF grid_mappings. + for cf_name in cf_group.grid_mappings.keys(): + engine.add_case_specific_fact("grid_mapping", (cf_name,)) + + # Assert facts for CF labels. + for cf_name in cf_group.labels.keys(): + engine.add_case_specific_fact("label", (cf_name,)) + + # Assert facts for CF formula terms associated with the cf_group + # of the CF data variable. + + # Collect varnames of formula-root variables as we go. + # NOTE: use dictionary keys as an 'OrderedSet' + # - see: https://stackoverflow.com/a/53657523/2615050 + # This is to ensure that we can handle the resulting facts in a definite + # order, as using a 'set' led to indeterminate results. + formula_root = {} + for cf_var in cf.cf_group.formula_terms.values(): + for cf_root, cf_term in cf_var.cf_terms_by_root.items(): + # Only assert this fact if the formula root variable is + # defined in the CF group of the CF data variable. + if cf_root in cf_group: + formula_root[cf_root] = True + engine.add_case_specific_fact( + "formula_term", + (cf_var.cf_name, cf_root, cf_term), + ) + + for cf_root in formula_root.keys(): + engine.add_case_specific_fact("formula_root", (cf_root,)) + + +def _actions_activation_stats(engine, cf_name): + print("-" * 80) + print("CF Data Variable: %r" % cf_name) + + engine.print_stats() + + print("Rules Triggered:") + + for rule in sorted(list(engine.rules_triggered)): + print("\t%s" % rule) + + print("Case Specific Facts:") + kb_facts = engine.get_kb() + + for key in kb_facts.entity_lists.keys(): + for arg in kb_facts.entity_lists[key].case_specific_facts: + print("\t%s%s" % (key, arg)) + + +def _set_attributes(attributes, key, value): + """Set attributes dictionary, converting unicode strings appropriately.""" + + if isinstance(value, str): + try: + attributes[str(key)] = str(value) + except UnicodeEncodeError: + attributes[str(key)] = value + else: + attributes[str(key)] = value + + +def _add_unused_attributes(iris_object, cf_var): + """ + Populate the attributes of a cf element with the "unused" attributes + from the associated CF-netCDF variable. That is, all those that aren't CF + reserved terms. + + """ + + def attribute_predicate(item): + return item[0] not in _CF_ATTRS + + tmpvar = filter(attribute_predicate, cf_var.cf_attrs_unused()) + for attr_name, attr_value in tmpvar: + _set_attributes(iris_object.attributes, attr_name, attr_value) + + +def _get_actual_dtype(cf_var): + # Figure out what the eventual data type will be after any scale/offset + # transforms. + dummy_data = np.zeros(1, dtype=cf_var.dtype) + if hasattr(cf_var, "scale_factor"): + dummy_data = cf_var.scale_factor * dummy_data + if hasattr(cf_var, "add_offset"): + dummy_data = cf_var.add_offset + dummy_data + return dummy_data.dtype + + +def _get_cf_var_data(cf_var, filename): + # Get lazy chunked data out of a cf variable. + dtype = _get_actual_dtype(cf_var) + + # Create cube with deferred data, but no metadata + fill_value = getattr( + cf_var.cf_data, + "_FillValue", + netCDF4.default_fillvals[cf_var.dtype.str[1:]], + ) + proxy = NetCDFDataProxy( + cf_var.shape, dtype, filename, cf_var.cf_name, fill_value + ) + # Get the chunking specified for the variable : this is either a shape, or + # maybe the string "contiguous". + chunks = cf_var.cf_data.chunking() + # In the "contiguous" case, pass chunks=None to 'as_lazy_data'. + if chunks == "contiguous": + chunks = None + return as_lazy_data(proxy, chunks=chunks) + + +class _OrderedAddableList(list): + """ + A custom container object for actions recording. + + Used purely in actions debugging, to accumulate a record of which actions + were activated. + + It replaces a set, so as to preserve the ordering of operations, with + possible repeats, and it also numbers the entries. + + The actions routines invoke an 'add' method, so this effectively replaces + a set.add with a list.append. + + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._n_add = 0 + + def add(self, msg): + self._n_add += 1 + n_add = self._n_add + self.append(f"#{n_add:03d} : {msg}") + + +def _load_cube(engine, cf, cf_var, filename): + from iris.cube import Cube + + """Create the cube associated with the CF-netCDF data variable.""" + data = _get_cf_var_data(cf_var, filename) + cube = Cube(data) + + # Reset the actions engine. + engine.reset() + + # Initialise engine rule processing hooks. + engine.cf_var = cf_var + engine.cube = cube + engine.cube_parts = {} + engine.requires = {} + engine.rules_triggered = _OrderedAddableList() + engine.filename = filename + + # Assert all the case-specific facts. + # This extracts 'facts' specific to this data-variable (aka cube), from + # the info supplied in the CFGroup object. + _assert_case_specific_facts(engine, cf, cf_var.cf_group) + + # Run the actions engine. + # This creates various cube elements and attaches them to the cube. + # It also records various other info on the engine, to be processed later. + engine.activate() + + # Having run the rules, now add the "unused" attributes to each cf element. + def fix_attributes_all_elements(role_name): + elements_and_names = engine.cube_parts.get(role_name, []) + + for iris_object, cf_var_name in elements_and_names: + _add_unused_attributes(iris_object, cf.cf_group[cf_var_name]) + + # Populate the attributes of all coordinates, cell-measures and ancillary-vars. + fix_attributes_all_elements("coordinates") + fix_attributes_all_elements("ancillary_variables") + fix_attributes_all_elements("cell_measures") + + # Also populate attributes of the top-level cube itself. + _add_unused_attributes(cube, cf_var) + + # Work out reference names for all the coords. + names = { + coord.var_name: coord.standard_name or coord.var_name or "unknown" + for coord in cube.coords() + } + + # Add all the cube cell methods. + cube.cell_methods = [ + iris.coords.CellMethod( + method=method.method, + intervals=method.intervals, + comments=method.comments, + coords=[ + names[coord_name] if coord_name in names else coord_name + for coord_name in method.coord_names + ], + ) + for method in cube.cell_methods + ] + + if DEBUG: + # Show activation statistics for this data-var (i.e. cube). + _actions_activation_stats(engine, cf_var.cf_name) + + return cube + + +def _load_aux_factory(engine, cube): + """ + Convert any CF-netCDF dimensionless coordinate to an AuxCoordFactory. + + """ + formula_type = engine.requires.get("formula_type") + if formula_type in [ + "atmosphere_sigma_coordinate", + "atmosphere_hybrid_height_coordinate", + "atmosphere_hybrid_sigma_pressure_coordinate", + "ocean_sigma_z_coordinate", + "ocean_sigma_coordinate", + "ocean_s_coordinate", + "ocean_s_coordinate_g1", + "ocean_s_coordinate_g2", + ]: + + def coord_from_term(term): + # Convert term names to coordinates (via netCDF variable names). + name = engine.requires["formula_terms"].get(term, None) + if name is not None: + for coord, cf_var_name in engine.cube_parts["coordinates"]: + if cf_var_name == name: + return coord + warnings.warn( + "Unable to find coordinate for variable " + "{!r}".format(name) + ) + + if formula_type == "atmosphere_sigma_coordinate": + pressure_at_top = coord_from_term("ptop") + sigma = coord_from_term("sigma") + surface_air_pressure = coord_from_term("ps") + factory = AtmosphereSigmaFactory( + pressure_at_top, sigma, surface_air_pressure + ) + elif formula_type == "atmosphere_hybrid_height_coordinate": + delta = coord_from_term("a") + sigma = coord_from_term("b") + orography = coord_from_term("orog") + factory = HybridHeightFactory(delta, sigma, orography) + elif formula_type == "atmosphere_hybrid_sigma_pressure_coordinate": + # Hybrid pressure has two valid versions of its formula terms: + # "p0: var1 a: var2 b: var3 ps: var4" or + # "ap: var1 b: var2 ps: var3" where "ap = p0 * a" + # Attempt to get the "ap" term. + delta = coord_from_term("ap") + if delta is None: + # The "ap" term is unavailable, so try getting terms "p0" + # and "a" terms in order to derive an "ap" equivalent term. + coord_p0 = coord_from_term("p0") + if coord_p0 is not None: + if coord_p0.shape != (1,): + msg = ( + "Expecting {!r} to be a scalar reference " + "pressure coordinate, got shape {!r}".format( + coord_p0.var_name, coord_p0.shape + ) + ) + raise ValueError(msg) + if coord_p0.has_bounds(): + msg = ( + "Ignoring atmosphere hybrid sigma pressure " + "scalar coordinate {!r} bounds.".format( + coord_p0.name() + ) + ) + warnings.warn(msg) + coord_a = coord_from_term("a") + if coord_a is not None: + if coord_a.units.is_unknown(): + # Be graceful, and promote unknown to dimensionless units. + coord_a.units = "1" + delta = coord_a * coord_p0.points[0] + delta.units = coord_a.units * coord_p0.units + delta.rename("vertical pressure") + delta.var_name = "ap" + cube.add_aux_coord(delta, cube.coord_dims(coord_a)) + + sigma = coord_from_term("b") + surface_air_pressure = coord_from_term("ps") + factory = HybridPressureFactory(delta, sigma, surface_air_pressure) + elif formula_type == "ocean_sigma_z_coordinate": + sigma = coord_from_term("sigma") + eta = coord_from_term("eta") + depth = coord_from_term("depth") + depth_c = coord_from_term("depth_c") + nsigma = coord_from_term("nsigma") + zlev = coord_from_term("zlev") + factory = OceanSigmaZFactory( + sigma, eta, depth, depth_c, nsigma, zlev + ) + elif formula_type == "ocean_sigma_coordinate": + sigma = coord_from_term("sigma") + eta = coord_from_term("eta") + depth = coord_from_term("depth") + factory = OceanSigmaFactory(sigma, eta, depth) + elif formula_type == "ocean_s_coordinate": + s = coord_from_term("s") + eta = coord_from_term("eta") + depth = coord_from_term("depth") + a = coord_from_term("a") + depth_c = coord_from_term("depth_c") + b = coord_from_term("b") + factory = OceanSFactory(s, eta, depth, a, b, depth_c) + elif formula_type == "ocean_s_coordinate_g1": + s = coord_from_term("s") + c = coord_from_term("c") + eta = coord_from_term("eta") + depth = coord_from_term("depth") + depth_c = coord_from_term("depth_c") + factory = OceanSg1Factory(s, c, eta, depth, depth_c) + elif formula_type == "ocean_s_coordinate_g2": + s = coord_from_term("s") + c = coord_from_term("c") + eta = coord_from_term("eta") + depth = coord_from_term("depth") + depth_c = coord_from_term("depth_c") + factory = OceanSg2Factory(s, c, eta, depth, depth_c) + cube.add_aux_factory(factory) + + +def _translate_constraints_to_var_callback(constraints): + """ + Translate load constraints into a simple data-var filter function, if possible. + + Returns: + * function(cf_var:CFDataVariable): --> bool, + or None. + + For now, ONLY handles a single NameConstraint with no 'STASH' component. + + """ + import iris._constraints + + constraints = iris._constraints.list_of_constraints(constraints) + result = None + if len(constraints) == 1: + (constraint,) = constraints + if ( + isinstance(constraint, iris._constraints.NameConstraint) + and constraint.STASH == "none" + ): + # As long as it doesn't use a STASH match, then we can treat it as + # a testing against name properties of cf_var. + # That's just like testing against name properties of a cube, except that they may not all exist. + def inner(cf_datavar): + match = True + for name in constraint._names: + expected = getattr(constraint, name) + if name != "STASH" and expected != "none": + attr_name = "cf_name" if name == "var_name" else name + # Fetch property : N.B. CFVariable caches the property values + # The use of a default here is the only difference from the code in NameConstraint. + if not hasattr(cf_datavar, attr_name): + continue + actual = getattr(cf_datavar, attr_name, "") + if actual != expected: + match = False + break + return match + + result = inner + return result + + +def load_cubes(filenames, callback=None, constraints=None): + """ + Loads cubes from a list of NetCDF filenames/OPeNDAP URLs. + + Args: + + * filenames (string/list): + One or more NetCDF filenames/OPeNDAP URLs to load from. + + Kwargs: + + * callback (callable function): + Function which can be passed on to :func:`iris.io.run_callback`. + + Returns: + Generator of loaded NetCDF :class:`iris.cube.Cube`. + + """ + # TODO: rationalise UGRID/mesh handling once experimental.ugrid is folded + # into standard behaviour. + # Deferred import to avoid circular imports. + from iris.experimental.ugrid.cf import CFUGridReader + from iris.experimental.ugrid.load import ( + PARSE_UGRID_ON_LOAD, + _build_mesh_coords, + _meshes_from_cf, + ) + from iris.io import run_callback + + # Create a low-level data-var filter from the original load constraints, if they are suitable. + var_callback = _translate_constraints_to_var_callback(constraints) + + # Create an actions engine. + engine = _actions_engine() + + if isinstance(filenames, str): + filenames = [filenames] + + for filename in filenames: + # Ingest the netCDF file. + meshes = {} + if PARSE_UGRID_ON_LOAD: + cf = CFUGridReader(filename) + meshes = _meshes_from_cf(cf) + else: + cf = iris.fileformats.cf.CFReader(filename) + + # Process each CF data variable. + data_variables = list(cf.cf_group.data_variables.values()) + list( + cf.cf_group.promoted.values() + ) + for cf_var in data_variables: + if var_callback and not var_callback(cf_var): + # Deliver only selected results. + continue + + # cf_var-specific mesh handling, if a mesh is present. + # Build the mesh_coords *before* loading the cube - avoids + # mesh-related attributes being picked up by + # _add_unused_attributes(). + mesh_name = None + mesh = None + mesh_coords, mesh_dim = [], None + if PARSE_UGRID_ON_LOAD: + mesh_name = getattr(cf_var, "mesh", None) + if mesh_name is not None: + try: + mesh = meshes[mesh_name] + except KeyError: + message = ( + f"File does not contain mesh: '{mesh_name}' - " + f"referenced by variable: '{cf_var.cf_name}' ." + ) + logger.debug(message) + if mesh is not None: + mesh_coords, mesh_dim = _build_mesh_coords(mesh, cf_var) + + cube = _load_cube(engine, cf, cf_var, filename) + + # Attach the mesh (if present) to the cube. + for mesh_coord in mesh_coords: + cube.add_aux_coord(mesh_coord, mesh_dim) + + # Process any associated formula terms and attach + # the corresponding AuxCoordFactory. + try: + _load_aux_factory(engine, cube) + except ValueError as e: + warnings.warn("{}".format(e)) + + # Perform any user registered callback function. + cube = run_callback(callback, cube, cf_var, filename) + + # Callback mechanism may return None, which must not be yielded + if cube is None: + continue + + yield cube diff --git a/lib/iris/fileformats/netcdf.py b/lib/iris/fileformats/netcdf/saver.py similarity index 83% rename from lib/iris/fileformats/netcdf.py rename to lib/iris/fileformats/netcdf/saver.py index 6a7b37a1cc..650c5e3338 100644 --- a/lib/iris/fileformats/netcdf.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -4,16 +4,16 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Module to support the loading of a NetCDF file into an Iris cube. +Module to support the saving of Iris cubes to a NetCDF file, also using the CF +conventions for metadata interpretation. -See also: `netCDF4 python `_ +See : `NetCDF User's Guide `_ +and `netCDF4 python module `_. -Also refer to document 'NetCDF Climate and Forecast (CF) Metadata Conventions'. +Also : `CF Conventions `_. """ - import collections -import collections.abc from itertools import repeat, zip_longest import os import os.path @@ -28,7 +28,7 @@ import numpy as np import numpy.ma as ma -from iris._lazy_data import _co_realise_lazy_arrays, as_lazy_data, is_lazy_data +from iris._lazy_data import _co_realise_lazy_arrays, is_lazy_data from iris.aux_factory import ( AtmosphereSigmaFactory, HybridHeightFactory, @@ -48,28 +48,17 @@ import iris.io import iris.util -# Show actions activation statistics. -DEBUG = False +# Get the logger : shared logger for all in 'iris.fileformats.netcdf'. +from . import logger -# Configure the logger. -logger = iris.config.get_logger(__name__) +# Avoid warning about unused import. +# We could use an __all__, but we don't want to maintain one here +logger # Standard CML spatio-temporal axis names. SPATIO_TEMPORAL_AXES = ["t", "z", "y", "x"] -# Pass through CF attributes: -# - comment -# - Conventions -# - flag_masks -# - flag_meanings -# - flag_values -# - history -# - institution -# - reference -# - source -# - title -# - positive -# +# The CF-meaningful attributes which may appear on a data variable. _CF_ATTRS = [ "add_offset", "ancillary_variables", @@ -447,555 +436,6 @@ def coord(self, name): return result -def _actions_engine(): - # Return an 'actions engine', which provides a pyke-rules-like interface to - # the core cf translation code. - # Deferred import to avoid circularity. - import iris.fileformats._nc_load_rules.engine as nc_actions_engine - - engine = nc_actions_engine.Engine() - return engine - - -class NetCDFDataProxy: - """A reference to the data payload of a single NetCDF file variable.""" - - __slots__ = ("shape", "dtype", "path", "variable_name", "fill_value") - - def __init__(self, shape, dtype, path, variable_name, fill_value): - self.shape = shape - self.dtype = dtype - self.path = path - self.variable_name = variable_name - self.fill_value = fill_value - - @property - def ndim(self): - return len(self.shape) - - def __getitem__(self, keys): - dataset = netCDF4.Dataset(self.path) - try: - variable = dataset.variables[self.variable_name] - # Get the NetCDF variable data and slice. - var = variable[keys] - finally: - dataset.close() - return np.asanyarray(var) - - def __repr__(self): - fmt = ( - "<{self.__class__.__name__} shape={self.shape}" - " dtype={self.dtype!r} path={self.path!r}" - " variable_name={self.variable_name!r}>" - ) - return fmt.format(self=self) - - def __getstate__(self): - return {attr: getattr(self, attr) for attr in self.__slots__} - - def __setstate__(self, state): - for key, value in state.items(): - setattr(self, key, value) - - -def _assert_case_specific_facts(engine, cf, cf_group): - # Initialise a data store for built cube elements. - # This is used to patch element attributes *not* setup by the actions - # process, after the actions code has run. - engine.cube_parts["coordinates"] = [] - engine.cube_parts["cell_measures"] = [] - engine.cube_parts["ancillary_variables"] = [] - - # Assert facts for CF coordinates. - for cf_name in cf_group.coordinates.keys(): - engine.add_case_specific_fact("coordinate", (cf_name,)) - - # Assert facts for CF auxiliary coordinates. - for cf_name in cf_group.auxiliary_coordinates.keys(): - engine.add_case_specific_fact("auxiliary_coordinate", (cf_name,)) - - # Assert facts for CF cell measures. - for cf_name in cf_group.cell_measures.keys(): - engine.add_case_specific_fact("cell_measure", (cf_name,)) - - # Assert facts for CF ancillary variables. - for cf_name in cf_group.ancillary_variables.keys(): - engine.add_case_specific_fact("ancillary_variable", (cf_name,)) - - # Assert facts for CF grid_mappings. - for cf_name in cf_group.grid_mappings.keys(): - engine.add_case_specific_fact("grid_mapping", (cf_name,)) - - # Assert facts for CF labels. - for cf_name in cf_group.labels.keys(): - engine.add_case_specific_fact("label", (cf_name,)) - - # Assert facts for CF formula terms associated with the cf_group - # of the CF data variable. - - # Collect varnames of formula-root variables as we go. - # NOTE: use dictionary keys as an 'OrderedSet' - # - see: https://stackoverflow.com/a/53657523/2615050 - # This is to ensure that we can handle the resulting facts in a definite - # order, as using a 'set' led to indeterminate results. - formula_root = {} - for cf_var in cf.cf_group.formula_terms.values(): - for cf_root, cf_term in cf_var.cf_terms_by_root.items(): - # Only assert this fact if the formula root variable is - # defined in the CF group of the CF data variable. - if cf_root in cf_group: - formula_root[cf_root] = True - engine.add_case_specific_fact( - "formula_term", - (cf_var.cf_name, cf_root, cf_term), - ) - - for cf_root in formula_root.keys(): - engine.add_case_specific_fact("formula_root", (cf_root,)) - - -def _actions_activation_stats(engine, cf_name): - print("-" * 80) - print("CF Data Variable: %r" % cf_name) - - engine.print_stats() - - print("Rules Triggered:") - - for rule in sorted(list(engine.rules_triggered)): - print("\t%s" % rule) - - print("Case Specific Facts:") - kb_facts = engine.get_kb() - - for key in kb_facts.entity_lists.keys(): - for arg in kb_facts.entity_lists[key].case_specific_facts: - print("\t%s%s" % (key, arg)) - - -def _set_attributes(attributes, key, value): - """Set attributes dictionary, converting unicode strings appropriately.""" - - if isinstance(value, str): - try: - attributes[str(key)] = str(value) - except UnicodeEncodeError: - attributes[str(key)] = value - else: - attributes[str(key)] = value - - -def _add_unused_attributes(iris_object, cf_var): - """ - Populate the attributes of a cf element with the "unused" attributes - from the associated CF-netCDF variable. That is, all those that aren't CF - reserved terms. - - """ - - def attribute_predicate(item): - return item[0] not in _CF_ATTRS - - tmpvar = filter(attribute_predicate, cf_var.cf_attrs_unused()) - for attr_name, attr_value in tmpvar: - _set_attributes(iris_object.attributes, attr_name, attr_value) - - -def _get_actual_dtype(cf_var): - # Figure out what the eventual data type will be after any scale/offset - # transforms. - dummy_data = np.zeros(1, dtype=cf_var.dtype) - if hasattr(cf_var, "scale_factor"): - dummy_data = cf_var.scale_factor * dummy_data - if hasattr(cf_var, "add_offset"): - dummy_data = cf_var.add_offset + dummy_data - return dummy_data.dtype - - -def _get_cf_var_data(cf_var, filename): - # Get lazy chunked data out of a cf variable. - dtype = _get_actual_dtype(cf_var) - - # Create cube with deferred data, but no metadata - fill_value = getattr( - cf_var.cf_data, - "_FillValue", - netCDF4.default_fillvals[cf_var.dtype.str[1:]], - ) - proxy = NetCDFDataProxy( - cf_var.shape, dtype, filename, cf_var.cf_name, fill_value - ) - # Get the chunking specified for the variable : this is either a shape, or - # maybe the string "contiguous". - chunks = cf_var.cf_data.chunking() - # In the "contiguous" case, pass chunks=None to 'as_lazy_data'. - if chunks == "contiguous": - chunks = None - return as_lazy_data(proxy, chunks=chunks) - - -class _OrderedAddableList(list): - """ - A custom container object for actions recording. - - Used purely in actions debugging, to accumulate a record of which actions - were activated. - - It replaces a set, so as to preserve the ordering of operations, with - possible repeats, and it also numbers the entries. - - The actions routines invoke an 'add' method, so this effectively replaces - a set.add with a list.append. - - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._n_add = 0 - - def add(self, msg): - self._n_add += 1 - n_add = self._n_add - self.append(f"#{n_add:03d} : {msg}") - - -def _load_cube(engine, cf, cf_var, filename): - from iris.cube import Cube - - """Create the cube associated with the CF-netCDF data variable.""" - data = _get_cf_var_data(cf_var, filename) - cube = Cube(data) - - # Reset the actions engine. - engine.reset() - - # Initialise engine rule processing hooks. - engine.cf_var = cf_var - engine.cube = cube - engine.cube_parts = {} - engine.requires = {} - engine.rules_triggered = _OrderedAddableList() - engine.filename = filename - - # Assert all the case-specific facts. - # This extracts 'facts' specific to this data-variable (aka cube), from - # the info supplied in the CFGroup object. - _assert_case_specific_facts(engine, cf, cf_var.cf_group) - - # Run the actions engine. - # This creates various cube elements and attaches them to the cube. - # It also records various other info on the engine, to be processed later. - engine.activate() - - # Having run the rules, now add the "unused" attributes to each cf element. - def fix_attributes_all_elements(role_name): - elements_and_names = engine.cube_parts.get(role_name, []) - - for iris_object, cf_var_name in elements_and_names: - _add_unused_attributes(iris_object, cf.cf_group[cf_var_name]) - - # Populate the attributes of all coordinates, cell-measures and ancillary-vars. - fix_attributes_all_elements("coordinates") - fix_attributes_all_elements("ancillary_variables") - fix_attributes_all_elements("cell_measures") - - # Also populate attributes of the top-level cube itself. - _add_unused_attributes(cube, cf_var) - - # Work out reference names for all the coords. - names = { - coord.var_name: coord.standard_name or coord.var_name or "unknown" - for coord in cube.coords() - } - - # Add all the cube cell methods. - cube.cell_methods = [ - iris.coords.CellMethod( - method=method.method, - intervals=method.intervals, - comments=method.comments, - coords=[ - names[coord_name] if coord_name in names else coord_name - for coord_name in method.coord_names - ], - ) - for method in cube.cell_methods - ] - - if DEBUG: - # Show activation statistics for this data-var (i.e. cube). - _actions_activation_stats(engine, cf_var.cf_name) - - return cube - - -def _load_aux_factory(engine, cube): - """ - Convert any CF-netCDF dimensionless coordinate to an AuxCoordFactory. - - """ - formula_type = engine.requires.get("formula_type") - if formula_type in [ - "atmosphere_sigma_coordinate", - "atmosphere_hybrid_height_coordinate", - "atmosphere_hybrid_sigma_pressure_coordinate", - "ocean_sigma_z_coordinate", - "ocean_sigma_coordinate", - "ocean_s_coordinate", - "ocean_s_coordinate_g1", - "ocean_s_coordinate_g2", - ]: - - def coord_from_term(term): - # Convert term names to coordinates (via netCDF variable names). - name = engine.requires["formula_terms"].get(term, None) - if name is not None: - for coord, cf_var_name in engine.cube_parts["coordinates"]: - if cf_var_name == name: - return coord - warnings.warn( - "Unable to find coordinate for variable " - "{!r}".format(name) - ) - - if formula_type == "atmosphere_sigma_coordinate": - pressure_at_top = coord_from_term("ptop") - sigma = coord_from_term("sigma") - surface_air_pressure = coord_from_term("ps") - factory = AtmosphereSigmaFactory( - pressure_at_top, sigma, surface_air_pressure - ) - elif formula_type == "atmosphere_hybrid_height_coordinate": - delta = coord_from_term("a") - sigma = coord_from_term("b") - orography = coord_from_term("orog") - factory = HybridHeightFactory(delta, sigma, orography) - elif formula_type == "atmosphere_hybrid_sigma_pressure_coordinate": - # Hybrid pressure has two valid versions of its formula terms: - # "p0: var1 a: var2 b: var3 ps: var4" or - # "ap: var1 b: var2 ps: var3" where "ap = p0 * a" - # Attempt to get the "ap" term. - delta = coord_from_term("ap") - if delta is None: - # The "ap" term is unavailable, so try getting terms "p0" - # and "a" terms in order to derive an "ap" equivalent term. - coord_p0 = coord_from_term("p0") - if coord_p0 is not None: - if coord_p0.shape != (1,): - msg = ( - "Expecting {!r} to be a scalar reference " - "pressure coordinate, got shape {!r}".format( - coord_p0.var_name, coord_p0.shape - ) - ) - raise ValueError(msg) - if coord_p0.has_bounds(): - msg = ( - "Ignoring atmosphere hybrid sigma pressure " - "scalar coordinate {!r} bounds.".format( - coord_p0.name() - ) - ) - warnings.warn(msg) - coord_a = coord_from_term("a") - if coord_a is not None: - if coord_a.units.is_unknown(): - # Be graceful, and promote unknown to dimensionless units. - coord_a.units = "1" - delta = coord_a * coord_p0.points[0] - delta.units = coord_a.units * coord_p0.units - delta.rename("vertical pressure") - delta.var_name = "ap" - cube.add_aux_coord(delta, cube.coord_dims(coord_a)) - - sigma = coord_from_term("b") - surface_air_pressure = coord_from_term("ps") - factory = HybridPressureFactory(delta, sigma, surface_air_pressure) - elif formula_type == "ocean_sigma_z_coordinate": - sigma = coord_from_term("sigma") - eta = coord_from_term("eta") - depth = coord_from_term("depth") - depth_c = coord_from_term("depth_c") - nsigma = coord_from_term("nsigma") - zlev = coord_from_term("zlev") - factory = OceanSigmaZFactory( - sigma, eta, depth, depth_c, nsigma, zlev - ) - elif formula_type == "ocean_sigma_coordinate": - sigma = coord_from_term("sigma") - eta = coord_from_term("eta") - depth = coord_from_term("depth") - factory = OceanSigmaFactory(sigma, eta, depth) - elif formula_type == "ocean_s_coordinate": - s = coord_from_term("s") - eta = coord_from_term("eta") - depth = coord_from_term("depth") - a = coord_from_term("a") - depth_c = coord_from_term("depth_c") - b = coord_from_term("b") - factory = OceanSFactory(s, eta, depth, a, b, depth_c) - elif formula_type == "ocean_s_coordinate_g1": - s = coord_from_term("s") - c = coord_from_term("c") - eta = coord_from_term("eta") - depth = coord_from_term("depth") - depth_c = coord_from_term("depth_c") - factory = OceanSg1Factory(s, c, eta, depth, depth_c) - elif formula_type == "ocean_s_coordinate_g2": - s = coord_from_term("s") - c = coord_from_term("c") - eta = coord_from_term("eta") - depth = coord_from_term("depth") - depth_c = coord_from_term("depth_c") - factory = OceanSg2Factory(s, c, eta, depth, depth_c) - cube.add_aux_factory(factory) - - -def _translate_constraints_to_var_callback(constraints): - """ - Translate load constraints into a simple data-var filter function, if possible. - - Returns: - * function(cf_var:CFDataVariable): --> bool, - or None. - - For now, ONLY handles a single NameConstraint with no 'STASH' component. - - """ - import iris._constraints - - constraints = iris._constraints.list_of_constraints(constraints) - result = None - if len(constraints) == 1: - (constraint,) = constraints - if ( - isinstance(constraint, iris._constraints.NameConstraint) - and constraint.STASH == "none" - ): - # As long as it doesn't use a STASH match, then we can treat it as - # a testing against name properties of cf_var. - # That's just like testing against name properties of a cube, except that they may not all exist. - def inner(cf_datavar): - match = True - for name in constraint._names: - expected = getattr(constraint, name) - if name != "STASH" and expected != "none": - attr_name = "cf_name" if name == "var_name" else name - # Fetch property : N.B. CFVariable caches the property values - # The use of a default here is the only difference from the code in NameConstraint. - if not hasattr(cf_datavar, attr_name): - continue - actual = getattr(cf_datavar, attr_name, "") - if actual != expected: - match = False - break - return match - - result = inner - return result - - -def load_cubes(filenames, callback=None, constraints=None): - """ - Loads cubes from a list of NetCDF filenames/OPeNDAP URLs. - - Args: - - * filenames (string/list): - One or more NetCDF filenames/OPeNDAP URLs to load from. - - Kwargs: - - * callback (callable function): - Function which can be passed on to :func:`iris.io.run_callback`. - - Returns: - Generator of loaded NetCDF :class:`iris.cube.Cube`. - - """ - # TODO: rationalise UGRID/mesh handling once experimental.ugrid is folded - # into standard behaviour. - # Deferred import to avoid circular imports. - from iris.experimental.ugrid.cf import CFUGridReader - from iris.experimental.ugrid.load import ( - PARSE_UGRID_ON_LOAD, - _build_mesh_coords, - _meshes_from_cf, - ) - from iris.io import run_callback - - # Create a low-level data-var filter from the original load constraints, if they are suitable. - var_callback = _translate_constraints_to_var_callback(constraints) - - # Create an actions engine. - engine = _actions_engine() - - if isinstance(filenames, str): - filenames = [filenames] - - for filename in filenames: - # Ingest the netCDF file. - meshes = {} - if PARSE_UGRID_ON_LOAD: - cf = CFUGridReader(filename) - meshes = _meshes_from_cf(cf) - else: - cf = iris.fileformats.cf.CFReader(filename) - - # Process each CF data variable. - data_variables = list(cf.cf_group.data_variables.values()) + list( - cf.cf_group.promoted.values() - ) - for cf_var in data_variables: - if var_callback and not var_callback(cf_var): - # Deliver only selected results. - continue - - # cf_var-specific mesh handling, if a mesh is present. - # Build the mesh_coords *before* loading the cube - avoids - # mesh-related attributes being picked up by - # _add_unused_attributes(). - mesh_name = None - mesh = None - mesh_coords, mesh_dim = [], None - if PARSE_UGRID_ON_LOAD: - mesh_name = getattr(cf_var, "mesh", None) - if mesh_name is not None: - try: - mesh = meshes[mesh_name] - except KeyError: - message = ( - f"File does not contain mesh: '{mesh_name}' - " - f"referenced by variable: '{cf_var.cf_name}' ." - ) - logger.debug(message) - if mesh is not None: - mesh_coords, mesh_dim = _build_mesh_coords(mesh, cf_var) - - cube = _load_cube(engine, cf, cf_var, filename) - - # Attach the mesh (if present) to the cube. - for mesh_coord in mesh_coords: - cube.add_aux_coord(mesh_coord, mesh_dim) - - # Process any associated formula terms and attach - # the corresponding AuxCoordFactory. - try: - _load_aux_factory(engine, cube) - except ValueError as e: - warnings.warn("{}".format(e)) - - # Perform any user registered callback function. - cube = run_callback(callback, cube, cf_var, filename) - - # Callback mechanism may return None, which must not be yielded - if cube is None: - continue - - yield cube - - def _bytes_if_ascii(string): """ Convert the given string to a byte string (str in py2k, bytes in py3k) @@ -1837,7 +1277,9 @@ def _get_dim_names(self, cube_or_mesh): """ - def record_dimension(names_list, dim_name, length, matching_coords=[]): + def record_dimension( + names_list, dim_name, length, matching_coords=None + ): """ Record a file dimension, its length and associated "coordinates" (which may in fact also be connectivities). @@ -1846,6 +1288,8 @@ def record_dimension(names_list, dim_name, length, matching_coords=[]): matches the earlier finding. """ + if matching_coords is None: + matching_coords = [] if dim_name not in self._existing_dim: self._existing_dim[dim_name] = length else: diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py index c18bdb8399..0cc3d09426 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py @@ -15,7 +15,7 @@ import iris.fileformats._nc_load_rules.engine from iris.fileformats.cf import CFReader import iris.fileformats.netcdf -from iris.fileformats.netcdf import _load_cube +from iris.fileformats.netcdf.loader import _load_cube from iris.tests.stock.netcdf import ncgen_from_cdl """ @@ -83,11 +83,11 @@ def load_cube_from_cdl(self, cdl_string, cdl_path, nc_path): # Grab a data variable : FOR NOW always grab the 'phenom' variable. cf_var = cf.cf_group.data_variables["phenom"] - engine = iris.fileformats.netcdf._actions_engine() + engine = iris.fileformats.netcdf.loader._actions_engine() # If debug enabled, switch on the activation summary debug output. # Use 'patch' so it is restored after the test. - self.patch("iris.fileformats.netcdf.DEBUG", self.debug) + self.patch("iris.fileformats.netcdf.loader.DEBUG", self.debug) with warnings.catch_warnings(): warnings.filterwarnings( diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py b/lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py new file mode 100644 index 0000000000..7c2ae96158 --- /dev/null +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris.fileformats.netcdf.loader` module.""" diff --git a/lib/iris/tests/unit/fileformats/netcdf/test__get_cf_var_data.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__get_cf_var_data.py similarity index 97% rename from lib/iris/tests/unit/fileformats/netcdf/test__get_cf_var_data.py rename to lib/iris/tests/unit/fileformats/netcdf/loader/test__get_cf_var_data.py index 1bf39591d2..054c8e2db1 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test__get_cf_var_data.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test__get_cf_var_data.py @@ -16,7 +16,7 @@ from iris._lazy_data import _optimum_chunksize import iris.fileformats.cf -from iris.fileformats.netcdf import _get_cf_var_data +from iris.fileformats.netcdf.loader import _get_cf_var_data class Test__get_cf_var_data(tests.IrisTest): diff --git a/lib/iris/tests/unit/fileformats/netcdf/test__load_aux_factory.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py similarity index 99% rename from lib/iris/tests/unit/fileformats/netcdf/test__load_aux_factory.py rename to lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py index eb9da6b5d6..841935cc81 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test__load_aux_factory.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py @@ -16,7 +16,7 @@ from iris.coords import DimCoord from iris.cube import Cube -from iris.fileformats.netcdf import _load_aux_factory +from iris.fileformats.netcdf.loader import _load_aux_factory class TestAtmosphereHybridSigmaPressureCoordinate(tests.IrisTest): diff --git a/lib/iris/tests/unit/fileformats/netcdf/test__load_cube.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_cube.py similarity index 96% rename from lib/iris/tests/unit/fileformats/netcdf/test__load_cube.py rename to lib/iris/tests/unit/fileformats/netcdf/loader/test__load_cube.py index 0e98eec916..6e28a2f8e4 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test__load_cube.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_cube.py @@ -15,7 +15,7 @@ from iris.coords import DimCoord import iris.fileformats.cf -from iris.fileformats.netcdf import _load_cube +from iris.fileformats.netcdf.loader import _load_cube class TestCoordAttributes(tests.IrisTest): @@ -28,7 +28,7 @@ def _patcher(engine, cf, cf_group): engine.cube_parts["coordinates"] = coordinates def setUp(self): - this = "iris.fileformats.netcdf._assert_case_specific_facts" + this = "iris.fileformats.netcdf.loader._assert_case_specific_facts" patch = mock.patch(this, side_effect=self._patcher) patch.start() self.addCleanup(patch.stop) @@ -112,7 +112,7 @@ def test_flag_pass_thru_multi(self): class TestCubeAttributes(tests.IrisTest): def setUp(self): - this = "iris.fileformats.netcdf._assert_case_specific_facts" + this = "iris.fileformats.netcdf.loader._assert_case_specific_facts" patch = mock.patch(this) patch.start() self.addCleanup(patch.stop) diff --git a/lib/iris/tests/unit/fileformats/netcdf/test__translate_constraints_to_var_callback.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py similarity index 97% rename from lib/iris/tests/unit/fileformats/netcdf/test__translate_constraints_to_var_callback.py rename to lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py index fb08ffda2b..77bb0d3950 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test__translate_constraints_to_var_callback.py +++ b/lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py @@ -13,7 +13,9 @@ import iris from iris.fileformats.cf import CFDataVariable -from iris.fileformats.netcdf import _translate_constraints_to_var_callback +from iris.fileformats.netcdf.loader import ( + _translate_constraints_to_var_callback, +) # import iris tests first so that some things can be initialised before # importing anything else diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py b/lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py new file mode 100644 index 0000000000..a68d5fc5d0 --- /dev/null +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris.fileformats.netcdf.saver` module.""" diff --git a/lib/iris/tests/unit/fileformats/netcdf/test__FillValueMaskCheckAndStoreTarget.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test__FillValueMaskCheckAndStoreTarget.py similarity index 97% rename from lib/iris/tests/unit/fileformats/netcdf/test__FillValueMaskCheckAndStoreTarget.py rename to lib/iris/tests/unit/fileformats/netcdf/saver/test__FillValueMaskCheckAndStoreTarget.py index 01ba7ff38d..77209efafc 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test__FillValueMaskCheckAndStoreTarget.py +++ b/lib/iris/tests/unit/fileformats/netcdf/saver/test__FillValueMaskCheckAndStoreTarget.py @@ -17,7 +17,7 @@ import numpy as np -from iris.fileformats.netcdf import _FillValueMaskCheckAndStoreTarget +from iris.fileformats.netcdf.saver import _FillValueMaskCheckAndStoreTarget class Test__FillValueMaskCheckAndStoreTarget(tests.IrisTest): diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py index e17082b5e9..174a46fdb7 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py @@ -203,7 +203,7 @@ def test_big_endian(self): def test_zlib(self): cube = self._simple_cube(">f4") - api = self.patch("iris.fileformats.netcdf.netCDF4") + api = self.patch("iris.fileformats.netcdf.saver.netCDF4") # Define mocked default fill values to prevent deprecation warning (#4374). api.default_fillvals = collections.defaultdict(lambda: -99.0) with Saver("/dummy/path", "NETCDF4") as saver: diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_save.py b/lib/iris/tests/unit/fileformats/netcdf/test_save.py index 669a3c4137..030edbfce2 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_save.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_save.py @@ -143,7 +143,7 @@ def test_None(self): # Test that when no fill_value argument is passed, the fill_value # argument to Saver.write is None or not present. cubes = self._make_cubes() - with mock.patch("iris.fileformats.netcdf.Saver") as Saver: + with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: save(cubes, "dummy.nc") # Get the Saver.write mock @@ -161,7 +161,7 @@ def test_single(self): # that value is passed to each call to Saver.write cubes = self._make_cubes() fill_value = 12345.0 - with mock.patch("iris.fileformats.netcdf.Saver") as Saver: + with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: save(cubes, "dummy.nc", fill_value=fill_value) # Get the Saver.write mock @@ -178,7 +178,7 @@ def test_multiple(self): # each element is passed to separate calls to Saver.write cubes = self._make_cubes() fill_values = [123.0, 456.0, 789.0] - with mock.patch("iris.fileformats.netcdf.Saver") as Saver: + with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: save(cubes, "dummy.nc", fill_value=fill_values) # Get the Saver.write mock @@ -195,7 +195,7 @@ def test_single_string(self): # that value is passed to calls to Saver.write cube = Cube(["abc", "def", "hij"]) fill_value = "xyz" - with mock.patch("iris.fileformats.netcdf.Saver") as Saver: + with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: save(cube, "dummy.nc", fill_value=fill_value) # Get the Saver.write mock @@ -211,7 +211,7 @@ def test_multi_wrong_length(self): # is passed as the fill_value argument, an error is raised cubes = self._make_cubes() fill_values = [1.0, 2.0, 3.0, 4.0] - with mock.patch("iris.fileformats.netcdf.Saver"): + with mock.patch("iris.fileformats.netcdf.saver.Saver"): with self.assertRaises(ValueError): save(cubes, "dummy.nc", fill_value=fill_values) From 96c00b6138e296aa6a56f2f215ec0355b753b6a4 Mon Sep 17 00:00:00 2001 From: Elias <110238618+ESadek-MO@users.noreply.github.com> Date: Wed, 28 Sep 2022 15:50:25 +0100 Subject: [PATCH 238/319] Added a glossary for Iris docs. (#4902) --- docs/src/userguide/glossary.rst | 210 ++++++++++++++++++++++++++++++++ docs/src/userguide/index.rst | 1 + docs/src/whatsnew/latest.rst | 1 + 3 files changed, 212 insertions(+) create mode 100644 docs/src/userguide/glossary.rst diff --git a/docs/src/userguide/glossary.rst b/docs/src/userguide/glossary.rst new file mode 100644 index 0000000000..818ef0c7ad --- /dev/null +++ b/docs/src/userguide/glossary.rst @@ -0,0 +1,210 @@ +.. _glossary: + +Glossary +============= + +.. glossary:: + + Cartopy + A python package for producing maps, and other geospatial data. + Allows plotting on these maps, over a range of projections. + + | **Related:** :term:`Matplotlib` + | **More information:** `CartoPy Site `_ + | + + CF Conventions + Rules for storing meteorological Climate and Forecast data in + :term:`NetCDF Format` files, defining a standard metadata format to + describe what the data is. + This also forms the data model which iris is based on. + + | **Related:** :term:`NetCDF Format` + | **More information:** `CF Conventions `_ + | + + Coordinate + A container for data points, comes in three main flavours. + + - Dimensional Coordinate - + A coordinate that describes a single data dimension of a cube. + They can only contain numerical values, in a sorted order (ascending + or descending). + - Auxiliary Coordinate - + A coordinate that can map to multiple data dimensions. Can + contain any type of data. + - Scalar Coordinate - + A coordinate that is not mapped to any data dimension, instead + representing the cube as a whole. + + | **Related:** :term:`Cube` + | **More information:** :doc:`iris_cubes` + | + + Cube + Cubes are the main method of storing data in Iris. A cube can consist of: + + - Array of :term:`Phenomenon` Data (Required) + - :term:`Coordinates ` + - :term:`Standard Name` + - :term:`Long Name` + - :term:`Unit` + - :term:`Cell Methods ` + - :term:`Coordinate Factories ` + + | **Related:** :term:`NumPy` + | **More information:** :doc:`iris_cubes` + | + + Cell Method + A cell method represents that a cube's data has been derived from + a past statistical operation, such as a + MEAN or SUM operation. + + | **Related:** :term:`Cube` + | **More information:** :doc:`iris_cubes` + | + + Coordinate Factory + A coordinate factory derives coordinates (sometimes referred to as + derived coordinates) from the values of existing coordinates. + E.g. A hybrid height factory might use "height above sea level" + and "height at ground level" coordinate data to calculate a + "height above ground level" coordinate. + + | **Related:** :term:`Cube` + | **More information:** :doc:`iris_cubes` + | + + + Dask + A data analytics python library. Iris predominantly uses Dask Arrays; + a collection of NumPy-esque arrays. The data is operated in batches, + so that not all data is in RAM at once. + + | **Related:** :term:`Lazy Data` **|** :term:`NumPy` + | **More information:** :doc:`real_and_lazy_data` + | + + Fields File (FF) Format + A meteorological file format, the output of the Unified Model. + + | **Related:** :term:`GRIB Format` + **|** :term:`Post Processing (PP) Format` **|** :term:`NetCDF Format` + | **More information:** `Unified Model `_ + | + + GRIB Format + A WMO-standard meteorological file format. + + | **Related:** :term:`Fields File (FF) Format` + **|** :term:`Post Processing (PP) Format` **|** :term:`NetCDF Format` + | **More information:** `GRIB 1 User Guide `_ + **|** `GRIB 2 User Guide.pdf `_ + | + + Lazy Data + Data stored in hard drive, and then temporarily loaded into RAM in + batches when needed. Allows of less memory usage and faster performance, + thanks to parallel processing. + + | **Related:** :term:`Dask` **|** :term:`Real Data` + | **More information:** :doc:`real_and_lazy_data` + | + + Long Name + A name describing a :term:`phenomenon`, not limited to the + the same restraints as :term:`standard name`. + + | **Related:** :term:`Standard Name` **|** :term:`Cube` + | **More information:** :doc:`iris_cubes` + | + + Matplotlib + A python package for plotting and projecting data in a wide variety + of formats. + + | **Related:** :term:`CartoPy` **|** :term:`NumPy` + | **More information:** `Matplotlib `_ + | + + Metadata + The information which describes a phenomenon. + Within Iris specifically, all information which + distinguishes one phenomenon from another, + e.g. :term:`units ` or :term:`Cell Methods ` + + | **Related:** :term:`Phenomenon` **|** :term:`Cube` + | **More information:** :doc:`../further_topics/metadata` + | + + NetCDF Format + A flexible file format for storing multi-dimensional array-like data. + When Iris loads this format, it also especially recognises and interprets data + encoded according to the :term:`CF Conventions`. + + | **Related:** :term:`Fields File (FF) Format` + **|** :term:`GRIB Format` **|** :term:`Post Processing (PP) Format` + | **More information:** `NetCDF-4 Python Git `_ + | + + NumPy + A mathematical Python library, predominantly based around + multi-dimensional arrays. + + | **Related:** :term:`Dask` **|** :term:`Cube` + **|** :term:`Xarray` + | **More information:** `NumPy.org `_ + | + + Phenomenon + The primary data which is measured, usually within a cube, e.g. + air temperature. + + | **Related:** :term:`Metadata` + **|** :term:`Standard Name` **|** :term:`Cube` + | **More information:** :doc:`iris_cubes` + | + + Post Processing (PP) Format + A meteorological file format, created from a post processed + :term:`Fields File (FF) Format`. + + | **Related:** :term:`GRIB Format` **|** :term:`NetCDF Format` + | **More information:** `PP Wikipedia Page `_ + | + + Real Data + Data that has been loaded into RAM, as opposed to sitting + on the hard drive. + + | **Related:** :term:`Lazy Data` **|** :term:`NumPy` + | **More information:** :doc:`real_and_lazy_data` + | + + Standard Name + A name describing a :term:`phenomenon`, one from a fixed list + defined at `CF Standard Names `_. + + | **Related:** :term:`Long Name` **|** :term:`Cube` + | **More information:** :doc:`iris_cubes` + | + + Unit + The unit with which the :term:`phenomenon` is measured e.g. m / sec. + + | **Related:** :term:`Cube` + | **More information:** :doc:`iris_cubes` + | + + Xarray + A python library for sophisticated labelled multi-dimensional operations. + Has a broader scope than Iris - it is not focused on meteorological data. + + | **Related:** :term:`NumPy` + | **More information:** `Xarray Documentation `_ + | + +---- + +`To top `_ diff --git a/docs/src/userguide/index.rst b/docs/src/userguide/index.rst index 08923e7662..fdd0c4d03e 100644 --- a/docs/src/userguide/index.rst +++ b/docs/src/userguide/index.rst @@ -35,6 +35,7 @@ they may serve as a useful reference for future exploration. cube_maths citation code_maintenance + glossary .. toctree:: diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d2eadb17d6..90614c8736 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -97,6 +97,7 @@ This document explains the changes made to Iris for this release #. `@ESadek-MO`_, `@TTV-Intrepid`_ and `@trexfeathers`_ added a gallery example for zonal means plotted parallel to a cartographic plot. (:pull:`4871`) +#. `@Esadek-MO`_ added a key-terms :doc:`glossary` page into the user guide. (:pull:`4902`) 💼 Internal From 04e757fe9e31f1fa7918ba12c760babdf61e6485 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 28 Sep 2022 16:01:52 +0100 Subject: [PATCH 239/319] Adaptions for Matplotlib 3.6 (#4998) * Change deprecated MPL colormap registration - matplotlib/matplotlib#23668. * Adapt benchmark for importing palette to cope with MPL 3.6. * What's New entry. * What's New typo. * Reformat import_iris imports. --- benchmarks/benchmarks/import_iris.py | 31 ++++++++++++++++++++++++++-- docs/src/whatsnew/latest.rst | 9 ++++++++ lib/iris/palette.py | 3 ++- requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.cfg | 2 +- 7 files changed, 44 insertions(+), 7 deletions(-) diff --git a/benchmarks/benchmarks/import_iris.py b/benchmarks/benchmarks/import_iris.py index ad54c23122..fc32ac289b 100644 --- a/benchmarks/benchmarks/import_iris.py +++ b/benchmarks/benchmarks/import_iris.py @@ -5,10 +5,30 @@ # licensing details. from importlib import import_module, reload +################ +# Prepare info for reset_colormaps: + +# Import and capture colormaps. +from matplotlib import colormaps # isort:skip + +_COLORMAPS_ORIG = set(colormaps) + +# Import iris.palette, which modifies colormaps. +import iris.palette + +# Derive which colormaps have been added by iris.palette. +_COLORMAPS_MOD = set(colormaps) +COLORMAPS_EXTRA = _COLORMAPS_MOD - _COLORMAPS_ORIG + +# Touch iris.palette to prevent linters complaining. +_ = iris.palette + +################ + class Iris: @staticmethod - def _import(module_name): + def _import(module_name, reset_colormaps=False): """ Have experimented with adding sleep() commands into the imported modules. The results reveal: @@ -25,6 +45,13 @@ def _import(module_name): and the repetitions are therefore no faster than the first run. """ mod = import_module(module_name) + + if reset_colormaps: + # Needed because reload() will attempt to register new colormaps a + # second time, which errors by default. + for cm_name in COLORMAPS_EXTRA: + colormaps.unregister(cm_name) + reload(mod) def time_iris(self): @@ -205,7 +232,7 @@ def time_iterate(self): self._import("iris.iterate") def time_palette(self): - self._import("iris.palette") + self._import("iris.palette", reset_colormaps=True) def time_plot(self): self._import("iris.plot") diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 90614c8736..d579ddcce7 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -83,14 +83,20 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ introduced the ``dask >=2.26`` minimum pin, so that Iris can benefit from Dask's support for `NEP13`_ and `NEP18`_. (:pull:`4905`) + #. `@trexfeathers`_ advanced the Cartopy pin to ``>=0.21``, as Cartopy's change to default Transverse Mercator projection affects an Iris test. See `SciTools/cartopy@fcb784d`_ and `SciTools/cartopy@8860a81`_ for more details. (:pull:`4968`) + #. `@trexfeathers`_ introduced the ``netcdf4!=1.6.1`` pin to avoid a problem with segfaults. (:pull:`4968`) +#. `@trexfeathers`_ updated the Matplotlib colormap registration in + :mod:`iris.palette` in response to a deprecation warning. Using the new + Matplotlib API also means a ``matplotlib>=3.5`` pin. (:pull:`4998`) + 📚 Documentation ================ @@ -115,6 +121,9 @@ This document explains the changes made to Iris for this release :mod:`~iris.fileformats.netcdf.loader` and :mod:`~iris.fileformats.netcdf.saver` submodules, just to make the code easier to handle. +#. `@trexfeathers`_ adapted the benchmark for importing :mod:`iris.palette` to + cope with new colormap behaviour in Matplotlib `v3.6`. (:pull:`4998`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/palette.py b/lib/iris/palette.py index 626ae4e341..5aa30a6b4e 100644 --- a/lib/iris/palette.py +++ b/lib/iris/palette.py @@ -15,6 +15,7 @@ import re import cf_units +from matplotlib import colormaps as mpl_colormaps import matplotlib.cm as mpl_cm import matplotlib.colors as mpl_colors import numpy as np @@ -337,7 +338,7 @@ def _load_palette(): ) # Register the color map for use. - mpl_cm.register_cmap(cmap=cmap) + mpl_colormaps.register(cmap) # Ensure to load the color map palettes. diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 76ca9e4f58..ae0090881d 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -15,7 +15,7 @@ dependencies: - cf-units >=3.1 - cftime >=1.5 - dask-core >=2.26 - - matplotlib + - matplotlib >=3.5 - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 5a8c878ee1..c0ed574c92 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -15,7 +15,7 @@ dependencies: - cf-units >=3.1 - cftime >=1.5 - dask-core >=2.26 - - matplotlib + - matplotlib >=3.5 - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 7931e20336..c79b0ede1d 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -15,7 +15,7 @@ dependencies: - cf-units >=3.1 - cftime >=1.5 - dask-core >=2.26 - - matplotlib + - matplotlib >=3.5 - netcdf4 !=1.6.1 - numpy >=1.19 - python-xxhash diff --git a/setup.cfg b/setup.cfg index 92cbe4747c..ca35a8eb4e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,7 +51,7 @@ install_requires = cf-units>=3.1 cftime>=1.5.0 dask[array]>=2.26 - matplotlib + matplotlib>=3.5 netcdf4!=1.6.1 numpy>=1.19 scipy From 6829e61bfcf5f5e04061811c1fc959770bd99d80 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Thu, 29 Sep 2022 10:35:16 +0100 Subject: [PATCH 240/319] Update whatsnew for 3.3.1 release (#5002) * set whatsnew release date * fix whatsnew --- docs/src/whatsnew/3.3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/3.3.rst b/docs/src/whatsnew/3.3.rst index 49e222a494..c2e47f298a 100644 --- a/docs/src/whatsnew/3.3.rst +++ b/docs/src/whatsnew/3.3.rst @@ -31,8 +31,8 @@ This document explains the changes made to Iris for this release any issues or feature requests for improving Iris. Enjoy! -v3.3.1 |build_date| [unreleased] -================================ +v3.3.1 (29 Sep 2022) +==================== .. dropdown:: :opticon:`alert` v3.3.1 Patches :container: + shadow From da03d73057a9f9de5760ecaeb75a3da054b8ca1d Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 29 Sep 2022 14:19:38 +0100 Subject: [PATCH 241/319] Fix benchmarks `netcdf` import (#5001) * Enable manual GHA benchmark run with custom first_commit. * Modified benchmark netcdf import - SciTools/iris#4803. --- .github/workflows/benchmark.yml | 13 ++++++++++++- benchmarks/benchmarks/generate_data/__init__.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b197b58e80..d9ebbbae01 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -6,6 +6,12 @@ on: schedule: # Runs every day at 23:00. - cron: "0 23 * * *" + workflow_dispatch: + inputs: + first_commit: + description: "Argument to be passed to the overnight benchmark script." + required: false + type: string jobs: benchmark: @@ -64,7 +70,12 @@ jobs: - name: Run overnight benchmarks run: | - first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1) + first_commit=${{ inputs.first_commit }} + if [ "$first_commit" != "" ] + then + first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1) + fi + if [ "$first_commit" != "" ] then nox --session="benchmarks(overnight)" -- $first_commit diff --git a/benchmarks/benchmarks/generate_data/__init__.py b/benchmarks/benchmarks/generate_data/__init__.py index 78b971d9de..52a5aceca8 100644 --- a/benchmarks/benchmarks/generate_data/__init__.py +++ b/benchmarks/benchmarks/generate_data/__init__.py @@ -113,7 +113,7 @@ def load_realised(): file loading, but some benchmarks are only meaningful if starting with real arrays. """ - from iris.fileformats.netcdf import _get_cf_var_data as pre_patched + from iris.fileformats.netcdf.loader import _get_cf_var_data as pre_patched def patched(cf_var, filename): return as_concrete_data(pre_patched(cf_var, filename)) From 27422111952911cb3e9458368f0337213ea43d85 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Thu, 29 Sep 2022 17:23:17 +0200 Subject: [PATCH 242/319] Speed up operations that use the `Coord.cells` method for time coordinates (#4969) * Convert all time points at once before generating cells * Small simplification * Add a whatnew entry * Fix link Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/coords.py | 33 ++++++++++++++++----------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d579ddcce7..9c9640f4de 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -71,6 +71,10 @@ This document explains the changes made to Iris for this release :obj:`~iris.analysis.SUM`, :obj:`~iris.analysis.COUNT` and :obj:`~iris.analysis.PROPORTION` on real data. (:pull:`4905`) +#. `@bouweandela`_ made :meth:`iris.coords.Coord.cells` faster for time + coordinates. This also affects :meth:`iris.cube.Cube.extract`, + :meth:`iris.cube.Cube.subset`, and :meth:`iris.coords.Coord.intersect`. + (:pull:`4969`) 🔥 Deprecations =============== diff --git a/lib/iris/coords.py b/lib/iris/coords.py index d0d471a634..ba0ed8ee5d 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -1895,7 +1895,22 @@ def cells(self): ... """ - return _CellIterator(self) + if self.ndim != 1: + raise iris.exceptions.CoordinateMultiDimError(self) + + points = self.points + bounds = self.bounds + if self.units.is_time_reference(): + points = self.units.num2date(points) + if self.has_bounds(): + bounds = self.units.num2date(bounds) + + if self.has_bounds(): + for point, bound in zip(points, bounds): + yield Cell(point, bound) + else: + for point in points: + yield Cell(point) def _sanity_check_bounds(self): if self.ndim == 1: @@ -3137,22 +3152,6 @@ def xml_element(self, doc): return cellMethod_xml_element -# See Coord.cells() for the description/context. -class _CellIterator(Iterator): - def __init__(self, coord): - self._coord = coord - if coord.ndim != 1: - raise iris.exceptions.CoordinateMultiDimError(coord) - self._indices = iter(range(coord.shape[0])) - - def __next__(self): - # NB. When self._indices runs out it will raise StopIteration for us. - i = next(self._indices) - return self._coord.cell(i) - - next = __next__ - - # See ExplicitCoord._group() for the description/context. class _GroupIterator(Iterator): def __init__(self, points): From 24ff3bd95a0c767eddbaabe1008a2e8f4651cdc5 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 29 Sep 2022 16:43:09 +0100 Subject: [PATCH 243/319] Correct wrong type of equality check in benchmark GHA. (#5005) --- .github/workflows/benchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d9ebbbae01..c0baabe572 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -71,7 +71,7 @@ jobs: - name: Run overnight benchmarks run: | first_commit=${{ inputs.first_commit }} - if [ "$first_commit" != "" ] + if [ "$first_commit" == "" ] then first_commit=$(git log --after="$(date -d "1 day ago" +"%Y-%m-%d") 23:00:00" --pretty=format:"%h" | tail -n 1) fi From 04122c03325663058c68e7676060b3db44240c9a Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Thu, 29 Sep 2022 17:05:15 +0100 Subject: [PATCH 244/319] remove outline workaround (#4999) --- docs/src/whatsnew/latest.rst | 2 ++ lib/iris/plot.py | 5 ----- lib/iris/tests/test_mapping.py | 6 ------ 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9c9640f4de..a7d66382cb 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -128,6 +128,8 @@ This document explains the changes made to Iris for this release #. `@trexfeathers`_ adapted the benchmark for importing :mod:`iris.palette` to cope with new colormap behaviour in Matplotlib `v3.6`. (:pull:`4998`) +#. `@rcomer`_ removed a now redundant workaround for an old matplotlib bug, + highlighted by :issue:`4090`. (:pull:`4999`) .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 2da91e8c67..4acb38b859 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -1344,11 +1344,6 @@ def outline(cube, coords=None, color="k", linewidth=None, axes=None): axes=axes, ) - # set the _is_stroked property to get a single color grid. - # See https://github.com/matplotlib/matplotlib/issues/1302 - result._is_stroked = False - if hasattr(result, "_wrapped_collection_fix"): - result._wrapped_collection_fix._is_stroked = False return result diff --git a/lib/iris/tests/test_mapping.py b/lib/iris/tests/test_mapping.py index a71385b5bc..202c319b61 100644 --- a/lib/iris/tests/test_mapping.py +++ b/lib/iris/tests/test_mapping.py @@ -242,12 +242,6 @@ def test_pcolormesh(self): iplt.pcolormesh(self.cube) self.check_graphic() - def test_grid(self): - iplt.pcolormesh(self.cube, facecolors="none", edgecolors="blue") - # the result is a graphic which has coloured edges. This is a mpl bug, - # see https://github.com/matplotlib/matplotlib/issues/1302 - self.check_graphic() - def test_outline(self): iplt.outline(self.cube) self.check_graphic() From 7ba620e5fa3e2c9821143253f18014729fd524a8 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 3 Oct 2022 09:55:02 +0100 Subject: [PATCH 245/319] Updated environment lockfiles (#5008) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 44 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 32 +++++++------- requirements/ci/nox.lock/py39-linux-64.lock | 34 +++++++-------- 3 files changed, 55 insertions(+), 55 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index b73b8af3da..90fad89bf7 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 9bcbc5c76124fc238f88ac16184aebeb8fac11fe9d4df03e70a7f50e2d24aa9f +# input_hash: 5bb7d18990d558bee14aa553199bb8ef5e346b16214bf8df00042789e3f420e8 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -12,7 +12,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022d-h191b570_0.tar.bz2#456b5b1d99e7a9654b331bcd82e71042 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab @@ -39,7 +39,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 @@ -54,7 +54,6 @@ https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -75,7 +74,6 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c @@ -83,9 +81,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -93,15 +92,12 @@ https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 @@ -115,21 +111,24 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bd https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 @@ -154,7 +153,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -180,7 +179,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.b https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b @@ -211,8 +210,8 @@ https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_0.tar https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py310hbf28c38_0.tar.bz2#85565efb2bf44e8a5782e7c418d30cfe https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py310h5764c6d_0.tar.bz2#e12fa8a9fee03765d98a93234ef5a901 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py310h5764c6d_0.tar.bz2#46eb2017ab2148b1f28334b07510f0e5 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 @@ -229,29 +228,30 @@ https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py310h8d5ebf3_0.tar.bz2#001fdef689e7cbcbbce6d5a6ebee90b6 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_0.tar.bz2#3e81d6afa50895d6dee115ac5d34c2ea https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py310hff52083_0.tar.bz2#2db9d22cc226ef79d9cd87fc958c2b04 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py310hff52083_0.tar.bz2#2db9d22cc226ef79d9cd87fc958c2b04 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 096162793f..301fd659f3 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 34099f3b69d60b791c26fcde2961739ff7cb0f9c144a37335b9f2183abe0dda3 +# input_hash: a434c868ae6df31789fd61bd87bd1c2bc217fccf612eabfe62832dbf48266923 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -38,7 +38,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 @@ -53,7 +53,6 @@ https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -74,7 +73,6 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c @@ -82,9 +80,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -92,15 +91,12 @@ https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 @@ -112,9 +108,13 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 @@ -131,7 +131,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb @@ -142,7 +142,7 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 @@ -162,7 +162,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -213,11 +213,11 @@ https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_0.tar. https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py38h43d8883_0.tar.bz2#0650a251fd701bbe5ac44e74cf632af8 https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py38h0a891b7_0.tar.bz2#ff4c112a78161241ca8a7af74de6a50b +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py38h0a891b7_0.tar.bz2#401adaccd86738f95a247d2007d925cf https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py38h8f669ce_0.tar.bz2#f91da48c62c91659da28bd95559c75ff https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py38h578d9bd_0.tar.bz2#1fdabff56623511910fef3b418ff07a2 @@ -233,7 +233,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.ta https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py38hb021067_0.tar.bz2#315ee5c0fbee508e739ddfac2bf8f600 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 9d454a2569..da72a1bb4f 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,9 +1,9 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 44cf413042165b62fe105f738e80b926629f61c1763d74df419910081521225b +# input_hash: 52411479c54ecfa70b3c975c0d86da72b762d12fce2a094c134e69adca9f3f48 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.14-ha878542_0.tar.bz2#87c986dab320658abaf3e701406b665c +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -12,7 +12,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022c-h191b570_0.tar.bz2#a56386ad31a7322940dd7d03fb3a9979 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022d-h191b570_0.tar.bz2#456b5b1d99e7a9654b331bcd82e71042 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab @@ -39,7 +39,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 -https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.16-h516909a_0.tar.bz2#5c0f338a513a2943c659ae619fca9211 +https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 @@ -54,7 +54,6 @@ https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 -https://conda.anaconda.org/conda-forge/linux-64/pcre-8.45-h9c3ff4c_0.tar.bz2#c05d1820a6d34ff07aaaab7a9b7eddaa https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -75,7 +74,6 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.72.1-h2d90d5f_0.tar.bz2#ebeadbb5fbc44052eeb6f96a2136e3c2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c @@ -83,9 +81,10 @@ https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.9.14-h22db469_4.tar.bz2#aced7c1f4b4dbfea08e033c6ae97c53e +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -93,15 +92,12 @@ https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.72.1-h6239696_0.tar.bz2#a3a99cc33279091262bbc4f5ee7c4571 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 @@ -113,9 +109,13 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 @@ -132,7 +132,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_ https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.14-pyhd8ed1ab_0.tar.bz2#963e8ceccba45b5cf15f33906d5a20a1 +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb @@ -143,7 +143,7 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.72.1-h6239696_0.tar.bz2#1698b7684d3c6a4d1de2ab946f5b0fb5 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 @@ -163,7 +163,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.3.0-pyhd8ed1ab_1.tar.bz2#a64c8af7be7a6348c1d9e530f88fa4da +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -214,11 +214,11 @@ https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_0.tar. https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py39hf939315_0.tar.bz2#c9ff0dfb602033b1f1aaf323b58e04fa https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.3-py39hb9d737c_0.tar.bz2#21622fe576fcce5b861036e8d7282470 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py39hb9d737c_0.tar.bz2#10ba86e931afab1164deae6b954b5f0d https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_0.tar.bz2#247c70ce54beeb3e60def44061576821 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py39h4661b88_0.tar.bz2#ae807099430cd22b09b869b0536425b7 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py39hf3d152e_0.tar.bz2#b807481ba94ec32bc742f2fe775d0bff @@ -234,7 +234,7 @@ https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.ta https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.0-pyhd8ed1ab_0.tar.bz2#aee564f0021a2a0ab12239fbdd28e209 +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py39hf9fd14e_0.tar.bz2#bdc55b4069ab9d2f938525c4cf90def0 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 From 536ff518b38643aaa3aaf9e15a4c00b2fc43ced9 Mon Sep 17 00:00:00 2001 From: Elias <110238618+ESadek-MO@users.noreply.github.com> Date: Mon, 3 Oct 2022 16:40:06 +0100 Subject: [PATCH 246/319] Updated "glossary" entry to link to iris glossary (#5012) Instead of Python-Glossary --- docs/src/whatsnew/latest.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a7d66382cb..939690ccb4 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -107,7 +107,7 @@ This document explains the changes made to Iris for this release #. `@ESadek-MO`_, `@TTV-Intrepid`_ and `@trexfeathers`_ added a gallery example for zonal means plotted parallel to a cartographic plot. (:pull:`4871`) -#. `@Esadek-MO`_ added a key-terms :doc:`glossary` page into the user guide. (:pull:`4902`) +#. `@Esadek-MO`_ added a key-terms :ref:`glossary` page into the user guide. (:pull:`4902`) 💼 Internal @@ -146,4 +146,4 @@ This document explains the changes made to Iris for this release .. _NEP18: https://numpy.org/neps/nep-0018-array-function-protocol.html .. _pypa/setuptools#1684: https://github.com/pypa/setuptools/issues/1684 .. _SciTools/cartopy@fcb784d: https://github.com/SciTools/cartopy/commit/fcb784daa65d95ed9a74b02ca292801c02bc4108 -.. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e \ No newline at end of file +.. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e From b5245f676a4109bf6191b13d02ad4be0fbf386fa Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 5 Oct 2022 12:58:05 +0100 Subject: [PATCH 247/319] DOC: add show option to Makefiles (#5000) * DOC: add show option to Makefiles * fix typo Co-authored-by: Patrick Peglar Co-authored-by: Patrick Peglar --- docs/Makefile | 5 +++++ docs/src/Makefile | 6 +++++- .../developers_guide/contributing_documentation_full.rst | 5 ++++- docs/src/whatsnew/latest.rst | 4 ++++ 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index f4c8d0b7f4..47f3e740fa 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -55,3 +55,8 @@ linkcheck: echo "Running linkcheck in $$i..."; \ (cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) linkcheck); done +show: + @for i in $(SUBDIRS); do \ + echo "Running show in $$i..."; \ + (cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) show); done + diff --git a/docs/src/Makefile b/docs/src/Makefile index c693a2c900..37c2e9e3e6 100644 --- a/docs/src/Makefile +++ b/docs/src/Makefile @@ -16,7 +16,7 @@ PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . -.PHONY: help clean html html-noplot dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest +.PHONY: help clean html html-noplot dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest show help: @echo "Please use \`make ' where is one of" @@ -36,6 +36,7 @@ help: @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " show to open the built documentation in the default browser" clean: -rm -rf $(BUILDDIR) @@ -153,3 +154,6 @@ doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." + +show: + @python -c "import webbrowser; webbrowser.open_new_tab('file://$(shell pwd)/$(BUILDDIR)/html/index.html')" \ No newline at end of file diff --git a/docs/src/developers_guide/contributing_documentation_full.rst b/docs/src/developers_guide/contributing_documentation_full.rst index ac62a67373..41314e80ac 100755 --- a/docs/src/developers_guide/contributing_documentation_full.rst +++ b/docs/src/developers_guide/contributing_documentation_full.rst @@ -61,7 +61,10 @@ If you wish to run a full clean build you can run:: make clean make html -This is useful for a final test before committing your changes. +This is useful for a final test before committing your changes. Having built +the documentation, you can view them in your default browser via:: + + make show .. note:: In order to preserve a clean build for the html, all **warnings** have been promoted to be **errors** to ensure they are addressed. diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 939690ccb4..0cafd4e94e 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -131,6 +131,10 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ removed a now redundant workaround for an old matplotlib bug, highlighted by :issue:`4090`. (:pull:`4999`) +#. `@rcomer`_ added the ``show`` option to the documentation Makefiles, as a + convenient way for contributors to view their built documentation. + (:pull:`5000`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: From 959b5902db98775c285ca1a8421249188c459650 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 6 Oct 2022 11:49:40 +0100 Subject: [PATCH 248/319] Sample code to convert ORCA data into a meshcube. (#5013) * Sample code to convert ORCA data into a meshcube. * Better illustrative image. * Added whatsnew. --- .../further_topics/ugrid/images/orca_grid.png | Bin 0 -> 831244 bytes .../src/further_topics/ugrid/other_meshes.rst | 135 ++++++++++++++++++ docs/src/whatsnew/latest.rst | 2 + 3 files changed, 137 insertions(+) create mode 100644 docs/src/further_topics/ugrid/images/orca_grid.png diff --git a/docs/src/further_topics/ugrid/images/orca_grid.png b/docs/src/further_topics/ugrid/images/orca_grid.png new file mode 100644 index 0000000000000000000000000000000000000000..6676e84fbbd1a5c23c45e20d103688f73ab55436 GIT binary patch literal 831244 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

    |CXKd`A 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

    ;X8K2Mit84C78P^DT1ZAYi!81 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

    oh=NPbwT&Z*?TBn2_ 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 z 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 8cV`KvdP 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

    aKcq*&esLx(EMa5&t`o!cB&fa0_wg-EyYc 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ℑ1o3f^BnqYkDZ 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 z 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 M07*qoM6N<$g4Gqgj{pDw literal 0 HcmV?d00001 diff --git a/docs/src/further_topics/ugrid/other_meshes.rst b/docs/src/further_topics/ugrid/other_meshes.rst index e6f477624e..38abeeca03 100644 --- a/docs/src/further_topics/ugrid/other_meshes.rst +++ b/docs/src/further_topics/ugrid/other_meshes.rst @@ -221,5 +221,140 @@ as the **nodes** when creating the Iris + +.. _ORCA_example: + +`NEMO`_ data on ORCA tripolar grid +---------------------------------- +.. figure:: images/orca_grid.png + :width: 300 + :alt: Plot of ORCA-gridded data from NEMO. + +NEMO can use various grids, but is frequently used with ORCA type grids. +ORCA grids store global data in 2-dimensional ny * nx arrays. All cells are +four-sided. The grids are based on tri-polar layouts, but X and Y spacings are +irregular and not given by any defined functional forms. + +* arrays (ny, nx) of face-located data variables +* arrays (ny, nx) of X+Y face centre coordinates +* arrays (ny, nx, 4) of X+Y face corner coordinates + (all faces are quadrilaterals) + +For simplicity, we treat each face corner as an independent node, and use a face-node +connectivity which simply lists the nodes in sequence, +i.e. [[0, 1, 2, 3], [4, 5, 6, 7], ...]. + +.. Note:: + This is the simplest solution, but produces approx 4x more nodes than + necessary, since the coordinate bounds contain many duplicate locations. + Removing the duplicates is quite easy, but often not necessary. + +To make an unstructured cube, the data must be 'flattened' to convert the given X and Y +dimensions into a single mesh dimension. Since Iris cubes don't support a "reshape" or +"flatten" operations, we create a new cube from the flattened data. + +.. dropdown:: :opticon:`code` + + .. code-block:: python + + >>> import numpy as np + >>> import iris + >>> from iris.coords import AuxCoord, CellMeasure + >>> from iris.cube import Cube + >>> from iris.experimental.ugrid.mesh import Mesh, Connectivity + + + >>> filepath = iris.sample_data_path('orca2_votemper.nc') + >>> cube = iris.load_cube(filepath) + >>> print(cube) + sea_water_potential_temperature / (degC) (-- : 148; -- : 180) + Auxiliary coordinates: + latitude x x + longitude x x + Scalar coordinates: + depth 4.999938 m, bound=(0.0, 10.0) m + time 0001-01-01 12:00:00 + Cell methods: + mean time + Attributes: + Conventions 'CF-1.5' + + + >>> co_x = cube.coord("longitude") + >>> co_y = cube.coord("latitude") + >>> ny, nx = co_x.shape + >>> n_faces = ny * nx + + >>> # Create face coords from flattened face-points + >>> face_x_co = AuxCoord(co_x.points.flatten()) + >>> face_y_co = AuxCoord(co_y.points.flatten()) + >>> assert face_x_co.shape == (n_faces,) + >>> face_x_co.metadata = co_x.metadata + >>> face_y_co.metadata = co_y.metadata + + >>> # Create node coordinates from bound points. + >>> n_nodes = n_faces * 4 + >>> node_x_co = AuxCoord(co_x.bounds.flatten()) + >>> node_y_co = AuxCoord(co_y.bounds.flatten()) + >>> assert node_x_co.shape == (n_nodes,) + >>> node_x_co.metadata = co_x.metadata + >>> node_y_co.metadata = co_y.metadata + + >>> # Create a face-node Connectivity matching the order of nodes in the bounds array + >>> face_node_inds = np.arange(n_nodes).reshape((n_faces, 4)) + >>> face_nodes_conn = Connectivity( + ... indices=face_node_inds, + ... cf_role='face_node_connectivity', + ... long_name='face_inds', units='1', + ... ) + + >>> # Create a mesh object. + >>> mesh = Mesh( + ... topology_dimension=2, + ... node_coords_and_axes=[(node_x_co, 'x'), (node_y_co, 'y')], + ... connectivities=face_nodes_conn, + ... face_coords_and_axes=[(face_x_co, 'x'), (face_y_co, 'y')] + ... ) + >>> print(mesh) + Mesh : 'unknown' + topology_dimension: 2 + node + node_dimension: 'Mesh2d_node' + node coordinates + + + face + face_dimension: 'Mesh2d_face' + face_node_connectivity: + face coordinates + + + + + >>> # Create an unstructured version of the input with flattened data + >>> meshcube = Cube(cube.core_data().flatten()) + >>> meshcube.metadata = cube.metadata + + >>> # Attach the mesh by adding the mesh 'face' MeshCoords into the cube + >>> mesh_dim = meshcube.ndim - 1 + >>> for co in mesh.to_MeshCoords('face'): + ... meshcube.add_aux_coord(co, mesh_dim) + ... + + >>> print(meshcube) + sea_water_potential_temperature / (degC) (-- : 26640) + Mesh coordinates: + latitude x + longitude x + Mesh: + name unknown + location face + Cell methods: + mean time + Attributes: + Conventions 'CF-1.5' + + .. _WAVEWATCH III: https://github.com/NOAA-EMC/WW3 .. _FESOM 1.4: https://fesom.de/models/fesom14/ +.. _NEMO: https://www.nemo-ocean.eu/ \ No newline at end of file diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 0cafd4e94e..f016b1e092 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -108,6 +108,8 @@ This document explains the changes made to Iris for this release #. `@ESadek-MO`_, `@TTV-Intrepid`_ and `@trexfeathers`_ added a gallery example for zonal means plotted parallel to a cartographic plot. (:pull:`4871`) #. `@Esadek-MO`_ added a key-terms :ref:`glossary` page into the user guide. (:pull:`4902`) +#. `@pp-mo`_ added a :ref:`code example ` + for converting ORCA-gridded data to an unstructured cube. (:pull:`5013`) 💼 Internal From bae9953c56f532aedc4bbd680a171afd1ec3b9e1 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 7 Oct 2022 10:50:17 +0100 Subject: [PATCH 249/319] New LBFC-CF mappings (#4859) * New LBFC-CF mappings based on info from Julian Heming. * Further mappings. * What's New entry. --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/fileformats/um_cf_map.py | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f016b1e092..d14d7b2452 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -35,6 +35,9 @@ This document explains the changes made to Iris for this release non-existing paths, and added expansion functionality to :func:`~iris.io.save`. (:issue:`4772`, :pull:`4913`) +#. `@trexfeathers`_ and `Julian Heming`_ added new mappings between CF + standard names and UK Met Office LBFC codes. (:pull:`4859`) + 🐛 Bugs Fixed ============= @@ -142,6 +145,7 @@ This document explains the changes made to Iris for this release core dev names are automatically included by the common_links.inc: .. _@TTV-Intrepid: https://github.com/TTV-Intrepid +.. _Julian Heming: https://www.metoffice.gov.uk/research/people/julian-heming diff --git a/lib/iris/fileformats/um_cf_map.py b/lib/iris/fileformats/um_cf_map.py index 8aee67ae3e..9091507668 100644 --- a/lib/iris/fileformats/um_cf_map.py +++ b/lib/iris/fileformats/um_cf_map.py @@ -16,10 +16,12 @@ LBFC_TO_CF = { 5: CFName('atmosphere_boundary_layer_thickness', None, 'm'), 16: CFName('air_temperature', None, 'K'), + 22: CFName('wet_bulb_potential_temperature', None, 'K'), 23: CFName('soil_temperature', None, 'K'), 27: CFName('air_density', None, 'kg m-3'), 36: CFName('land_area_fraction', None, '1'), 37: CFName('sea_ice_area_fraction', None, '1'), + 42: CFName('upward_air_velocity', None, 'm s-1'), 50: CFName('wind_speed', None, 'm s-1'), 56: CFName('x_wind', None, 'm s-1'), 57: CFName('y_wind', None, 'm s-1'), @@ -28,11 +30,16 @@ 83: CFName('potential_vorticity_of_atmosphere_layer', None, 'Pa-1 s-1'), 94: CFName('convective_rainfall_amount', None, 'kg m-2'), 97: CFName('rainfall_flux', None, 'kg m-2 s-1'), + 98: CFName('convective_rainfall_flux', None, 'kg m-2 s-1'), + 99: CFName('stratiform_rainfall_flux', None, 'kg m-2 s-1'), 102: CFName('stratiform_rainfall_amount', None, 'kg m-2'), + 106: CFName('soil_moisture_content', None, 'kg m-2'), 108: CFName('snowfall_flux', None, 'kg m-2 s-1'), 111: CFName('surface_runoff_amount', None, 'kg m-2'), 116: CFName('stratiform_snowfall_amount', None, 'kg m-2'), 117: CFName('convective_snowfall_amount', None, 'kg m-2'), + 118: CFName('stratiform_snowfall_flux', None, 'kg m-2 s-1'), + 119: CFName('convective_snowfall_flux', None, 'kg m-2 s-1'), 122: CFName('moisture_content_of_soil_layer', None, 'kg m-2'), 183: CFName('wind_speed', None, 'm s-1'), 200: CFName('toa_incoming_shortwave_flux', None, 'W m-2'), @@ -1157,7 +1164,9 @@ CFName('cloud_area_fraction_in_atmosphere_layer', None, '1'): 1720, CFName('convective_cloud_area_fraction', None, '1'): 34, CFName('convective_rainfall_amount', None, 'kg m-2'): 94, + CFName('convective_rainfall_flux', None, 'kg m-2 s-1'): 98, CFName('convective_snowfall_amount', None, 'kg m-2'): 117, + CFName('convective_snowfall_flux', None, 'kg m-2 s-1'): 119, CFName('dimensionless_exner_function', None, '1'): 7, CFName('divergence_of_wind', None, 's-1'): 74, CFName('downward_heat_flux_in_sea_ice', None, 'W m-2'): 261, @@ -1203,6 +1212,7 @@ CFName('soil_albedo', None, '1'): 1395, CFName('soil_carbon_content', None, 'kg m-2'): 1397, CFName('soil_hydraulic_conductivity_at_saturation', None, 'm s-1'): 333, + CFName('soil_moisture_content', None, 'kg m-2'): 106, CFName('soil_moisture_content_at_field_capacity', None, 'kg m-2'): 1559, CFName('soil_porosity', None, '1'): 332, CFName('soil_suction_at_saturation', None, 'Pa'): 342, @@ -1212,8 +1222,10 @@ CFName('specific_kinetic_energy_of_air', None, 'm2 s-2'): 60, CFName('stratiform_cloud_area_fraction_in_atmosphere_layer', None, '1'): 220, CFName('stratiform_rainfall_amount', None, 'kg m-2'): 102, + CFName('stratiform_rainfall_flux', None, 'kg m-2 s-1'): 99, CFName('stratiform_rainfall_rate', None, 'kg m-2 s-1'): 99, CFName('stratiform_snowfall_amount', None, 'kg m-2'): 116, + CFName('stratiform_snowfall_flux', None, 'kg m-2 s-1'): 118, CFName('subsurface_runoff_amount', None, 'kg m-2'): 112, CFName('subsurface_runoff_flux', None, 'kg m-2 s-1'): 1533, CFName('surface_albedo_assuming_deep_snow', None, '1'): 328, @@ -1260,6 +1272,7 @@ CFName('volume_fraction_of_condensed_water_in_soil_at_critical_point', None, '1'): 330, CFName('volume_fraction_of_condensed_water_in_soil_at_wilting_point', None, '1'): 329, CFName('water_potential_evaporation_flux', None, 'kg m-2 s-1'): 115, + CFName('wet_bulb_potential_temperature', None, 'K'): 22, CFName('wind_mixing_energy_flux_into_sea_water', None, 'W m-2'): 182, CFName('wind_speed', None, 'm s-1'): 50, CFName('x_wind', None, 'm s-1'): 56, From d1f3e4564e09de1e269872f5d14d4d3bf3ce5489 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 7 Oct 2022 11:51:37 +0100 Subject: [PATCH 250/319] Cell comparison: remove ancient netcdftime/cftime workarounds (#4729) * remove ancient workarounds * whatsnew --- docs/src/whatsnew/latest.rst | 3 +++ lib/iris/coords.py | 25 +----------------- lib/iris/tests/unit/coords/test_Cell.py | 34 +++---------------------- 3 files changed, 7 insertions(+), 55 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d14d7b2452..f4cd3f44bd 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -60,6 +60,9 @@ This document explains the changes made to Iris for this release variables and cell measures that map to a cube dimension of length 1 are now included in the respective vector sections. (:pull:`4945`) +#. `@rcomer`_ removed some old redundant code that prevented determining the + order of time cells. (:issue:`4697`, :pull:`4729`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/coords.py b/lib/iris/coords.py index ba0ed8ee5d..bf802b1540 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -18,7 +18,6 @@ import warnings import zlib -import cftime import dask.array as da import numpy as np import numpy.ma as ma @@ -1411,16 +1410,6 @@ def __common_cmp__(self, other, operator_method): ): raise ValueError("Unexpected operator_method") - # Prevent silent errors resulting from missing cftime - # behaviour. - if isinstance(other, cftime.datetime) or ( - isinstance(self.point, cftime.datetime) - and not isinstance(other, iris.time.PartialDateTime) - ): - raise TypeError( - "Cannot determine the order of " "cftime.datetime objects" - ) - if isinstance(other, Cell): # Cell vs Cell comparison for providing a strict sort order if self.bound is None: @@ -1485,19 +1474,7 @@ def __common_cmp__(self, other, operator_method): else: me = max(self.bound) - # Work around to handle cftime.datetime comparison, which - # doesn't return NotImplemented on failure in some versions of the - # library - try: - result = operator_method(me, other) - except TypeError: - rop = { - operator.lt: operator.gt, - operator.gt: operator.lt, - operator.le: operator.ge, - operator.ge: operator.le, - }[operator_method] - result = rop(other, me) + result = operator_method(me, other) return result diff --git a/lib/iris/tests/unit/coords/test_Cell.py b/lib/iris/tests/unit/coords/test_Cell.py index 650f9ded6c..81370bd0de 100644 --- a/lib/iris/tests/unit/coords/test_Cell.py +++ b/lib/iris/tests/unit/coords/test_Cell.py @@ -30,33 +30,6 @@ def assert_raises_on_comparison(self, cell, other, exception_type, regexp): with self.assertRaisesRegex(exception_type, regexp): cell >= other - def test_cftime_cell(self): - # Check that cell comparison when the cell contains - # cftime.datetime objects raises an exception otherwise - # this will fall back to id comparison producing unreliable - # results. - cell = Cell(cftime.datetime(2010, 3, 21)) - dt = mock.Mock(timetuple=mock.Mock()) - self.assert_raises_on_comparison( - cell, dt, TypeError, "determine the order of cftime" - ) - self.assert_raises_on_comparison( - cell, 23, TypeError, "determine the order of cftime" - ) - self.assert_raises_on_comparison( - cell, "hello", TypeError, "Unexpected type.*str" - ) - - def test_cftime_other(self): - # Check that cell comparison to a cftime.datetime object - # raises an exception otherwise this will fall back to id comparison - # producing unreliable results. - dt = cftime.datetime(2010, 3, 21) - cell = Cell(mock.Mock(timetuple=mock.Mock())) - self.assert_raises_on_comparison( - cell, dt, TypeError, "determine the order of cftime" - ) - def test_PartialDateTime_bounded_cell(self): # Check that bounded comparisions to a PartialDateTime # raise an exception. These are not supported as they @@ -85,10 +58,9 @@ def test_PartialDateTime_unbounded_cell(self): def test_datetime_unbounded_cell(self): # Check that cell comparison works with datetimes. dt = datetime.datetime(2000, 6, 15) - cell = Cell(datetime.datetime(2000, 1, 1)) - # Note the absence of the inverse of these - # e.g. self.assertGreater(dt, cell). - # See http://bugs.python.org/issue8005 + cell = Cell(cftime.datetime(2000, 1, 1)) + self.assertGreater(dt, cell) + self.assertGreaterEqual(dt, cell) self.assertLess(cell, dt) self.assertLessEqual(cell, dt) From 379c098bdb06331c2231f6e2e372f164605fe563 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 10:04:25 +0100 Subject: [PATCH 251/319] Updated environment lockfiles (#5018) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 28 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 28 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 28 ++++++++++---------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 90fad89bf7..e60113c475 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -49,7 +49,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar. https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_4.tar.bz2#6a2e5b333ba57ce7eec61e90260cbb79 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -77,7 +77,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 @@ -90,7 +90,7 @@ https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_4.tar.bz2#995cc7813221edbc25a3db15357599a0 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce @@ -104,7 +104,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -137,7 +137,7 @@ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.85.0-h7bff187_0.tar.bz2#054fb5981fdbe031caeb612b71d85f84 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 @@ -152,8 +152,8 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.4-pyhd8ed1ab_0.tar.bz2#fc0dcaf9761d042fb8ac9128ce03fddb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.1-pyhd8ed1ab_0.tar.bz2#d61d9f25af23c24002e659b854c6f5ae https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -166,7 +166,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 -https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 @@ -177,7 +177,7 @@ https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.85.0-h7bff187_0.tar.bz2#a8ac96d6b09b8ed5b0ac6563901e2195 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e @@ -202,14 +202,14 @@ https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d -https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_0.tar.bz2#e572565848d8d19e74983f4d122734a8 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_0.tar.bz2#6290f1bc763ed75a42aaea29384f9858 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py310hbf28c38_0.tar.bz2#85565efb2bf44e8a5782e7c418d30cfe -https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py310h597c629_0.tar.bz2#f285746449d16d92884f4ce0cfe26679 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.1-py310h597c629_0.tar.bz2#890771047730c6fa66a8926f0ca9bd13 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.2-pyhd8ed1ab_0.tar.bz2#9993f51a02fc8338837421211b53ab19 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py310h5764c6d_0.tar.bz2#46eb2017ab2148b1f28334b07510f0e5 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 @@ -229,12 +229,12 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.6-pyhd8ed1ab_0.tar.bz2#8c3563a8e310ea9a727f07caa0341ec2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py310h8d5ebf3_0.tar.bz2#001fdef689e7cbcbbce6d5a6ebee90b6 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 301fd659f3..d6301a12b7 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -48,7 +48,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar. https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_4.tar.bz2#6a2e5b333ba57ce7eec61e90260cbb79 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -76,7 +76,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 @@ -89,7 +89,7 @@ https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_4.tar.bz2#995cc7813221edbc25a3db15357599a0 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce @@ -102,7 +102,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -119,7 +119,7 @@ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.85.0-h7bff187_0.tar.bz2#054fb5981fdbe031caeb612b71d85f84 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc @@ -136,7 +136,7 @@ https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#eb https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.85.0-h7bff187_0.tar.bz2#a8ac96d6b09b8ed5b0ac6563901e2195 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 @@ -161,8 +161,8 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.4-pyhd8ed1ab_0.tar.bz2#fc0dcaf9761d042fb8ac9128ce03fddb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.1-pyhd8ed1ab_0.tar.bz2#d61d9f25af23c24002e659b854c6f5ae https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -175,7 +175,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 -https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a @@ -205,21 +205,21 @@ https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f -https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_0.tar.bz2#b2247bb2492e261c25fabbbb2c7a23b5 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_0.tar.bz2#df081ec90a13f53fe522c8e876d3f0cf https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py38h43d8883_0.tar.bz2#0650a251fd701bbe5ac44e74cf632af8 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py38h2b5fc30_0.tar.bz2#28e9acd6f13ed29f27d5550a1cf0554b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.1-py38h2b5fc30_0.tar.bz2#692d3e8efeb7f290daafa660ec7f1154 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.2-pyhd8ed1ab_0.tar.bz2#9993f51a02fc8338837421211b53ab19 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py38h0a891b7_0.tar.bz2#401adaccd86738f95a247d2007d925cf https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py38h8f669ce_0.tar.bz2#f91da48c62c91659da28bd95559c75ff -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py38h578d9bd_0.tar.bz2#1fdabff56623511910fef3b418ff07a2 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe @@ -232,7 +232,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.6-pyhd8ed1ab_0.tar.bz2#8c3563a8e310ea9a727f07caa0341ec2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py38hb021067_0.tar.bz2#315ee5c0fbee508e739ddfac2bf8f600 diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index da72a1bb4f..76de50e763 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -49,7 +49,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar. https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_3.tar.bz2#29b2d63b0e21b765da0418bc452538c9 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_4.tar.bz2#6a2e5b333ba57ce7eec61e90260cbb79 https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e @@ -77,7 +77,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.3-h753d276_0.tar.bz2#ccb2457c73609f2622b8a4b3e42e5d8b +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 @@ -90,7 +90,7 @@ https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_3.tar.bz2#76c717057865201aa2d24b79315645bb +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_4.tar.bz2#995cc7813221edbc25a3db15357599a0 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce @@ -103,7 +103,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.3-h4ff8645_0.tar.bz2#f03cf4ec974e32b6c5d349f62637e36e +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -120,7 +120,7 @@ https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.83.1-h7bff187_0.tar.bz2#d0c278476dba3b29ee13203784672ab1 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.85.0-h7bff187_0.tar.bz2#054fb5981fdbe031caeb612b71d85f84 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc @@ -137,7 +137,7 @@ https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#eb https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.83.1-h7bff187_0.tar.bz2#ba33b9995f5e691e4f439422d6efafc7 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.85.0-h7bff187_0.tar.bz2#a8ac96d6b09b8ed5b0ac6563901e2195 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 @@ -162,8 +162,8 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.2.1-pyhd8ed1ab_0.tar.bz2#974bca71d00364630f63f31fa7e059cb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.0-pyhd8ed1ab_0.tar.bz2#1fd586687cb05aac5655143c104df8f6 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.4-pyhd8ed1ab_0.tar.bz2#fc0dcaf9761d042fb8ac9128ce03fddb +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.1-pyhd8ed1ab_0.tar.bz2#d61d9f25af23c24002e659b854c6f5ae https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -176,7 +176,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 -https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.3.0-pyha770c72_0.tar.bz2#a9d85960bc62d53cc4ea0d1d27f73c98 +https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb @@ -206,21 +206,21 @@ https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd -https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.3.0-hd8ed1ab_0.tar.bz2#f3e98e944832fb271a0dbda7b7771dc6 +https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_0.tar.bz2#165e71a44187ac22e2e1669fd3ca2392 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_0.tar.bz2#4b108127973b66b36edd6449aa6afde0 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py39hf939315_0.tar.bz2#c9ff0dfb602033b1f1aaf323b58e04fa -https://conda.anaconda.org/conda-forge/linux-64/cryptography-37.0.4-py39hd97740a_0.tar.bz2#edc3668e7b71657237f94cf25e286478 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.1-pyhd8ed1ab_0.tar.bz2#68bb7f24f75b9691c42fd50e178749f5 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.1-py39hd97740a_0.tar.bz2#db3436b5db460fa721859db55694d8ff +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.2-pyhd8ed1ab_0.tar.bz2#9993f51a02fc8338837421211b53ab19 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py39hb9d737c_0.tar.bz2#10ba86e931afab1164deae6b954b5f0d https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py39h4661b88_0.tar.bz2#ae807099430cd22b09b869b0536425b7 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.10-hc4f8a73_0.tar.bz2#fead2b3178129155c334c751df4daba6 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py39hf3d152e_0.tar.bz2#b807481ba94ec32bc742f2fe775d0bff https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 @@ -233,7 +233,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.5-pyhd8ed1ab_0.tar.bz2#985ef0c4ed7a26731c419818080ef6ce +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.6-pyhd8ed1ab_0.tar.bz2#8c3563a8e310ea9a727f07caa0341ec2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py39hf9fd14e_0.tar.bz2#bdc55b4069ab9d2f938525c4cf90def0 From 6117f60704c5bc966d7d954c21dccbad88dfbe91 Mon Sep 17 00:00:00 2001 From: Bouwe Andela Date: Tue, 11 Oct 2022 18:04:26 +0200 Subject: [PATCH 252/319] Speed up Cube.subset/Coord.intersect (#4955) * Speed up Cube.subset/Coord.intersect * Add a whatsnew entry --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/coords.py | 21 +++++++++++++-------- lib/iris/tests/test_cell.py | 26 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f4cd3f44bd..b44e740724 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -82,6 +82,10 @@ This document explains the changes made to Iris for this release :meth:`iris.cube.Cube.subset`, and :meth:`iris.coords.Coord.intersect`. (:pull:`4969`) +#. `@bouweandela`_ improved the speed of :meth:`iris.cube.Cube.subset` / + :meth:`iris.coords.Coord.intersect`. + (:pull:`4955`) + 🔥 Deprecations =============== diff --git a/lib/iris/coords.py b/lib/iris/coords.py index bf802b1540..cfcdcd92a0 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -1344,7 +1344,14 @@ def __add__(self, mod): return Cell(point, bound) def __hash__(self): - return super().__hash__() + # See __eq__ for the definition of when two cells are equal. + if self.bound is None: + return hash(self.point) + bound = self.bound + rbound = bound[::-1] + if rbound < bound: + bound = rbound + return hash((self.point, bound)) def __eq__(self, other): """ @@ -2374,18 +2381,16 @@ def intersect(self, other, return_indices=False): ) raise ValueError(msg) - # Cache self.cells for speed. We can also use the index operation on a - # list conveniently. - self_cells = [cell for cell in self.cells()] + # Cache self.cells for speed. We can also use the dict for fast index + # lookup. + self_cells = {cell: idx for idx, cell in enumerate(self.cells())} # Maintain a list of indices on self for which cells exist in both self # and other. self_intersect_indices = [] for cell in other.cells(): - try: - self_intersect_indices.append(self_cells.index(cell)) - except ValueError: - pass + if cell in self_cells: + self_intersect_indices.append(self_cells[cell]) if return_indices is False and self_intersect_indices == []: raise ValueError( diff --git a/lib/iris/tests/test_cell.py b/lib/iris/tests/test_cell.py index 03d3fa7d7c..21d2603072 100644 --- a/lib/iris/tests/test_cell.py +++ b/lib/iris/tests/test_cell.py @@ -169,9 +169,35 @@ def test_coord_bounds_cmp(self): self.assertTrue(self.e < 2) def test_cell_cell_cmp(self): + self.e = iris.coords.Cell(1) + self.f = iris.coords.Cell(1) + + self.assertTrue(self.e == self.f) + self.assertEqual(hash(self.e), hash(self.f)) + + self.e = iris.coords.Cell(1) + self.f = iris.coords.Cell(1, [0, 2]) + + self.assertFalse(self.e == self.f) + self.assertNotEqual(hash(self.e), hash(self.f)) + + self.e = iris.coords.Cell(1, [0, 2]) + self.f = iris.coords.Cell(1, [0, 2]) + + self.assertTrue(self.e == self.f) + self.assertEqual(hash(self.e), hash(self.f)) + + self.e = iris.coords.Cell(1, [0, 2]) + self.f = iris.coords.Cell(1, [2, 0]) + + self.assertTrue(self.e == self.f) + self.assertEqual(hash(self.e), hash(self.f)) + self.e = iris.coords.Cell(0.7, [1.1, 1.9]) self.f = iris.coords.Cell(0.8, [1.1, 1.9]) + self.assertFalse(self.e == self.f) + self.assertNotEqual(hash(self.e), hash(self.f)) self.assertFalse(self.e > self.f) self.assertTrue(self.e <= self.f) self.assertTrue(self.f >= self.e) From ea67311f24ef0b039976adefb31007b7eece51cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 12 Oct 2022 09:24:37 +0100 Subject: [PATCH 253/319] [pre-commit.ci] pre-commit autoupdate (#5021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 22746cb0ee..b7746edb43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: no-commit-to-branch - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 22.10.0 hooks: - id: black pass_filenames: false From fd241df669bcc86b5dba80b282da9e958fe5c33a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 08:47:05 +0100 Subject: [PATCH 254/319] Bump peter-evans/create-pull-request from 4.1.3 to 4.2.0 (#5028) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.1.3 to 4.2.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/671dc9c9e0c2d73f07fa45a3eb0220e1622f0c5f...b4d51739f96fca8047ad065eccef63442d8e99f7) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/refresh-lockfiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index a45cccfeab..3bef604b41 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -91,7 +91,7 @@ jobs: - name: Create Pull Request id: cpr - uses: peter-evans/create-pull-request@671dc9c9e0c2d73f07fa45a3eb0220e1622f0c5f + uses: peter-evans/create-pull-request@b4d51739f96fca8047ad065eccef63442d8e99f7 with: token: ${{ steps.generate-token.outputs.token }} commit-message: Updated environment lockfiles From d56824cbc892356302c135ee23d704ff01e12d71 Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Fri, 21 Oct 2022 15:30:50 +0100 Subject: [PATCH 255/319] Meshcoord metadata follows face/edge coords where present. (#5020) * Meshcoords take metadata from face/edge coords. * Test fixes. * Added tests for missing-vs-present standard-names and units. * Slight code tidying. * Review changes. * Added whatsnew. --- docs/src/whatsnew/latest.rst | 9 + lib/iris/experimental/ugrid/mesh.py | 62 ++++- .../ugrid/2D_1t_face_half_levels.cml | 8 +- .../ugrid/2D_72t_face_half_levels.cml | 8 +- .../ugrid/3D_1t_face_full_levels.cml | 8 +- .../ugrid/3D_1t_face_half_levels.cml | 8 +- .../ugrid/3D_snow_pseudo_levels.cml | 8 +- .../ugrid/3D_soil_pseudo_levels.cml | 8 +- .../ugrid/3D_tile_pseudo_levels.cml | 8 +- .../ugrid/3D_veg_pseudo_levels.cml | 8 +- .../experimental/ugrid/surface_mean.cml | 144 ++++++------ .../collapse_all_dims.cml | 14 +- .../collapse_last_dims.cml | 14 +- .../collapse_middle_dim.cml | 7 +- .../collapse_zeroth_dim.cml | 7 +- .../TestBroadcastingWithMesh/slice.cml | 7 +- .../TestBroadcastingWithMesh/transposed.cml | 7 +- .../collapse_all_dims.cml | 14 +- .../collapse_last_dims.cml | 14 +- .../collapse_middle_dim.cml | 7 +- .../collapse_zeroth_dim.cml | 7 +- .../slice.cml | 7 +- .../transposed.cml | 7 +- .../unit/coords/test__DimensionalMetadata.py | 8 +- .../experimental/ugrid/mesh/test_MeshCoord.py | 219 +++++++++++++++--- 25 files changed, 366 insertions(+), 252 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index b44e740724..ea4b52ce2a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -38,6 +38,15 @@ This document explains the changes made to Iris for this release #. `@trexfeathers`_ and `Julian Heming`_ added new mappings between CF standard names and UK Met Office LBFC codes. (:pull:`4859`) +#. `@pp-mo`_ changed the metadata of a face/edge-type + :class:`~iris.experimental.ugrid.mesh.MeshCoord`, to be same as the face/edge + coordinate in the mesh from which it takes its ``.points``. Previously, all MeshCoords + took their metadata from the node coord, but only a node-type MeshCoord now does + that. Also, the MeshCoord ``.var_name`` is now that of the underlying coord, whereas + previously this was always None. These changes make MeshCoord more like an ordinary + :class:`~iris.coords.AuxCoord`, which avoids some specific known usage problems. + (:issue:`4860`, :pull:`5020`) + 🐛 Bugs Fixed ============= diff --git a/lib/iris/experimental/ugrid/mesh.py b/lib/iris/experimental/ugrid/mesh.py index 974a563046..4fd09175af 100644 --- a/lib/iris/experimental/ugrid/mesh.py +++ b/lib/iris/experimental/ugrid/mesh.py @@ -2841,16 +2841,60 @@ def __init__( # Get the 'coord identity' metadata from the relevant node-coordinate. node_coord = self.mesh.coord(include_nodes=True, axis=self.axis) + node_metadict = node_coord.metadata._asdict() + # Use node metadata, unless location is face/edge. + use_metadict = node_metadict.copy() + if location != "node": + # Location is either "edge" or "face" - get the relevant coord. + kwargs = {f"include_{location}s": True, "axis": axis} + location_coord = self.mesh.coord(**kwargs) + + # Take the MeshCoord metadata from the 'location' coord. + use_metadict = location_coord.metadata._asdict() + unit_unknown = Unit(None) + + # N.B. at present, coords in a Mesh are stored+accessed by 'axis', which + # means they must have a standard_name. So ... + # (a) the 'location' (face/edge) coord *always* has a useable phenomenon + # identity. + # (b) we still want to check that location+node coords have the same + # phenomenon (i.e. physical meaning identity + units), **but** ... + # (c) we will accept/ignore some differences : not just "var_name", but + # also "long_name" *and* "attributes". So it is *only* "standard_name" + # and "units" that cause an error if they differ. + for key in ("standard_name", "units"): + bounds_value = use_metadict[key] + nodes_value = node_metadict[key] + if key == "units" and ( + bounds_value == unit_unknown or nodes_value == unit_unknown + ): + # Allow "any" unit to match no-units (for now) + continue + if bounds_value != nodes_value: + + def fix_repr(val): + # Tidy values appearance by converting Unit to string, and + # wrapping strings in '', but leaving other types as a + # plain str() representation. + if isinstance(val, Unit): + val = str(val) + if isinstance(val, str): + val = repr(val) + return val + + nodes_value, bounds_value = [ + fix_repr(val) for val in (nodes_value, bounds_value) + ] + msg = ( + f"Node coordinate {node_coord!r} disagrees with the " + f"{location} coordinate {location_coord!r}, " + f'in having a "{key}" value of {nodes_value} ' + f"instead of {bounds_value}." + ) + raise ValueError(msg) + # Call parent constructor to handle the common constructor args. - super().__init__( - points, - bounds=bounds, - standard_name=node_coord.standard_name, - long_name=node_coord.long_name, - var_name=None, # We *don't* "represent" the underlying node var - units=node_coord.units, - attributes=node_coord.attributes, - ) + super().__init__(points, bounds=bounds, **use_metadict) # Define accessors for MeshCoord-specific properties mesh/location/axis. # These are all read-only. diff --git a/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml index b863adcf55..7422bfe044 100644 --- a/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/2D_1t_face_half_levels.cml @@ -20,8 +20,8 @@ ..., [-42.7342, -40.8934, -46.161, -48.912], [-40.8934, -38.4268, -42.6612, -46.161], - [-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791, - -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> diff --git a/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml index b46908a648..f9e0511ccb 100644 --- a/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/2D_72t_face_half_levels.cml @@ -20,8 +20,8 @@ ..., [-42.7342, -40.8934, -46.161, -48.912], [-40.8934, -38.4268, -42.6612, -46.161], - [-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791, - -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_full_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_full_levels_face_x"/> diff --git a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml index c260587921..9a819eee9e 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_1t_face_half_levels.cml @@ -31,8 +31,8 @@ ..., [-42.7342, -40.8934, -46.161, -48.912], [-40.8934, -38.4268, -42.6612, -46.161], - [-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791, - -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> diff --git a/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml index e545e05fdc..9133d98e73 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_snow_pseudo_levels.cml @@ -20,8 +20,8 @@ ..., [-42.7342, -40.8934, -46.161, -48.912], [-40.8934, -38.4268, -42.6612, -46.161], - [-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791, - -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> diff --git a/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml index 4eedfc21b3..05aeab9ccb 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_soil_pseudo_levels.cml @@ -20,8 +20,8 @@ ..., [-42.7342, -40.8934, -46.161, -48.912], [-40.8934, -38.4268, -42.6612, -46.161], - [-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791, - -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> diff --git a/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml index 55155047bb..9dc3e08ee6 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_tile_pseudo_levels.cml @@ -20,8 +20,8 @@ ..., [-42.7342, -40.8934, -46.161, -48.912], [-40.8934, -38.4268, -42.6612, -46.161], - [-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791, - -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> diff --git a/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml b/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml index fc52fce0b3..7bb47c5296 100644 --- a/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml +++ b/lib/iris/tests/results/experimental/ugrid/3D_veg_pseudo_levels.cml @@ -20,8 +20,8 @@ ..., [-42.7342, -40.8934, -46.161, -48.912], [-40.8934, -38.4268, -42.6612, -46.161], - [-38.4268, -35.2644, -38.4268, -42.6612]]" id="21594c35" long_name="Latitude of mesh nodes." points="[33.4328, 36.1226, 38.2012, ..., -44.791, - -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-38.4268, -35.2644, -38.4268, -42.6612]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[33.4328, 36.1226, 38.2012, ..., -44.791, + -42.1583, -38.815]" shape="(864,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [-127.5, -135.0, -142.5, -135.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[-41.3152, -33.8068, -26.296, ..., -119.377, + -127.321, -135.0]" shape="(864,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> diff --git a/lib/iris/tests/results/experimental/ugrid/surface_mean.cml b/lib/iris/tests/results/experimental/ugrid/surface_mean.cml index 368b3508e3..8ccd602c11 100644 --- a/lib/iris/tests/results/experimental/ugrid/surface_mean.cml +++ b/lib/iris/tests/results/experimental/ugrid/surface_mean.cml @@ -20,8 +20,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -71,8 +71,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -122,8 +122,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -173,8 +173,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -224,8 +224,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -275,8 +275,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -326,8 +326,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -377,8 +377,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -428,8 +428,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -479,8 +479,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -530,8 +530,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -581,8 +581,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -632,8 +632,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -683,8 +683,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -734,8 +734,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -785,8 +785,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -836,8 +836,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> @@ -887,8 +887,8 @@ ..., [-37.7044, -36.9373, -37.9318, -38.7655], [-36.9373, -36.1244, -37.0517, -37.9318], - [-36.1244, -35.2644, -36.1244, -37.0517]]" id="21594c35" long_name="Latitude of mesh nodes." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, - -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32"/> + [-36.1244, -35.2644, -36.1244, -37.0517]]" id="72da1058" long_name="Characteristic latitude of mesh faces." points="[34.8187, 35.6462, 36.4283, ..., -37.8421, + -37.0187, -36.1485]" shape="(13824,)" standard_name="latitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_y"/> + [226.875, 225.0, 223.125, 225.0]]" id="b5c6bdeb" long_name="Characteristic longitude of mesh faces." points="[315.933, 317.808, 319.683, ..., 228.759, + 226.878, 225.0]" shape="(13824,)" standard_name="longitude" units="Unit('degrees')" value_type="float32" var_name="Mesh2d_half_levels_face_x"/> diff --git a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml index 9fc80a0e4d..e318abad67 100644 --- a/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml +++ b/lib/iris/tests/results/unit/analysis/maths/_arith__meshcoords/TestBroadcastingWithMesh/collapse_all_dims.cml @@ -50,12 +50,7 @@ - - - - - - + - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> - - - - - + [1196, 1197, 1198, 1199]]" id="e0db29d6" points="[3100, 3101, 3102, ..., 3197, 3198, 3199]" shape="(100,)" standard_name="longitude" units="Unit('unknown')" value_type="int64"/> " ), - "MeshCoord : longitude / (degrees_east)", + "MeshCoord : longitude / (unknown)", " mesh: ", " location: 'face'", " points: [3100, 3101, 3102]", @@ -926,10 +926,6 @@ def test_meshcoord(self): " shape: (3,) bounds(3, 4)", " dtype: int64", " standard_name: 'longitude'", - " long_name: 'long-name'", - " attributes:", - " a 1", - " b 'c'", " axis: 'x'", ] self.assertLines(expected, result) diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py b/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py index ce99a8b4be..538cecdc7d 100644 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py +++ b/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py @@ -16,9 +16,10 @@ import dask.array as da import numpy as np +import pytest from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.common.metadata import BaseMetadata +from iris.common.metadata import BaseMetadata, CoordMetadata from iris.coords import AuxCoord, Coord from iris.cube import Cube from iris.experimental.ugrid.mesh import Connectivity, Mesh, MeshCoord @@ -45,16 +46,11 @@ def test_derived_properties(self): # underlying mesh coordinate. for axis in Mesh.AXES: meshcoord = sample_meshcoord(axis=axis) - # N.B. - node_x_coord = meshcoord.mesh.coord(include_nodes=True, axis=axis) - for key in node_x_coord.metadata._fields: + face_x_coord = meshcoord.mesh.coord(include_faces=True, axis=axis) + for key in face_x_coord.metadata._fields: meshval = getattr(meshcoord, key) - if key == "var_name": - # var_name is unused. - self.assertIsNone(meshval) - else: - # names, units and attributes are derived from the node coord. - self.assertEqual(meshval, getattr(node_x_coord, key)) + # All relevant attributes are derived from the face coord. + self.assertEqual(meshval, getattr(face_x_coord, key)) def test_fail_bad_mesh(self): with self.assertRaisesRegex(TypeError, "must be a.*Mesh"): @@ -270,10 +266,11 @@ def setUp(self): def _expected_elements_regexp( self, standard_name="longitude", - long_name="long-name", - attributes=True, + long_name=None, + attributes=False, location="face", axis="x", + var_name=None, ): # Printed name is standard or long -- we don't have a case with neither coord_name = standard_name or long_name @@ -282,24 +279,30 @@ def _expected_elements_regexp( regexp = f"MeshCoord : {coord_name} / [^\n]+\n *" regexp += r"mesh: \\n *" regexp += f"location: '{location}'\n *" + # Now some optional sections : whichever comes first will match # arbitrary content leading up to it. - matched_any_upto = False - if standard_name: - regexp += ".*" - matched_any_upto = True - regexp += f"standard_name: '{standard_name}'\n *" - if long_name: + matched_upto = False + + def upto_first_expected(regexp, matched_any_upto): if not matched_any_upto: regexp += ".*" matched_any_upto = True + return regexp, matched_any_upto + + if standard_name: + regexp, matched_upto = upto_first_expected(regexp, matched_upto) + regexp += f"standard_name: '{standard_name}'\n *" + if long_name: + regexp, matched_upto = upto_first_expected(regexp, matched_upto) regexp += f"long_name: '{long_name}'\n *" + if var_name: + regexp, matched_upto = upto_first_expected(regexp, matched_upto) + regexp += f"var_name: '{var_name}'\n *" if attributes: # if we expected attributes, they should come next # TODO: change this when each attribute goes on a new line - if not matched_any_upto: - regexp += ".*" - matched_any_upto = True + regexp, matched_upto = upto_first_expected(regexp, matched_upto) # match 'attributes:' followed by N*lines with larger indent regexp += "attributes:(\n [^ \n]+ +[^ \n]+)+\n " # After those items, expect 'axis' next @@ -314,7 +317,7 @@ def test_repr(self): # A simple check for the condensed form. result = repr(self.meshcoord) expected = ( - "" ) self.assertEqual(expected, result) @@ -331,7 +334,7 @@ def test_repr_lazy(self): self.assertTrue(self.meshcoord.has_lazy_bounds()) expected = ( - "+bounds shape(3,)>" ) self.assertEqual(expected, result) @@ -342,7 +345,7 @@ def test_repr__nameless_mesh(self): assert self.mesh.name() == "unknown" result = repr(self.meshcoord) re_expected = ( - r".MeshCoord: longitude / \(degrees_east\) " + r".MeshCoord: longitude / \(unknown\) " r"mesh\(.Mesh object at 0x[^>]+.\) location\(face\) " ) self.assertRegex(result, re_expected) @@ -392,18 +395,6 @@ def test_str_no_long_name(self): re_expected = self._expected_elements_regexp(long_name=False) self.assertRegex(result, re_expected) - def test_str_no_standard_name(self): - mesh = self.mesh - # Remove the standard_name of the node coord in the mesh. - node_coord = mesh.coord(include_nodes=True, axis="x") - node_coord.standard_name = None - node_coord.axis = "x" # This is required : but it's a kludge !! - # Make a new meshcoord, based on the modified mesh. - meshcoord = sample_meshcoord(mesh=self.mesh) - result = str(meshcoord) - re_expected = self._expected_elements_regexp(standard_name=False) - self.assertRegex(result, re_expected) - def test_str_no_attributes(self): mesh = self.mesh # No attributes on the node coord in the mesh. @@ -451,9 +442,11 @@ def test_cube_dims(self): def test_find_by_name(self): meshcoord = self.meshcoord + # hack to give it a long name + meshcoord.long_name = "odd_case" cube = self.cube self.assertIs(cube.coord(standard_name="longitude"), meshcoord) - self.assertIs(cube.coord(long_name="long-name"), meshcoord) + self.assertIs(cube.coord(long_name="odd_case"), meshcoord) def test_find_by_axis(self): meshcoord = self.meshcoord @@ -796,5 +789,157 @@ def test_bounds_badvalues__lazy(self): self._check_bounds_bad_index_values(lazy=True) +class Test__metadata: + def setup_mesh(self, location, axis): + # Create a standard test mesh + attach it to the test instance. + mesh = sample_mesh() + + # Modify the metadata of specific coordinates used in this test. + def select_coord(location, axis): + kwargs = {f"include_{location}s": True, "axis": axis} + return mesh.coord(**kwargs) + + node_coord = select_coord("node", axis) + location_coord = select_coord(location, axis) + for i_place, coord in enumerate((node_coord, location_coord)): + coord.standard_name = "longitude" if axis == "x" else "latitude" + coord.units = "degrees" + coord.long_name = f"long_name_{i_place}" + coord.var_name = f"var_name_{i_place}" + coord.attributes = {"att": i_place} + + # attach all the relevant testcase context to the test instance. + self.mesh = mesh + self.location = location + self.axis = axis + self.location_coord = location_coord + self.node_coord = node_coord + + def coord_metadata_matches(self, test_coord, ref_coord): + # Check that two coords match, in all the basic Coord identity/phenomenon + # metadata fields -- so it works even between coords of different subclasses. + for key in CoordMetadata._fields: + assert getattr(test_coord, key) == getattr(ref_coord, key) + + @pytest.fixture(params=["face", "edge"]) + def location_face_or_edge(self, request): + # Fixture to parametrise over location = face/edge + return request.param + + @pytest.fixture(params=["x", "y"]) + def axis_x_or_y(self, request): + # Fixture to parametrise over axis = X/Y + return request.param + + def test_node_meshcoord(self, axis_x_or_y): + # MeshCoord metadata matches that of the relevant node coord. + self.setup_mesh(location="node", axis=axis_x_or_y) + meshcoord = self.mesh.to_MeshCoord( + location=self.location, axis=self.axis + ) + self.coord_metadata_matches(meshcoord, self.node_coord) + + def test_faceedge_basic(self, location_face_or_edge, axis_x_or_y): + # MeshCoord metadata matches that of the face/edge ("points") coord. + self.setup_mesh(location_face_or_edge, axis_x_or_y) + meshcoord = self.mesh.to_MeshCoord( + location=self.location, axis=self.axis + ) + self.coord_metadata_matches(meshcoord, self.location_coord) + + @pytest.mark.parametrize( + "fieldname", ["long_name", "var_name", "attributes"] + ) + def test_faceedge_dontcare_fields( + self, location_face_or_edge, axis_x_or_y, fieldname + ): + # Check that it's ok for the face/edge and node coords to have different + # long-name, var-name or attributes. + self.setup_mesh(location_face_or_edge, axis_x_or_y) + if fieldname == "attributes": + different_value = {"myattrib": "different attributes"} + else: + # others are just arbitrary strings. + different_value = "different" + setattr(self.location_coord, fieldname, different_value) + # Mostly.. just check this does not cause an error, as it would do if we + # modified "standard_name" or "units" (see other tests) ... + meshcoord = self.mesh.to_MeshCoord( + location=self.location, axis=self.axis + ) + # ... but also, check that the result matches the expected face/edge coord. + self.coord_metadata_matches(meshcoord, self.location_coord) + + def test_faceedge_fail_mismatched_stdnames( + self, location_face_or_edge, axis_x_or_y + ): + # Different "standard_name" for node and face/edge causes an error. + self.setup_mesh(location_face_or_edge, axis_x_or_y) + node_name = f"projection_{axis_x_or_y}_coordinate" + self.node_coord.standard_name = node_name + location_name = "longitude" if axis_x_or_y == "x" else "latitude" + msg = ( + "Node coordinate .*" + f"disagrees with the {location_face_or_edge} coordinate .*, " + 'in having a "standard_name" value of ' + f"'{node_name}' instead of '{location_name}'" + ) + with pytest.raises(ValueError, match=msg): + self.mesh.to_MeshCoord( + location=location_face_or_edge, axis=axis_x_or_y + ) + + def test_faceedge_fail_missing_stdnames( + self, location_face_or_edge, axis_x_or_y + ): + # "standard_name" compared with None also causes an error. + self.setup_mesh(location_face_or_edge, axis_x_or_y) + self.node_coord.standard_name = None + # N.B. in the absence of a standard-name, we **must** provide an extra ".axis" + # property, or the coordinate cannot be correctly identified in the Mesh. + # This is a bit of a kludge, but works with current code. + self.node_coord.axis = axis_x_or_y + + location_name = "longitude" if axis_x_or_y == "x" else "latitude" + msg = ( + "Node coordinate .*" + f"disagrees with the {location_face_or_edge} coordinate .*, " + 'in having a "standard_name" value of ' + f"None instead of '{location_name}'" + ) + with pytest.raises(ValueError, match=msg): + self.mesh.to_MeshCoord( + location=location_face_or_edge, axis=axis_x_or_y + ) + + def test_faceedge_fail_mismatched_units( + self, location_face_or_edge, axis_x_or_y + ): + # Different "units" for node and face/edge causes an error. + self.setup_mesh(location_face_or_edge, axis_x_or_y) + self.node_coord.units = "hPa" + msg = ( + "Node coordinate .*" + f"disagrees with the {location_face_or_edge} coordinate .*, " + 'in having a "units" value of ' + "'hPa' instead of 'degrees'" + ) + with pytest.raises(ValueError, match=msg): + self.mesh.to_MeshCoord( + location=location_face_or_edge, axis=axis_x_or_y + ) + + def test_faceedge_missing_units(self, location_face_or_edge, axis_x_or_y): + # Units compared with a None ("unknown") is not an error. + self.setup_mesh(location_face_or_edge, axis_x_or_y) + self.node_coord.units = None + # This is OK + meshcoord = self.mesh.to_MeshCoord( + location=self.location, axis=self.axis + ) + # ... but also, check that the result matches the expected face/edge coord. + self.coord_metadata_matches(meshcoord, self.location_coord) + + if __name__ == "__main__": tests.main() From 323448939e9d3d982c45f8fe0c6d4fb27e1c5ec0 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Mon, 24 Oct 2022 11:42:20 +0100 Subject: [PATCH 256/319] Added pandas_ndim FUTURE flag (#4909) * Added pandas_ndim FUTURE flag. * TODO flag to document pandas_ndim future flag. --- lib/iris/__init__.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index b944f9b22f..70dcaa60de 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -140,7 +140,7 @@ def callback(cube, field, filename): class Future(threading.local): """Run-time configuration controller.""" - def __init__(self, datum_support=False): + def __init__(self, datum_support=False, pandas_ndim=False): """ A container for run-time options controls. @@ -157,6 +157,12 @@ def __init__(self, datum_support=False): iris.FUTURE.example_future_flag does not exist. It is provided as an example. + .. todo:: + + Document the ``pandas_ndim`` flag once iris#4669 is merged - can + add cross-referencing documentation both here and in + iris.pandas.as_dataframe(). + """ # The flag 'example_future_flag' is provided as a reference for the # structure of this class. @@ -166,13 +172,14 @@ def __init__(self, datum_support=False): # # self.__dict__['example_future_flag'] = example_future_flag self.__dict__["datum_support"] = datum_support + self.__dict__["pandas_ndim"] = pandas_ndim def __repr__(self): # msg = ('Future(example_future_flag={})') # return msg.format(self.example_future_flag) - msg = "Future(datum_support={})" - return msg.format(self.datum_support) + msg = "Future(datum_support={}, pandas_ndim={})" + return msg.format(self.datum_support, self.pandas_ndim) # deprecated_options = {'example_future_flag': 'warning',} deprecated_options = {} From b5178a9634aea83ff61c29d1e07f6badefcfa2e9 Mon Sep 17 00:00:00 2001 From: Elias <110238618+ESadek-MO@users.noreply.github.com> Date: Thu, 27 Oct 2022 16:03:17 +0100 Subject: [PATCH 257/319] Prioritise dim coord in `_get_lon_lat_coords()` (#5029) * Added in dim_coord prioritisation within _get_lon_lat_coords * Completed testing for dim coord prioritisation, added what's new entry * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated tests to include fixtures * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Mostly formatting changes for readability, largely suggested by Payton for better PyTest consistency. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Replaced with throughout * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated docstring in project Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 7 ++ lib/iris/analysis/cartography.py | 26 ++-- .../cartography/test__get_lon_lat_coords.py | 114 ++++++++++++++++++ 3 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index ea4b52ce2a..d0aefd02c1 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -47,6 +47,13 @@ This document explains the changes made to Iris for this release :class:`~iris.coords.AuxCoord`, which avoids some specific known usage problems. (:issue:`4860`, :pull:`5020`) +#. `@Esadek-MO`_ and `@trexfeathers`_ added dim coord + prioritisation to ``_get_lon_lat_coords()`` in :mod:`iris.analysis.cartography`. + This allows :func:`iris.analysis.cartography.area_weights` and + :func:`~iris.analysis.cartography.project` to handle cubes which contain + both dim and aux coords of the same type e.g. ``longitude`` and ``grid_longitude``. + (:issue:`3916`, :pull:`5029`). + 🐛 Bugs Fixed ============= diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index f38e48354d..8dbad9c4e9 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -169,20 +169,25 @@ def rotate_pole(lons, lats, pole_lon, pole_lat): def _get_lon_lat_coords(cube): - lat_coords = [ - coord for coord in cube.coords() if "latitude" in coord.name() - ] - lon_coords = [ - coord for coord in cube.coords() if "longitude" in coord.name() - ] + def search_for_coord(coord_iterable, coord_name): + return [ + coord for coord in coord_iterable if coord_name in coord.name() + ] + + lat_coords = search_for_coord( + cube.dim_coords, "latitude" + ) or search_for_coord(cube.coords(), "latitude") + lon_coords = search_for_coord( + cube.dim_coords, "longitude" + ) or search_for_coord(cube.coords(), "longitude") if len(lat_coords) > 1 or len(lon_coords) > 1: raise ValueError( - "Calling `_get_lon_lat_coords` with multiple lat or lon coords" + "Calling `_get_lon_lat_coords` with multiple same-type (i.e. dim/aux) lat or lon coords" " is currently disallowed" ) lat_coord = lat_coords[0] lon_coord = lon_coords[0] - return (lon_coord, lat_coord) + return lon_coord, lat_coord def _xy_range(cube, mode=None): @@ -578,6 +583,11 @@ def project(cube, target_proj, nx=None, ny=None): An instance of :class:`iris.cube.Cube` and a list describing the extent of the projection. + .. note:: + + If there are both dim and aux latitude-longitude coordinates, only + the dim coordinates will be used. + .. note:: This function assumes global data and will if necessary extrapolate diff --git a/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py b/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py new file mode 100644 index 0000000000..612e5d8ecf --- /dev/null +++ b/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py @@ -0,0 +1,114 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Test function :func:`iris.analysis.cartography._get_lon_lat_coords""" + +import pytest + +from iris.analysis.cartography import _get_lon_lat_coords as g_lon_lat +from iris.coords import AuxCoord +from iris.tests.stock import lat_lon_cube + + +@pytest.fixture +def dim_only_cube(): + return lat_lon_cube() + + +def test_dim_only(dim_only_cube): + t_lat, t_lon = dim_only_cube.dim_coords + + lon, lat = g_lon_lat(dim_only_cube) + + assert lon == t_lon + assert lat == t_lat + + +@pytest.fixture +def dim_aux_cube(dim_only_cube): + lat_dim, lon_dim = dim_only_cube.dim_coords + + lat_aux = AuxCoord.from_coord(lat_dim) + lat_aux.standard_name = "grid_latitude" + lon_aux = AuxCoord.from_coord(lon_dim) + lon_aux.standard_name = "grid_longitude" + + dim_aux_cube = dim_only_cube + dim_aux_cube.add_aux_coord(lat_aux, 0) + dim_aux_cube.add_aux_coord(lon_aux, 1) + + return dim_aux_cube + + +def test_dim_aux(dim_aux_cube): + t_lat_dim, t_lon_dim = dim_aux_cube.dim_coords + + lon, lat = g_lon_lat(dim_aux_cube) + + assert lon == t_lon_dim + assert lat == t_lat_dim + + +@pytest.fixture +def aux_only_cube(dim_aux_cube): + lon_dim, lat_dim = dim_aux_cube.dim_coords + + aux_only_cube = dim_aux_cube + aux_only_cube.remove_coord(lon_dim) + aux_only_cube.remove_coord(lat_dim) + + return dim_aux_cube + + +def test_aux_only(aux_only_cube): + aux_lat, aux_lon = aux_only_cube.aux_coords + + lon, lat = g_lon_lat(aux_only_cube) + + assert lon == aux_lon + assert lat == aux_lat + + +@pytest.fixture +def double_dim_cube(dim_only_cube): + double_dim_cube = dim_only_cube + double_dim_cube.coord("latitude").standard_name = "grid_longitude" + + return double_dim_cube + + +def test_double_dim(double_dim_cube): + t_error_message = "with multiple.*is currently disallowed" + + with pytest.raises(ValueError, match=t_error_message): + g_lon_lat(double_dim_cube) + + +@pytest.fixture +def double_aux_cube(aux_only_cube): + double_aux_cube = aux_only_cube + double_aux_cube.coord("grid_latitude").standard_name = "longitude" + + return double_aux_cube + + +def test_double_aux(double_aux_cube): + t_error_message = "with multiple.*is currently disallowed" + + with pytest.raises(ValueError, match=t_error_message): + g_lon_lat(double_aux_cube) + + +@pytest.fixture +def missing_lat_cube(dim_only_cube): + missing_lat_cube = dim_only_cube + missing_lat_cube.remove_coord("latitude") + + return missing_lat_cube + + +def test_missing_coord(missing_lat_cube): + with pytest.raises(IndexError): + g_lon_lat(missing_lat_cube) From 8499fe0d7c552a4e6eed765ed03610d643f4a9f3 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Tue, 1 Nov 2022 11:32:59 +0000 Subject: [PATCH 258/319] Improve error messages when comparing against objects vs strings (#4928) * improve error messages * whitespace fix * edit/fix error messages * add whatsnew entry * Apply suggestions from code review Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> * add test Co-authored-by: Ruth Comer <10599679+rcomer@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 6 ++++++ lib/iris/cube.py | 27 +++++++++++++++++++++++---- lib/iris/tests/unit/cube/test_Cube.py | 19 +++++++++++++++++++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d0aefd02c1..25c0c5f3c9 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -79,6 +79,12 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ removed some old redundant code that prevented determining the order of time cells. (:issue:`4697`, :pull:`4729`) +#. `@stephenworsley`_ improved the accuracy of the error messages for + :meth:`~iris.cube.Cube.coord` when failing to find coordinates in the case where + a coordinate is given as the argument. Similarly, improved the error messages for + :meth:`~iris.cube.Cube.cell_measure` and :meth:`~iris.cube.Cube.ancillary_variable`. + (:issue:`4898`, :pull:`4928`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 9779558506..4e24c099fe 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -1990,6 +1990,12 @@ def coord( if name_or_coord is not None: if not isinstance(name_or_coord, str): _name = name_or_coord.name() + emsg = ( + "Expected to find exactly 1 coordinate matching the given " + f"{_name!r} coordinate's metadata, but found none." + ) + raise iris.exceptions.CoordinateNotFoundError(emsg) + bad_name = _name or standard_name or long_name or "" emsg = ( f"Expected to find exactly 1 {bad_name!r} coordinate, " @@ -2194,9 +2200,15 @@ def cell_measure(self, name_or_cell_measure=None): bad_name = ( name_or_cell_measure and name_or_cell_measure.name() ) or "" + if name_or_cell_measure is not None: + emsg = ( + "Expected to find exactly 1 cell measure matching the given " + f"{bad_name!r} cell measure's metadata, but found none." + ) + raise iris.exceptions.CellMeasureNotFoundError(emsg) msg = ( - "Expected to find exactly 1 %s cell_measure, but found " - "none." % bad_name + f"Expected to find exactly 1 {bad_name!r} cell measure, " + "but found none." ) raise iris.exceptions.CellMeasureNotFoundError(msg) @@ -2281,9 +2293,16 @@ def ancillary_variable(self, name_or_ancillary_variable=None): name_or_ancillary_variable and name_or_ancillary_variable.name() ) or "" + if name_or_ancillary_variable is not None: + emsg = ( + "Expected to find exactly 1 ancillary_variable matching the " + f"given {bad_name!r} ancillary_variable's metadata, but found " + "none." + ) + raise iris.exceptions.AncillaryVariableNotFoundError(emsg) msg = ( - "Expected to find exactly 1 {!s} ancillary_variable, but " - "found none.".format(bad_name) + f"Expected to find exactly 1 {bad_name!r} ancillary_variable, " + "but found none." ) raise iris.exceptions.AncillaryVariableNotFoundError(msg) diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 62c719aab9..7a6a200890 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -2528,6 +2528,25 @@ def test_fail_remove_ancilliary_variable_by_name(self): self.cube.remove_ancillary_variable("notname") +class TestCoords(tests.IrisTest): + def setUp(self): + cube = Cube(np.arange(6).reshape(2, 3)) + x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") + cube.add_dim_coord(x_coord, 1) + self.x_coord = x_coord + self.cube = cube + + def test_bad_coord(self): + bad_coord = self.x_coord.copy() + bad_coord.attributes = {"bad": "attribute"} + re = ( + "Expected to find exactly 1 coordinate matching the given " + "'x' coordinate's metadata, but found none." + ) + with self.assertRaisesRegex(CoordinateNotFoundError, re): + _ = self.cube.coord(bad_coord) + + class Test__getitem_CellMeasure(tests.IrisTest): def setUp(self): cube = Cube(np.arange(6).reshape(2, 3)) From 5ab3dc2af05ef2c340df2a4277143ce5117ff7c2 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Tue, 1 Nov 2022 16:34:28 +0000 Subject: [PATCH 259/319] test for 'coord exists' constraint (#5043) --- lib/iris/tests/test_constraints.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/iris/tests/test_constraints.py b/lib/iris/tests/test_constraints.py index 1972cdeb90..e568105f91 100644 --- a/lib/iris/tests/test_constraints.py +++ b/lib/iris/tests/test_constraints.py @@ -67,6 +67,27 @@ def test_constraints(self): sub_list = self.slices.extract(constraint) self.assertEqual(len(sub_list), 70 * 6) + def test_coord_availability(self): + # "model_level_number" coordinate available + constraint = iris.Constraint(model_level_number=lambda x: True) + result = self.slices.extract(constraint) + self.assertTrue(result) + + # "wibble" coordinate is not available + constraint = iris.Constraint(wibble=lambda x: False) + result = self.slices.extract(constraint) + self.assertFalse(result) + + # "wibble" coordinate is not available + constraint = iris.Constraint(wibble=lambda x: True) + result = self.slices.extract(constraint) + self.assertFalse(result) + + # "lambda x: False" always (confusingly) throws away the cube + constraint = iris.Constraint(model_level_number=lambda x: False) + result = self.slices.extract(constraint) + self.assertFalse(result) + def test_mismatched_type(self): constraint = iris.Constraint(model_level_number="aardvark") sub_list = self.slices.extract(constraint) From 75c7570bf4d509295e239aff47b70324cd68c22d Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:23:55 +0000 Subject: [PATCH 260/319] not nan functions (#5039) --- lib/iris/analysis/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 11810f2901..85b49ece4e 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -2118,7 +2118,7 @@ def interp_order(length): .. note:: - Lazy operation is supported, via :func:`dask.array.nanstd`. + Lazy operation is supported, via :func:`dask.array.std`. This aggregator handles masked data. @@ -2192,7 +2192,7 @@ def interp_order(length): .. note:: - Lazy operation is supported, via :func:`dask.array.nanvar`. + Lazy operation is supported, via :func:`dask.array.var`. This aggregator handles masked data. From a6795cf9791597902ebb040bfe326c9aa762cbf6 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 2 Nov 2022 13:58:19 +0000 Subject: [PATCH 261/319] Use reusable workflow for refresh lockfile workflow (#5036) * Use reusable workflow for refresh lockfile workflow * Update location of reusable workflows --- .github/workflows/refresh-lockfiles.yml | 109 ++---------------------- 1 file changed, 5 insertions(+), 104 deletions(-) diff --git a/.github/workflows/refresh-lockfiles.yml b/.github/workflows/refresh-lockfiles.yml index 3bef604b41..94c20aedb9 100644 --- a/.github/workflows/refresh-lockfiles.yml +++ b/.github/workflows/refresh-lockfiles.yml @@ -1,13 +1,5 @@ -# This workflow periodically creates new environment lock files based on the newest -# available packages and dependencies. -# -# Environment specifications are given as conda environment.yml files found in -# `requirements/ci/py**.yml`. These state the packages required, the conda channels -# that the packages will be pulled from, and any versions of packages that need to be -# pinned at specific versions. -# -# For environments that have changed, a pull request will be made and submitted -# to the main branch +# Updates the environment lock files. See the called workflow in the +# scitools/workflows repo for more details. name: Refresh Lockfiles @@ -20,98 +12,7 @@ on: # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onschedule - cron: "1 0 * * 6" - jobs: - - get_python_matrix: - # Determines which Python versions should be included in the matrix used in - # the gen_lockfiles job. - if: "github.repository == 'SciTools/iris'" - runs-on: ubuntu-latest - outputs: - matrix: ${{ steps.get_py.outputs.matrix }} - steps: - - uses: actions/checkout@v3 - - id: get_py - run: echo "::set-output name=matrix::$(ls -1 requirements/ci/py*.yml | xargs -n1 basename | sed 's/....$//' | jq -cnR '[inputs]')" - - gen_lockfiles: - # this is a matrix job: it splits to create new lockfiles for each - # of the CI test python versions. - if: "github.repository == 'SciTools/iris'" - runs-on: ubuntu-latest - needs: get_python_matrix - - strategy: - matrix: - python: ${{ fromJSON(needs.get_python_matrix.outputs.matrix) }} - - steps: - - uses: actions/checkout@v3 - - name: install requirements - run: | - source $CONDA/bin/activate base - conda install -y -c conda-forge conda-libmamba-solver conda-lock - - name: generate lockfile - env: - CONDA_EXPERIMENTAL_SOLVER: libmamba - run: | - $CONDA/bin/conda-lock lock -k explicit -p linux-64 -f requirements/ci/${{matrix.python}}.yml - mv conda-linux-64.lock ${{matrix.python}}-linux-64.lock - - name: output lockfile - uses: actions/upload-artifact@v3 - with: - path: ${{matrix.python}}-linux-64.lock - - create_pr: - # once the matrix job has completed all the lock files will have been uploaded as artifacts. - # Download the artifacts, add them to the repo, and create a PR. - if: "github.repository == 'SciTools/iris'" - runs-on: ubuntu-latest - needs: gen_lockfiles - - steps: - - uses: actions/checkout@v3 - - name: get artifacts - uses: actions/download-artifact@v3 - with: - path: artifacts - - - name: Update lock files in repo - run: | - cp artifacts/artifact/*.lock requirements/ci/nox.lock - rm -r artifacts - - - name: "Generate token" - uses: tibdex/github-app-token@v1 - id: generate-token - with: - app_id: ${{ secrets.AUTH_APP_ID }} - private_key: ${{ secrets.AUTH_APP_PRIVATE_KEY }} - - - name: Create Pull Request - id: cpr - uses: peter-evans/create-pull-request@b4d51739f96fca8047ad065eccef63442d8e99f7 - with: - token: ${{ steps.generate-token.outputs.token }} - commit-message: Updated environment lockfiles - committer: "Lockfile bot " - author: "Lockfile bot " - delete-branch: true - branch: auto-update-lockfiles - title: "[iris.ci] environment lockfiles auto-update" - body: | - Lockfiles updated to the latest resolvable environment. - - ### If the CI tasks fail, create a new branch based on this PR and add the required fixes to that branch. - labels: | - New: Pull Request - Bot - - - name: Check Pull Request - if: steps.cpr.outputs.pull-request-number != '' - run: | - echo "pull-request #${{ steps.cpr.outputs.pull-request-number }}" - echo "pull-request URL ${{ steps.cpr.outputs.pull-request-url }}" - echo "pull-request operation [${{ steps.cpr.outputs.pull-request-operation }}]" - echo "pull-request head SHA ${{ steps.cpr.outputs.pull-request-head-sha }}" + refresh_lockfiles: + uses: scitools/workflows/.github/workflows/refresh-lockfiles.yml@main + secrets: inherit From 421e193d9a73d767c64d989550c816cebdebecab Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 4 Nov 2022 11:07:58 +0000 Subject: [PATCH 262/319] Accept new copy behaviour from dask/dask#9555. (#5041) * Replicate legacy dask behaviour pre dask/dask#9555. * Revert "Replicate legacy dask behaviour pre dask/dask#9555." This reverts commit 7363412a069310acf63b0aabb1a293f0f741a26a. * Accept shared NumPy arrays when copying certain Dask arrays - dask/dask#9555. * Updated lock-files. * What's New entry. * Re-arrange What's New entry. --- docs/src/whatsnew/latest.rst | 13 +- lib/iris/tests/unit/cube/test_Cube.py | 9 +- requirements/ci/nox.lock/py310-linux-64.lock | 192 ++++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 190 +++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 192 ++++++++++--------- 5 files changed, 309 insertions(+), 287 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 25c0c5f3c9..5b23c38fe4 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -89,7 +89,15 @@ This document explains the changes made to Iris for this release 💣 Incompatible Changes ======================= -#. N/A +#. `@trexfeathers`_ altered testing to accept new Dask copying behaviour from + `dask/dask#9555`_ - copies of a Dask array created using ``da.from_array()`` + will all ``compute()`` to a shared identical array. So creating a + :class:`~iris.cube.Cube` using ``Cube(data=da.from_array(...``, then + using :class:`~iris.cube.Cube` :meth:`~iris.cube.Cube.copy`, + will produce two :class:`~iris.cube.Cube`\s that both return an identical + array when requesting :class:`~iris.cube.Cube` :attr:`~iris.cube.Cube.data`. + We do not expect this to affect typical user workflows but please get in + touch if you need help. (:pull:`5041`) 🚀 Performance Enhancements @@ -133,6 +141,8 @@ This document explains the changes made to Iris for this release :mod:`iris.palette` in response to a deprecation warning. Using the new Matplotlib API also means a ``matplotlib>=3.5`` pin. (:pull:`4998`) +#. See `💣 Incompatible Changes`_ for notes about `dask/dask#9555`_. + 📚 Documentation ================ @@ -186,3 +196,4 @@ This document explains the changes made to Iris for this release .. _pypa/setuptools#1684: https://github.com/pypa/setuptools/issues/1684 .. _SciTools/cartopy@fcb784d: https://github.com/SciTools/cartopy/commit/fcb784daa65d95ed9a74b02ca292801c02bc4108 .. _SciTools/cartopy@8860a81: https://github.com/SciTools/cartopy/commit/8860a8186d4dc62478e74c83f3b2b3e8f791372e +.. _dask/dask#9555: https://github.com/dask/dask/pull/9555 diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 7a6a200890..5d120a6982 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -1865,12 +1865,14 @@ class Test_copy(tests.IrisTest): def _check_copy(self, cube, cube_copy): self.assertIsNot(cube_copy, cube) self.assertEqual(cube_copy, cube) - self.assertIsNot(cube_copy.data, cube.data) + self.assertIsNot(cube_copy.core_data(), cube.core_data()) if ma.isMaskedArray(cube.data): self.assertMaskedArrayEqual(cube_copy.data, cube.data) if cube.data.mask is not ma.nomask: # "No mask" is a constant : all other cases must be distinct. - self.assertIsNot(cube_copy.data.mask, cube.data.mask) + self.assertIsNot( + cube_copy.core_data().mask, cube.core_data().mask + ) else: self.assertArrayEqual(cube_copy.data, cube.data) @@ -1911,6 +1913,9 @@ def test__masked_scalar_arraymask(self): self._check_copy(cube, cube.copy()) def test__lazy(self): + # 2022-11-02: Dask's current behaviour is that the computed array will + # be the same for cube and cube.copy(), even if the Dask arrays are + # different. cube = Cube(as_lazy_data(np.array([1, 0]))) self._check_copy(cube, cube.copy()) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index e60113c475..b4a9107fbe 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -8,33 +8,34 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hc81fddc_0.tar.bz2#c2719e2faa7bd7076d3a4b52271e5622 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022d-h191b570_0.tar.bz2#456b5b1d99e7a9654b331bcd82e71042 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022f-h191b570_0.tar.bz2#e366350e2343a798e29833286abe2560 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 -https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d +https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373 https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2#e4c94f80aef025c17ab0828cd85ef535 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2#be733e69048951df1e4b4b7bb8c7666f https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f +https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 +https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f -https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 @@ -46,14 +47,15 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 +https://conda.anaconda.org/conda-forge/linux-64/libudev1-251-h166bdaf_0.tar.bz2#2fbd69c19564f7609d92ad887d11df98 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_4.tar.bz2#6a2e5b333ba57ce7eec61e90260cbb79 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -66,43 +68,41 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b -https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_4.tar.bz2#995cc7813221edbc25a3db15357599a0 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 -https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h7a41b64_0.tar.bz2#5635a2490d52955dc3192d901434c26d https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.3-h63197d8_1.tar.bz2#556ee34231614bb1d196e1c0ee693bb8 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 @@ -113,47 +113,49 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e +https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb -https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.0-pyhd8ed1ab_0.tar.bz2#da409b864dc21631820c81973df42587 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.10.0-pyhd8ed1ab_0.tar.bz2#ee4d78b97e857cb8d845c8fa4c898433 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_0.tar.bz2#df71cc96499592e632046ee20c2c3bd0 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f -https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.3-default_h3a83d3e_0.tar.bz2#736feeb04fac846a0e2cb5babec0b8c8 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.85.0-h7bff187_0.tar.bz2#054fb5981fdbe031caeb612b71d85f84 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 +https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.4-pyhd8ed1ab_0.tar.bz2#fc0dcaf9761d042fb8ac9128ce03fddb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.1-pyhd8ed1ab_0.tar.bz2#d61d9f25af23c24002e659b854c6f5ae +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.0-pyhd8ed1ab_0.tar.bz2#462466739c786f85f9ed555f87099fe1 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -171,89 +173,89 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_0.tar.bz2#3e4b55b02998782f8ca9ceaaa4f5ada9 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.85.0-h7bff187_0.tar.bz2#a8ac96d6b09b8ed5b0ac6563901e2195 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_2.tar.bz2#1cdb74e021e4e0b703a8c2f7cc57d798 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_2.tar.bz2#6bb8063dd08f9724c18744b0e040cfe2 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3.tar.bz2#785160da087cf1d70e989afbb761f01c +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_0.tar.bz2#2addd7ce0b4dffd5818d29f174c2d5d8 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e -https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py310hff52083_0.tar.bz2#8ea386e64531f1ecf4a5765181579e7e -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_0.tar.bz2#8dc3e2dce8fa122f8df4f3739d1f771b +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.3-default_h2e3cab8_0.tar.bz2#09f01447ebab86a3e4f9dbf8cc72c94e https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_1.tar.bz2#ec5a727504409ad1380fc2a84f83d002 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_2.tar.bz2#0211369f253eedce9e570b4f0e5a981a +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_3.tar.bz2#a12684b4b8bcc3c700a00d0365b0b9f7 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py310h53a5b5f_0.tar.bz2#0a60ccaed9ad236cc7463322fe742eb6 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py310h53a5b5f_1.tar.bz2#0b7d4c8253f7191030adf34e2768c412 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310hbd86126_2.tar.bz2#443272de4234f6df4a78f50105edc741 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py310hff52083_3.tar.bz2#97f9a22577338f91a94dfac5c1a65a50 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310h454ad03_3.tar.bz2#eb354ff791f505b1d6f13f776359d88e +https://conda.anaconda.org/conda-forge/noarch/pip-22.3-pyhd8ed1ab_0.tar.bz2#6f4c6de9fed2a9bd8043283611b09587 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py310h5764c6d_0.tar.bz2#6ac13c26fe4f9d8d6b38657664c37fd3 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.3-py310h5764c6d_1.tar.bz2#9bfec73f587caed65c74e2219895f723 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0d2025b_11.tar.bz2#316026c5f80d8b5b9666e87b8614ac6c https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_1.tar.bz2#b6f54b7c4177a745d5e6e4319282253a -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_4.tar.bz2#505dcf6be997e732d7a33831950dc3cf -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_0.tar.bz2#c42dcb37acd84b3ca197f03f57ef927d +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_2.tar.bz2#cce72b32ccc346ed166fc85071854a86 +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_5.tar.bz2#9e68d2ff6d98737c855b65f48dd3c597 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_1.tar.bz2#be4a201ac582c11d89ed7d15b3157cc3 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py310h5764c6d_1.tar.bz2#791689ce9e578e2e83b635974af61743 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_0.tar.bz2#e572565848d8d19e74983f4d122734a8 -https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1004.tar.bz2#6499bb11b7feffb63b26847fc9181319 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_0.tar.bz2#6290f1bc763ed75a42aaea29384f9858 -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py310hbf28c38_0.tar.bz2#85565efb2bf44e8a5782e7c418d30cfe -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.1-py310h597c629_0.tar.bz2#890771047730c6fa66a8926f0ca9bd13 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.2-pyhd8ed1ab_0.tar.bz2#9993f51a02fc8338837421211b53ab19 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py310h5764c6d_0.tar.bz2#46eb2017ab2148b1f28334b07510f0e5 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py310h5764c6d_0.tar.bz2#e972c5a1f472561cf4a91962cb01f4b4 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_1.tar.bz2#f41231b142c5adcd84dbf3c9a93a8a80 +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.2-py310h597c629_2.tar.bz2#e63caeba7d88f0bc7c4d6c04ba1599da +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.10.2-pyhd8ed1ab_0.tar.bz2#6f837aa0cbc910b39207fe5d97dfdf1e +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1007.tar.bz2#c2ec7c118184ddfd855fc3698d1c8e63 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py310h769672d_0.tar.bz2#06efc4b5f4b418b78de14d1db4a65cad -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_1.tar.bz2#0ad6207e9d553c67984a5b0b06bbd2a3 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py310hff52083_0.tar.bz2#18ef27d620d67af2feef22acfd42cf4a -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_2.tar.bz2#a282f30e2e1efa1f210817597e144762 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_1.tar.bz2#cbfce984f85c64401e3d4fedf4bc4247 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py310hdfbd76f_0.tar.bz2#bfb55d07ad9d15d2f2f8e59afcbcf578 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py310h5e49deb_0.tar.bz2#2f2c225d04e99ff99d6d3a86692ce968 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py310hd8f1fbe_0.tar.bz2#3d311837eadeb8137fca02bdb5a9751f +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.1-py310h769672d_1.tar.bz2#4dd589c55d445e52ef0a7102158254df +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_2.tar.bz2#e1648c222911ad7559d62831e4bc447c +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2.tar.bz2#61e2f2f7befaf45f47d1da449a9a0aca +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_1.tar.bz2#4140058701f7a3bad773f31496586256 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5e49deb_1.tar.bz2#e5519576751b59d67164b965a4eb4406 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.3-py310hd8f1fbe_0.tar.bz2#2c6397f4fdfd0e2818c946adcff79c02 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 -https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_2.tar.bz2#46784478afa27e33b9d5f017c4deb49d -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_0.tar.bz2#49790458218da5f86068f32e3938d334 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.6-pyhd8ed1ab_0.tar.bz2#8c3563a8e310ea9a727f07caa0341ec2 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_1.tar.bz2#f334893ef6856cb89786d2e0c6fb5a53 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py310h8d5ebf3_0.tar.bz2#001fdef689e7cbcbbce6d5a6ebee90b6 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.1-py310h8d5ebf3_1.tar.bz2#bc8d8dcad6b921b0996df46f0e7f120d https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_0.tar.bz2#9e3db99607d6f9285b7348c2af28a095 -https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_0.tar.bz2#3e81d6afa50895d6dee115ac5d34c2ea +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_2.tar.bz2#0d815f1b2258d3d4c17cc80fd01e0f36 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_1.tar.bz2#5f9997662e6b2d3918149b65904d600c https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_0.tar.bz2#5af49a9342d50006017b897698921f43 -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_1.tar.bz2#8c151d720f9fe3b9962efe71fc10b07b +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_0.tar.bz2#b5fb5328cae86d0b1591fc4894e68238 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_2.tar.bz2#1e2c49215b17e6cf06edf100c9869ebe https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py310hff52083_0.tar.bz2#2db9d22cc226ef79d9cd87fc958c2b04 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.1-py310hff52083_1.tar.bz2#51fbce233e5680a4258db5a16e2c1832 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a + diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index d6301a12b7..83fb2e641c 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -8,32 +8,33 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hc81fddc_0.tar.bz2#c2719e2faa7bd7076d3a4b52271e5622 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 -https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d +https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373 https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2#e4c94f80aef025c17ab0828cd85ef535 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2#be733e69048951df1e4b4b7bb8c7666f https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f +https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 +https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f -https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 @@ -45,14 +46,15 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 +https://conda.anaconda.org/conda-forge/linux-64/libudev1-251-h166bdaf_0.tar.bz2#2fbd69c19564f7609d92ad887d11df98 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_4.tar.bz2#6a2e5b333ba57ce7eec61e90260cbb79 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -65,43 +67,41 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b -https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_4.tar.bz2#995cc7813221edbc25a3db15357599a0 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 -https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h7a41b64_0.tar.bz2#5635a2490d52955dc3192d901434c26d https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.3-h63197d8_1.tar.bz2#556ee34231614bb1d196e1c0ee693bb8 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -109,18 +109,18 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bd https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_0.tar.bz2#df71cc96499592e632046ee20c2c3bd0 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f -https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.3-default_h3a83d3e_0.tar.bz2#736feeb04fac846a0e2cb5babec0b8c8 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.85.0-h7bff187_0.tar.bz2#054fb5981fdbe031caeb612b71d85f84 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 @@ -134,35 +134,38 @@ https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.b https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e +https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb -https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.85.0-h7bff187_0.tar.bz2#a8ac96d6b09b8ed5b0ac6563901e2195 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.0-pyhd8ed1ab_0.tar.bz2#da409b864dc21631820c81973df42587 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.10.0-pyhd8ed1ab_0.tar.bz2#ee4d78b97e857cb8d845c8fa4c898433 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_0.tar.bz2#2addd7ce0b4dffd5818d29f174c2d5d8 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.3-default_h2e3cab8_0.tar.bz2#09f01447ebab86a3e4f9dbf8cc72c94e https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 +https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf -https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0d2025b_11.tar.bz2#316026c5f80d8b5b9666e87b8614ac6c https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.4-pyhd8ed1ab_0.tar.bz2#fc0dcaf9761d042fb8ac9128ce03fddb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.1-pyhd8ed1ab_0.tar.bz2#d61d9f25af23c24002e659b854c6f5ae +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.0-pyhd8ed1ab_0.tar.bz2#462466739c786f85f9ed555f87099fe1 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -177,82 +180,81 @@ https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_0.tar.bz2#a970d201055ec06a75db83bf25447eb2 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_2.tar.bz2#affd6b87adb2b0c98da0e3ad274349be -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 -https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py38h578d9bd_0.tar.bz2#037225c33a50e99c5d4f86fac90f6de8 -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_0.tar.bz2#ae54c61918e1cbd280b8587ed6219258 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_1.tar.bz2#20d003ad5f584e212c299f64cac46c05 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_2.tar.bz2#fccce86e5fc8183bf2658ac9bfc535b4 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_2.tar.bz2#2276b1f4d1ede3f5f14cc7e4ae6f9a33 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_2.tar.bz2#c342a370480791db83d5dd20f2d8899f +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_3.tar.bz2#1fb632adee369c41a36de4426f49bad6 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py38h3a7f9d9_0.tar.bz2#83ba913fc1174925d4e862eccb53db59 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py38h7042d01_1.tar.bz2#7fa0e9ed4e8a096e2f00b2426ebba0de https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38ha3b2c9c_2.tar.bz2#a077cc2bb9d854074b1cf4607252da7a -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py38h578d9bd_3.tar.bz2#6ce4ce3d4490a56eb33b52c179609193 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h9eb91d8_3.tar.bz2#61dc7b3140b7b79b1985b53d52726d74 +https://conda.anaconda.org/conda-forge/noarch/pip-22.3-pyhd8ed1ab_0.tar.bz2#6f4c6de9fed2a9bd8043283611b09587 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py38h0a891b7_0.tar.bz2#907a39b6d7443f770ed755885694f864 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.3-py38h0a891b7_1.tar.bz2#7bf4c28494eaa7de44b7dcdc55d45a89 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hd7890fc_1.tar.bz2#f851bb08c85122fd0e1f66d2072ebf0b +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hce0a2d1_2.tar.bz2#be61a535f279bffdf7f449a654eaa19d https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_1.tar.bz2#69fc64e4f4c13abe0b8df699ddaa1051 -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_4.tar.bz2#ba24ff01bb38c5cd5be54b45ef685db3 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_0.tar.bz2#acd276486a0067bee3098590f0952a0f +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_2.tar.bz2#9b13816a39904084556126a6ce7fd0d0 +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_5.tar.bz2#0856c59f9ddb710c640dc0428d66b1b7 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_1.tar.bz2#358beb228a53b5e1031862de3525d1d3 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py38h0a891b7_1.tar.bz2#83df0e9e3faffc295f12607438691465 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_0.tar.bz2#b2247bb2492e261c25fabbbb2c7a23b5 -https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1004.tar.bz2#9fcaaca218dcfeb8da806d4fd4824aa0 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_0.tar.bz2#df081ec90a13f53fe522c8e876d3f0cf -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py38h43d8883_0.tar.bz2#0650a251fd701bbe5ac44e74cf632af8 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.1-py38h2b5fc30_0.tar.bz2#692d3e8efeb7f290daafa660ec7f1154 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.2-pyhd8ed1ab_0.tar.bz2#9993f51a02fc8338837421211b53ab19 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py38h0a891b7_0.tar.bz2#401adaccd86738f95a247d2007d925cf -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py38h0a891b7_0.tar.bz2#44421904760e9f5ae2035193e04360f0 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_1.tar.bz2#cc87260724704301cc768386aa51e801 +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.2-py38h2b5fc30_2.tar.bz2#188b896c30c5b8f6825451fc0db8185e +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.10.2-pyhd8ed1ab_0.tar.bz2#6f837aa0cbc910b39207fe5d97dfdf1e +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h71d37f0_1007.tar.bz2#c8d3d8f137f8af7b1daca318131223b1 +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py38h8f669ce_0.tar.bz2#f91da48c62c91659da28bd95559c75ff +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.1-py38h8f669ce_1.tar.bz2#75f37fc81d6513ba5db1e05301671a2e https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py38h578d9bd_0.tar.bz2#1fdabff56623511910fef3b418ff07a2 -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h71d37f0_2.tar.bz2#cdef2f7b0e263e338016da4b77ae4c0b -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h71d37f0_1.tar.bz2#704f1776af689de568514b0ff9dd0fbe -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py38hea3f02b_0.tar.bz2#b232edb409c6a79e5921b3591c56b716 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py38h3b45516_0.tar.bz2#d8621497bcc7b369ef9cce25d5a58aeb -https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py38hfa26641_0.tar.bz2#b869c6b54a02c92fac8b10c0d9b32e43 +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2.tar.bz2#d30399a3c636c75cfd3460c92effa960 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_1.tar.bz2#7b69c1e959b9f2ef360d2927284042cc +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hc9bb657_1.tar.bz2#1d08682ad092550a728bf45fce568f4e +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.3-py38hfa26641_0.tar.bz2#be1c99ff98790fe5179aa82f088ff505 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 -https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_2.tar.bz2#3f6ce81c7d28563fe2af763d9ff43e62 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h71d37f0_0.tar.bz2#b9e7f6f7509496a4a62906d02dfe3128 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar.bz2#82b3797d08a43a101b645becbb938e65 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_1.tar.bz2#d21ffab1bfd053f55087695d5a78c449 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.6-pyhd8ed1ab_0.tar.bz2#8c3563a8e310ea9a727f07caa0341ec2 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py38hb021067_0.tar.bz2#315ee5c0fbee508e739ddfac2bf8f600 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.1-py38hb021067_1.tar.bz2#c4d6a40038af11440cdb21170669a42b https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_0.tar.bz2#6ddbd9abb62e70243702c006b81c63e4 -https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38h606536b_0.tar.bz2#38fc3704565e44fb9fcdfaded03eee76 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_2.tar.bz2#ad6437509a14f1e8e5b8a354f93f340c +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38hd475de4_1.tar.bz2#fee89fda76666a7cafa20ec721aa9d5e https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_0.tar.bz2#ac8aa845f1177901eecf1518997ea0a1 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_0.tar.bz2#59ece9f652baf50ee6b842db833896ae -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_1.tar.bz2#38d9029214399e4bfc378b62b0171bf0 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_2.tar.bz2#cfa725eff634872f90dcd5ebf8e8dc1a https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py38h578d9bd_0.tar.bz2#602eb908e81892115c1405c9d99abd56 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.1-py38h578d9bd_1.tar.bz2#cd81b06cc1d9f631b969bf59633c95b0 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a + diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 76de50e763..da844ecd29 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -8,33 +8,34 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.36.1-hea4e1c9_2.tar.bz2#bd4f2e711b39af170e7ff15163fe87ee -https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.1.0-hdcd56e2_16.tar.bz2#b02605b875559ff99f04351fd5040760 -https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.1.0-ha89aaad_16.tar.bz2#6f5ba041a41eb102a1027d9e68731be7 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hc81fddc_0.tar.bz2#c2719e2faa7bd7076d3a4b52271e5622 +https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 +https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022d-h191b570_0.tar.bz2#456b5b1d99e7a9654b331bcd82e71042 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022f-h191b570_0.tar.bz2#e366350e2343a798e29833286abe2560 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 -https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.1.0-h69a702a_16.tar.bz2#6bf15e29a20f614b18ae89368260d0a2 -https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.1.0-h8d9b700_16.tar.bz2#f013cf7749536ce43d82afbffdf499ab +https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d +https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373 https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2#73aaf86a425cc6e73fcf236a5a46396d https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2#fee5683a3f04bd15cbd8318b096a27ab -https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.1.0-h8d9b700_16.tar.bz2#4f05bc9844f7c101e6e147dab3c88d5c -https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.7.2-h166bdaf_0.tar.bz2#4a826cd983be6c8fff07a64b6d2079e7 +https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-12.2.0-h65d4601_19.tar.bz2#e4c94f80aef025c17ab0828cd85ef535 +https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.8-h166bdaf_0.tar.bz2#be733e69048951df1e4b4b7bb8c7666f https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9c69a24ad678ffce24c6543a0176b00 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a -https://conda.anaconda.org/conda-forge/linux-64/expat-2.4.9-h27087fc_0.tar.bz2#493ac8b2503a949aebe33d99ea0c284f +https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 -https://conda.anaconda.org/conda-forge/linux-64/gettext-0.19.8.1-h27087fc_1009.tar.bz2#17f91dc8bb7a259b02be5bfb2cd2395f +https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 +https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f -https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_7.tar.bz2#f82dc1c78bcf73583f2656433ce2933c +https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 @@ -46,14 +47,15 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/linux-64/libudev1-249-h166bdaf_4.tar.bz2#dc075ff6fcb46b3d3c7652e543d5f334 +https://conda.anaconda.org/conda-forge/linux-64/libudev1-251-h166bdaf_0.tar.bz2#2fbd69c19564f7609d92ad887d11df98 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 -https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.12-h166bdaf_4.tar.bz2#6a2e5b333ba57ce7eec61e90260cbb79 +https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1q-h166bdaf_0.tar.bz2#07acc367c7fc8b716770cd5b36d31717 +https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -66,43 +68,41 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_4.tar.bz2#dd3e1941dd06f64cb88647d2f7ff8aaa https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b -https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_7.tar.bz2#37a460703214d0d1b421e2a47eb5e6d0 -https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_7.tar.bz2#785a9296ea478eb78c47593c4da6550f -https://conda.anaconda.org/conda-forge/linux-64/libcap-2.65-ha37c62d_0.tar.bz2#2c1c43f5442731b58e070bcee45a86ec +https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 +https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 +https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 -https://conda.anaconda.org/conda-forge/linux-64/libflac-1.3.4-h27087fc_0.tar.bz2#620e52e160fd09eb8772dedd46bb19ef -https://conda.anaconda.org/conda-forge/linux-64/libllvm14-14.0.6-he0ac6c6_0.tar.bz2#f5759f0c80708fbf9c4836c0cb46d0fe +https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 -https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.2-h4c7fe37_1.tar.bz2#d543c0192b13a1d0236367d3fb1e022c +https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.30-haf5c9bc_1.tar.bz2#62b588b2a313ac3d9c2ead767baa3b5d +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 -https://conda.anaconda.org/conda-forge/linux-64/portaudio-19.6.0-h8e90077_6.tar.bz2#2935b98de57e1f261ef8253655a8eb80 https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 -https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.12-h166bdaf_4.tar.bz2#995cc7813221edbc25a3db15357599a0 +https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 -https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_7.tar.bz2#1699c1211d56a23c66047524cd76796e +https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-14.0.6-default_h3a83d3e_0.tar.bz2#cdbd49e0ab5c5a6c522acb8271977d4c -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.0-h7a41b64_0.tar.bz2#fe768553d0fe619bb9704e3c79c0ee2e +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h7a41b64_0.tar.bz2#5635a2490d52955dc3192d901434c26d https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.0.31-h9c3ff4c_1.tar.bz2#fc4b6d93da04731db7601f2a1b1dc96a +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.3-h63197d8_1.tar.bz2#556ee34231614bb1d196e1c0ee693bb8 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.30-h28c427c_1.tar.bz2#0bd292db365c83624316efc2764d9f16 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -110,18 +110,18 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bd https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_7.tar.bz2#3889dec08a472eb0f423e5609c76bde1 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.0-hc2a2eb6_1.tar.bz2#139ace7da04f011abbd531cb2a9840ee +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.0-h6239696_0.tar.bz2#d2db078274532eeab50a06d65172a3c4 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_0.tar.bz2#df71cc96499592e632046ee20c2c3bd0 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.18-h8c3723f_1003.tar.bz2#9cb956b6605cfc7d8ee1b15e96bd88ba -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.12-hddcbb42_0.tar.bz2#797117394a4aa588de6d741b06fad80f -https://conda.anaconda.org/conda-forge/linux-64/libclang-14.0.6-default_h2e3cab8_0.tar.bz2#eb70548da697e50cefa7ba939d57d001 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.3-default_h3a83d3e_0.tar.bz2#736feeb04fac846a0e2cb5babec0b8c8 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.85.0-h7bff187_0.tar.bz2#054fb5981fdbe031caeb612b71d85f84 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_0.tar.bz2#d3126b425a04ed2360da1e651cef1b2d +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 @@ -135,35 +135,38 @@ https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.b https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e +https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb -https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.5-pyhd8ed1ab_0.tar.bz2#c267da48ce208905d7d976d49dfd9433 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.85.0-h7bff187_0.tar.bz2#a8ac96d6b09b8ed5b0ac6563901e2195 +https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.0-pyhd8ed1ab_0.tar.bz2#da409b864dc21631820c81973df42587 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.8.2-pyhd8ed1ab_0.tar.bz2#140dc6615896e7d4be1059a63370be93 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.0-h6239696_0.tar.bz2#60e6c8c867cdac0fc5f52fbbdfd7a057 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.10.0-pyhd8ed1ab_0.tar.bz2#ee4d78b97e857cb8d845c8fa4c898433 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_0.tar.bz2#2addd7ce0b4dffd5818d29f174c2d5d8 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.3-default_h2e3cab8_0.tar.bz2#09f01447ebab86a3e4f9dbf8cc72c94e https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 +https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0868958_9.tar.bz2#5bca71f0cf9b86ec58dd9d6216a3ffaf -https://conda.anaconda.org/conda-forge/noarch/py-1.11.0-pyh6c4a22f_0.tar.bz2#b4613d7e7a493916d867842a6a148054 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0d2025b_11.tar.bz2#316026c5f80d8b5b9666e87b8614ac6c https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.4-pyhd8ed1ab_0.tar.bz2#fc0dcaf9761d042fb8ac9128ce03fddb -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.4.1-pyhd8ed1ab_0.tar.bz2#d61d9f25af23c24002e659b854c6f5ae +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.0-pyhd8ed1ab_0.tar.bz2#462466739c786f85f9ed555f87099fe1 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -178,82 +181,81 @@ https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 -https://conda.anaconda.org/conda-forge/noarch/zipp-3.8.1-pyhd8ed1ab_0.tar.bz2#a3508a0c850745b875de88aea4c40cc5 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_0.tar.bz2#61e961a94c8fd535e4496b17e7452dfe -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_2.tar.bz2#fea5dea40592ea943aa56f4935308ee4 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.20.3-hd4edc92_2.tar.bz2#153cfb02fb8be7dd7cabcbcb58a63053 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.2.0-hf9f4e7c_0.tar.bz2#3c5f4fbd64c7254fbe246ca9d87863b6 -https://conda.anaconda.org/conda-forge/linux-64/importlib-metadata-4.11.4-py39hf3d152e_0.tar.bz2#4c2a0eabf0b8980b2c755646a6f750eb -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_0.tar.bz2#e8d1310648c189d6d11a2e13f73da1fe -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_h06c54e2_4.tar.bz2#491803a7356c6a668a84d71f491c4014 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_1.tar.bz2#7cda413e43b252044a270c2477031c5c -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_2.tar.bz2#b809706525f081610469169b671b2600 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_2.tar.bz2#fc70a133e8162f51e363cff3b6dc741c +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411 +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_2.tar.bz2#c678e07e7862b3157fb9f6d908233ffa +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_3.tar.bz2#278dadf837f97b29c78439f468bfb563 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.3-py39hba7629e_0.tar.bz2#320e25179733ec4a2ecffcebc8abbc80 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py39h3d75532_1.tar.bz2#ba4f1c0466d4ba7f53090c150fc88434 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hd5dbb17_2.tar.bz2#3b74a959f6a8008f5901de60b3572c09 -https://conda.anaconda.org/conda-forge/noarch/pip-22.2.2-pyhd8ed1ab_0.tar.bz2#0b43abe4d3ee93e82742d37def53a836 -https://conda.anaconda.org/conda-forge/linux-64/pluggy-1.0.0-py39hf3d152e_3.tar.bz2#c375c89340e563053f3656c7f134d265 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hf3a2cdf_3.tar.bz2#2bd111c38da69056e5fe25a51b832eba +https://conda.anaconda.org/conda-forge/noarch/pip-22.3-pyhd8ed1ab_0.tar.bz2#6f4c6de9fed2a9bd8043283611b09587 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.2-py39hb9d737c_0.tar.bz2#1e7ffe59e21862559e06b981817e5058 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.3-py39hb9d737c_1.tar.bz2#0f50870227259af1edbb7b8bd04f2e07 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h2c22827_1.tar.bz2#a1ca42c2a746601d42f27bbcb7f6acfc +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h14a8356_2.tar.bz2#5d93c781338ff274a0b3dc3d901e19a6 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_1.tar.bz2#9f71f72dad4fd7b9da7bcc2ba64505bc -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_4.tar.bz2#dcc47a3b751508507183d17e569805e5 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_0.tar.bz2#a3c57360af28c0d9956622af99a521cd +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_2.tar.bz2#b643f1e19306b75a6013d77228156076 +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2#ef9db3c38ae7275f6b14491cfe61a248 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_1.tar.bz2#8a7d309b08cff6386fe384aa10dd3748 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-14.0.0-py39hb9d737c_1.tar.bz2#ef84376736d1e8a814ccb06d1d814e6f -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_0.tar.bz2#165e71a44187ac22e2e1669fd3ca2392 -https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1004.tar.bz2#05a99367d885ec9990f25e74128a8a08 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_0.tar.bz2#4b108127973b66b36edd6449aa6afde0 -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.5-py39hf939315_0.tar.bz2#c9ff0dfb602033b1f1aaf323b58e04fa -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.1-py39hd97740a_0.tar.bz2#db3436b5db460fa721859db55694d8ff -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.9.2-pyhd8ed1ab_0.tar.bz2#9993f51a02fc8338837421211b53ab19 -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.37.4-py39hb9d737c_0.tar.bz2#10ba86e931afab1164deae6b954b5f0d -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.20.3-h57caac4_2.tar.bz2#58838c4ca7d1a5948f5cdcbb8170d753 +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py39hb9d737c_0.tar.bz2#230d65004135bf312504a1bbcb0c7a08 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_1.tar.bz2#f72ebd9b581b7d091a4d509a45ece280 +https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.2-py39hd97740a_2.tar.bz2#2f615a211058c94af786b92cefbc3d5b +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.10.2-pyhd8ed1ab_0.tar.bz2#6f837aa0cbc910b39207fe5d97dfdf1e +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39hd257fcd_1007.tar.bz2#e7527bcf8da0dad996aaefd046c17480 +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.0-py39h4661b88_0.tar.bz2#ae807099430cd22b09b869b0536425b7 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.1-py39h4661b88_1.tar.bz2#d541bbe75ce0f2679344ead981b2f858 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 -https://conda.anaconda.org/conda-forge/linux-64/pytest-7.1.3-py39hf3d152e_0.tar.bz2#b807481ba94ec32bc742f2fe775d0bff -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39hd257fcd_2.tar.bz2#644be766007a1dc7590c3277647f81a1 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39hd257fcd_1.tar.bz2#c4b698994b2d8d2e659ae02202e6abe4 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.1-py39h8ba3f38_0.tar.bz2#beed054d4979cd70690aea2b257a6d55 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_0.tar.bz2#743074b7a216807886f7e8f6d497cceb -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.4-py39h68ae834_0.tar.bz2#e871ee7de5bfa95095256e95e30be2a6 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.6.2-py39h5a03fae_0.tar.bz2#e37704c6be07b8b14ffc1ce912802ce0 +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2.tar.bz2#234ad9828eca1caf0f2fdcb4a24ad816 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_1.tar.bz2#f26da935bc8e8ede63e075ffb5fa8e45 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h5b5020f_1.tar.bz2#9d969b4f06adee93e9cf4a62d8056cb6 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.3-py39h5a03fae_0.tar.bz2#85e7fc94451c6b86db6e2789fe92288d https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 -https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_2.tar.bz2#5a3bb9dc2fe08a4a6f2b61548a1431d6 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39hd257fcd_0.tar.bz2#e0f1f1d3013be31359d3ac635b288469 +https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar.bz2#0f11bcdf9669a5ae0f39efd8c830209a +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_1.tar.bz2#26e4d1f6a01fb44597431eacd6e0fe96 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.6-pyhd8ed1ab_0.tar.bz2#8c3563a8e310ea9a727f07caa0341ec2 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.0-py39hf9fd14e_0.tar.bz2#bdc55b4069ab9d2f938525c4cf90def0 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.1-py39hf9fd14e_1.tar.bz2#f8d7b01f5cc777cbd671e86c530deba9 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.0.0-pyhd8ed1ab_1.tar.bz2#2e7e3630919d29c8216bfa2cd643d79e -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_0.tar.bz2#1fd9112714d50ee5be3dbf4fd23964dc -https://conda.anaconda.org/conda-forge/noarch/pytest-forked-1.4.0-pyhd8ed1ab_0.tar.bz2#95286e05a617de9ebfe3246cecbfb72f -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hc525480_0.tar.bz2#abd0f27f5e84cd0d5ae14d22b08795d7 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39hf5d525c_0.tar.bz2#b99ba7383d1c9dd18445dfff08439c48 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_2.tar.bz2#306f1a018668f06a0bd89350a3f62c07 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39hfafebef_1.tar.bz2#f6777b70ffcae0820aa10027610875c8 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_0.tar.bz2#314c8cb1538706f62ec36cf64370f2b2 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_0.tar.bz2#5ed8f83afff3b64fa91f7a6af8d7ff04 -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-2.5.0-pyhd8ed1ab_0.tar.bz2#1fdd1f3baccf0deb647385c677a1a48e +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_1.tar.bz2#921f8a7c2a16d18d7168fdac88b2adfe +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_2.tar.bz2#384809c51fb2adc04773f6fa097cd051 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.0-py39hf3d152e_0.tar.bz2#93f29e4d6f852de18384412b0e0d03b5 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.1-py39hf3d152e_1.tar.bz2#b1df0bf44b0652eb428409d1c16c8ef4 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a + From 8744f67724b86ccfbfa60516b31daa103c1b2f66 Mon Sep 17 00:00:00 2001 From: Hamish Steptoe Date: Fri, 4 Nov 2022 17:34:26 +0000 Subject: [PATCH 263/319] Improve iris.pandas cube -> data.frame (#4669) * Update as_data_frame() * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Minor typo fixes * Matching data raveling with dimension meshgrid * Revise test_simple to check long-syle dataframe * Revise NaN and 1D dataframe tests * Better pandas.MultiIndex solution * Add 3D test case * Fixes for cube with partially defined dims * Update tests for partially defined dims * Update time tests * Reuse _as_pandas_coord() * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove Series conversion * Remove option for copy * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * First go at adding aux coords * First go at adding global attributes * Update doc string * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix for time based AuxCoords * Minor misc fixes * Fix copy issue * Fix black weirdness and add copy tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add attributes test * Name fixes * Add AuxCoord test * Improved aux coord indexing * Blacking * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Preserve index on aux coord merge * Simplify adding AuxCoords * Add assertRaises test for attributes * Better dim coord making logic * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Updates to docstring * Handle multidim AuxCoords * Add handling for AuxCoords + scalar coord info * Fix STASH attribute handling * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Improve and simplify dim extraction * Re-fix copy behaviour * Doc updates * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add Series example * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * What's New update * Add `as_series` depreciation warning * Fix indent * flake8 fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix deprecation warning Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Fix pytest styles Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Fix pytest styles Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Minor syntax change to list Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Update error style Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Fix error type * Reinstate copy warning behaviour * Add author link * Remove add_global_attribute code * Further global attribute code removal * Add instance checking test * Improve _make_dim_coord_list efficiency * Correct list ouput * Make tests more efficient * Add scalar coordinate test * Docstring fixes * Refactor making of aux_coord_list * Fix type error * Add masked array -> nan warning in doc * Consolidate use of `_as_pandas_coord` * Roll back breaking _make_dim_coord_list changes * Raise error for Ancillary variables without dims * Ancillary variables tweaks * Ancillary variables tests * Split out metadata for consistency with `as_cubes` * Add cell_measure tests * Docstring fixes * Test kwarg fixes * `_make_aux_coord_list` optimisation * Refactor metadata merging * Roundabout :issue: fix (remove) * Roundabout :issue: fix (re-add) * Docstring typo fixes Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * Fix doctests * Update docs/src/whatsnew/latest.rst Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Further doctest fixes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 15 +- lib/iris/pandas.py | 335 +++++++++++++++++++--- lib/iris/tests/test_pandas.py | 514 +++++++++++++++++++--------------- 3 files changed, 596 insertions(+), 268 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d0b03d9304..d7f340737f 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -25,7 +25,7 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. Welcome to `@ESadek-MO`_ who made their first contribution to Iris 🎉 +#. Welcome to `@ESadek-MO`_ and `@hsteptoe`_ who made their first contribution to Iris 🎉 ✨ Features @@ -35,6 +35,12 @@ This document explains the changes made to Iris for this release non-existing paths, and added expansion functionality to :func:`~iris.io.save`. (:issue:`4772`, :pull:`4913`) +#. `@hsteptoe`_ and `@trexfeathers`_ (reviewer) added :func:`iris.pandas.as_data_frame`, + which provides improved conversion of :class:`~iris.cube.Cube`\s to + :class:`~pandas.DataFrame`\s. This includes better handling of multiple + :class:`~iris.cube.Cube` dimensions, auxiliary coordinates and attribute information. + (:issue:`4526`, :pull:`4669`) + 🐛 Bugs Fixed ============= @@ -65,7 +71,10 @@ This document explains the changes made to Iris for this release 🔥 Deprecations =============== -#. N/A +#. `@hsteptoe`_ and `@trexfeathers`_ (reviewer) deprecated + :func:`iris.pandas.as_series` in favour of the new + :func:`iris.pandas.as_data_frame` - see `✨ Features`_ for more details. + (:pull:`4669`) 🔗 Dependencies @@ -92,7 +101,7 @@ This document explains the changes made to Iris for this release Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: - +.. _@hsteptoe: https://github.com/hsteptoe .. comment diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index b00eb3f117..1b4e444efc 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -9,7 +9,6 @@ See also: http://pandas.pydata.org/ """ - import datetime from itertools import chain, combinations import warnings @@ -157,8 +156,8 @@ def as_cube( Example usage:: - as_cube(series, calendars={0: cf_units.CALENDAR_360_DAY}) - as_cube(data_frame, calendars={1: cf_units.CALENDAR_STANDARD}) + as_cube(series, calendars={0: cf_units.CALENDAR_360_DAY}) + as_cube(data_frame, calendars={1: cf_units.CALENDAR_STANDARD}) """ message = ( @@ -518,26 +517,92 @@ def _get_base(array): raise AssertionError(msg) +def _make_dim_coord_list(cube): + """Get Dimension coordinates.""" + outlist = [] + for dimn in range(cube.ndim): + dimn_coord = cube.coords(dimensions=dimn, dim_coords=True) + if dimn_coord: + outlist += [ + [dimn_coord[0].name(), _as_pandas_coord(dimn_coord[0])] + ] + else: + outlist += [[f"dim{dimn}", range(cube.shape[dimn])]] + return list(zip(*outlist)) + + +def _make_aux_coord_list(cube): + """Get Auxiliary coordinates.""" + outlist = [] + for aux_coord in cube.coords(dim_coords=False): + outlist += [ + [ + aux_coord.name(), + cube.coord_dims(aux_coord), + _as_pandas_coord(aux_coord), + ] + ] + return list(chain.from_iterable([outlist])) + + +def _make_ancillary_variables_list(cube): + """Get Ancillary variables.""" + outlist = [] + for ancil_var in cube.ancillary_variables(): + outlist += [ + [ + ancil_var.name(), + cube.ancillary_variable_dims(ancil_var), + ancil_var.data, + ] + ] + return list(chain.from_iterable([outlist])) + + +def _make_cell_measures_list(cube): + """Get cell measures.""" + outlist = [] + for cell_measure in cube.cell_measures(): + outlist += [ + [ + cell_measure.name(), + cube.cell_measure_dims(cell_measure), + cell_measure.data, + ] + ] + return list(chain.from_iterable([outlist])) + + def as_series(cube, copy=True): """ Convert a 1D cube to a Pandas Series. - Args: - - * cube - The cube to convert to a Pandas Series. - - Kwargs: - - * copy - Whether to make a copy of the data. - Defaults to True. Must be True for masked data. + .. deprecated:: 3.4.0 + This function is scheduled for removal in a future release, being + replaced by :func:`iris.pandas.as_data_frame`, which offers improved + multi dimension handling. - .. note:: + Parameters + ---------- + cube: :class:`Cube` + The cube to convert to a Pandas Series. + copy : bool, default=True + Whether to make a copy of the data. + Defaults to True. Must be True for masked data. - This function will copy your data by default. - If you have a large array that cannot be copied, - make sure it is not masked and use copy=False. + Notes + ----- + This function will copy your data by default. + If you have a large array that cannot be copied, + make sure it is not masked and use copy=False. """ + message = ( + "iris.pandas.as_series has been deprecated, and will be removed in a " + "future release. Please use iris.pandas.as_data_frame instead." + ) + warn_deprecated(message) + data = cube.data if ma.isMaskedArray(data): if not copy: @@ -545,61 +610,241 @@ def as_series(cube, copy=True): data = data.astype("f").filled(np.nan) elif copy: data = data.copy() - index = None if cube.dim_coords: index = _as_pandas_coord(cube.dim_coords[0]) - series = pandas.Series(data, index) if not copy: _assert_shared(data, series) - return series -def as_data_frame(cube, copy=True): +def as_data_frame( + cube, + copy=True, + add_aux_coords=False, + add_cell_measures=False, + add_ancillary_variables=False, +): """ Convert a 2D cube to a Pandas DataFrame. - Args: + :attr:`~iris.cube.Cube.dim_coords` and :attr:`~iris.cube.Cube.data` are + flattened into a long-style :class:`~pandas.DataFrame`. Other + :attr:`~iris.cube.Cube.aux_coords`, :attr:`~iris.cube.Cube.aux_coords` and :attr:`~iris.cube.Cube.attributes` + may be optionally added as additional :class:`~pandas.DataFrame` columns. - * cube - The cube to convert to a Pandas DataFrame. + Parameters + ---------- + cube: :class:`~iris.cube.Cube` + The :class:`~iris.cube.Cube` to be converted to a :class:`pandas.DataFrame`. + copy : bool, default=True + Whether the :class:`pandas.DataFrame` is a copy of the the Cube + :attr:`~iris.cube.Cube.data`. This option is provided to help with memory + size concerns. + add_aux_coords : bool, default=False + If True, add all :attr:`~iris.cube.Cube.aux_coords` (including scalar + coordinates) to the returned :class:`pandas.DataFrame`. + add_cell_measures : bool, default=False + If True, add :attr:`~iris.cube.Cube.cell_measures` to the returned + :class:`pandas.DataFrame`. + add_ancillary_variables: bool, default=False + If True, add :attr:`~iris.cube.Cube.ancillary_variables` to the returned + :class:`pandas.DataFrame`. - Kwargs: + Returns + ------- + :class:`~pandas.DataFrame` + A :class:`~pandas.DataFrame` with :class:`~iris.cube.Cube` dimensions + forming a :class:`~pandas.MultiIndex` - * copy - Whether to make a copy of the data. - Defaults to True. Must be True for masked data - and some data types (see notes below). + Notes + ----- + Dask ``DataFrame``\\s are not supported. - .. note:: + A :class:`~pandas.MultiIndex` :class:`~pandas.DataFrame` is returned by default. + Use the :meth:`~pandas.DataFrame.reset_index` to return a + :class:`~pandas.DataFrame` without :class:`~pandas.MultiIndex` levels. Use + 'inplace=True` to preserve memory object reference. - This function will copy your data by default. - If you have a large array that cannot be copied, - make sure it is not masked and use copy=False. + :class:`~iris.cube.Cube` data `dtype` is preserved. - .. note:: + Warnings + -------- + Where the :class:`~iris.cube.Cube` contains masked values, these become + :data:`numpy.nan` in the returned :class:`~pandas.DataFrame`. - Pandas will sometimes make a copy of the array, - for example when creating from an int32 array. - Iris will detect this and raise an exception if copy=False. + Examples + -------- + >>> import iris + >>> from iris.pandas import as_data_frame + + Convert a simple :class:`~iris.cube.Cube`: + + >>> path = iris.sample_data_path('ostia_monthly.nc') + >>> cube = iris.load_cube(path) + >>> df = as_data_frame(cube) + >>> print(df) + ... # doctest: +NORMALIZE_WHITESPACE + surface_temperature + time latitude longitude + 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 + 0.833333 301.785004 + 1.666667 301.820984 + 2.500000 301.865234 + 3.333333 301.926819 + ... ... + 2010-09-16 00:00:00 4.444450 355.833313 298.779938 + 356.666656 298.913147 + 357.500000 NaN + 358.333313 NaN + 359.166656 298.995148 + + [419904 rows x 1 columns] + + Using ``add_aux_coords=True`` maps :class:`~iris.coords.AuxCoord` and scalar + coordinate information to the :class:`~pandas.DataFrame`: + + >>> df = as_data_frame(cube, add_aux_coords=True) + >>> print(df) + ... # doctest: +NORMALIZE_WHITESPACE + surface_temperature ... forecast_reference_time + time latitude longitude ... + 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 ... 2006-04-16 12:00:00 + 0.833333 301.785004 ... 2006-04-16 12:00:00 + 1.666667 301.820984 ... 2006-04-16 12:00:00 + 2.500000 301.865234 ... 2006-04-16 12:00:00 + 3.333333 301.926819 ... 2006-04-16 12:00:00 + ... ... ... ... + 2010-09-16 00:00:00 4.444450 355.833313 298.779938 ... 2010-09-16 12:00:00 + 356.666656 298.913147 ... 2010-09-16 12:00:00 + 357.500000 NaN ... 2010-09-16 12:00:00 + 358.333313 NaN ... 2010-09-16 12:00:00 + 359.166656 298.995148 ... 2010-09-16 12:00:00 + + [419904 rows x 3 columns] + + To add netCDF global attribution information to the :class:`~pandas.DataFrame`, + add a column directly to the :class:`~pandas.DataFrame`: + + >>> df['STASH'] = str(cube.attributes['STASH']) + >>> print(df) + ... # doctest: +NORMALIZE_WHITESPACE + surface_temperature ... STASH + time latitude longitude ... + 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 ... m01s00i024 + 0.833333 301.785004 ... m01s00i024 + 1.666667 301.820984 ... m01s00i024 + 2.500000 301.865234 ... m01s00i024 + 3.333333 301.926819 ... m01s00i024 + ... ... ... ... + 2010-09-16 00:00:00 4.444450 355.833313 298.779938 ... m01s00i024 + 356.666656 298.913147 ... m01s00i024 + 357.500000 NaN ... m01s00i024 + 358.333313 NaN ... m01s00i024 + 359.166656 298.995148 ... m01s00i024 + + [419904 rows x 4 columns] + + To return a :class:`~pandas.DataFrame` without a :class:`~pandas.MultiIndex` + use :meth:`~pandas.DataFrame.reset_index`. Optionally use `inplace=True` keyword + to modify the DataFrame rather than creating a new one: + + >>> df.reset_index(inplace=True) + >>> print(df) + ... # doctest: +NORMALIZE_WHITESPACE + time latitude ... forecast_reference_time STASH + 0 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 + 1 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 + 2 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 + 3 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 + 4 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 + ... ... ... ... ... ... + 419899 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 + 419900 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 + 419901 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 + 419902 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 + 419903 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 + + [419904 rows x 7 columns] + + To retrieve a :class:`~pandas.Series` from `df` :class:`~pandas.DataFrame`, + subselect a column: + + >>> df['surface_temperature'] + 0 301.659271 + 1 301.785004 + 2 301.820984 + 3 301.865234 + 4 301.926819 + ... + 419899 298.779938 + 419900 298.913147 + 419901 NaN + 419902 NaN + 419903 298.995148 + Name: surface_temperature, Length: 419904, dtype: float32 """ - data = cube.data + + def merge_metadata(meta_var_list): + """Add auxiliary cube metadata to the DataFrame""" + nonlocal data_frame + for meta_var_name, meta_var_index, meta_var in meta_var_list: + if not meta_var_index: + # Broadcast any meta var informtation without an associated + # dimension over the whole DataFrame + data_frame[meta_var_name] = meta_var.squeeze() + else: + meta_df = pandas.DataFrame( + meta_var.ravel(), + columns=[meta_var_name], + index=pandas.MultiIndex.from_product( + [coords[i] for i in meta_var_index], + names=[coord_names[i] for i in meta_var_index], + ), + ) + # Merge to main data frame + data_frame = pandas.merge( + data_frame, + meta_df, + left_index=True, + right_index=True, + sort=False, + ) + return data_frame + + # Checks + if not isinstance(cube, iris.cube.Cube): + raise TypeError( + f"Expected input to be iris.cube.Cube instance, got: {type(cube)}" + ) + if copy: + data = cube.data.copy() + else: + data = cube.data if ma.isMaskedArray(data): if not copy: raise ValueError("Masked arrays must always be copied.") data = data.astype("f").filled(np.nan) - elif copy: - data = data.copy() - index = columns = None - if cube.coords(dimensions=[0]): - index = _as_pandas_coord(cube.coord(dimensions=[0])) - if cube.coords(dimensions=[1]): - columns = _as_pandas_coord(cube.coord(dimensions=[1])) + # Extract dim coord information: separate lists for dim names and dim values + coord_names, coords = _make_dim_coord_list(cube) + # Make base DataFrame + index = pandas.MultiIndex.from_product(coords, names=coord_names) + data_frame = pandas.DataFrame( + data.ravel(), columns=[cube.name()], index=index + ) - data_frame = pandas.DataFrame(data, index, columns) - if not copy: - _assert_shared(data, data_frame) + if add_aux_coords: + data_frame = merge_metadata(_make_aux_coord_list(cube)) + if add_ancillary_variables: + data_frame = merge_metadata(_make_ancillary_variables_list(cube)) + if add_cell_measures: + data_frame = merge_metadata(_make_cell_measures_list(cube)) - return data_frame + if copy: + return data_frame.reorder_levels(coord_names).sort_index() + else: + data_frame.reorder_levels(coord_names).sort_index(inplace=True) + return data_frame diff --git a/lib/iris/tests/test_pandas.py b/lib/iris/tests/test_pandas.py index f47df75def..4841108aa2 100644 --- a/lib/iris/tests/test_pandas.py +++ b/lib/iris/tests/test_pandas.py @@ -42,110 +42,6 @@ import iris.pandas -@skip_pandas -class TestAsSeries(tests.IrisTest): - """Test conversion of 1D cubes to Pandas using as_series()""" - - def test_no_dim_coord(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") - series = iris.pandas.as_series(cube) - expected_index = np.array([0, 1, 2, 3, 4]) - self.assertArrayEqual(series, cube.data) - self.assertArrayEqual(series.index, expected_index) - - def test_simple(self): - cube = Cube(np.array([0, 1, 2, 3, 4.4]), long_name="foo") - dim_coord = DimCoord([5, 6, 7, 8, 9], long_name="bar") - cube.add_dim_coord(dim_coord, 0) - expected_index = np.array([5, 6, 7, 8, 9]) - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data) - self.assertArrayEqual(series.index, expected_index) - - def test_masked(self): - data = np.ma.MaskedArray([0, 1, 2, 3, 4.4], mask=[0, 1, 0, 1, 0]) - cube = Cube(data, long_name="foo") - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data.astype("f").filled(np.nan)) - - def test_time_standard(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") - time_coord = DimCoord( - [0, 100.1, 200.2, 300.3, 400.4], - long_name="time", - units="days since 2000-01-01 00:00", - ) - cube.add_dim_coord(time_coord, 0) - expected_index = [ - datetime.datetime(2000, 1, 1, 0, 0), - datetime.datetime(2000, 4, 10, 2, 24), - datetime.datetime(2000, 7, 19, 4, 48), - datetime.datetime(2000, 10, 27, 7, 12), - datetime.datetime(2001, 2, 4, 9, 36), - ] - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data) - assert list(series.index) == expected_index - - def test_time_360(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") - time_unit = cf_units.Unit( - "days since 2000-01-01 00:00", calendar=cf_units.CALENDAR_360_DAY - ) - time_coord = DimCoord( - [0, 100.1, 200.2, 300.3, 400.4], long_name="time", units=time_unit - ) - cube.add_dim_coord(time_coord, 0) - expected_index = [ - cftime.Datetime360Day(2000, 1, 1, 0, 0), - cftime.Datetime360Day(2000, 4, 11, 2, 24), - cftime.Datetime360Day(2000, 7, 21, 4, 48), - cftime.Datetime360Day(2000, 11, 1, 7, 12), - cftime.Datetime360Day(2001, 2, 11, 9, 36), - ] - - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data) - self.assertArrayEqual(series.index, expected_index) - - def test_copy_true(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") - series = iris.pandas.as_series(cube) - series[0] = 99 - assert cube.data[0] == 0 - - def test_copy_int32_false(self): - cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int32), long_name="foo") - series = iris.pandas.as_series(cube, copy=False) - series[0] = 99 - assert cube.data[0] == 99 - - def test_copy_int64_false(self): - cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int32), long_name="foo") - series = iris.pandas.as_series(cube, copy=False) - series[0] = 99 - assert cube.data[0] == 99 - - def test_copy_float_false(self): - cube = Cube(np.array([0, 1, 2, 3.3, 4]), long_name="foo") - series = iris.pandas.as_series(cube, copy=False) - series[0] = 99 - assert cube.data[0] == 99 - - def test_copy_masked_true(self): - data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) - cube = Cube(data, long_name="foo") - series = iris.pandas.as_series(cube) - series[0] = 99 - assert cube.data[0] == 0 - - def test_copy_masked_false(self): - data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) - cube = Cube(data, long_name="foo") - with pytest.raises(ValueError): - _ = iris.pandas.as_series(cube, copy=False) - - @skip_pandas class TestAsDataFrame(tests.IrisTest): """Test conversion of 2D cubes to Pandas using as_data_frame()""" @@ -154,66 +50,132 @@ def test_no_dim_coords(self): cube = Cube( np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" ) - expected_index = [0, 1] - expected_columns = [0, 1, 2, 3, 4] + expected_dim0 = np.repeat([0, 1], 5) + expected_dim1 = np.tile([0, 1, 2, 3, 4], 2) + expected_foo = np.arange(0, 10) data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) + self.assertArrayEqual(data_frame.foo.values, expected_foo) + self.assertArrayEqual( + data_frame.index.get_level_values("dim0"), expected_dim0 + ) + self.assertArrayEqual( + data_frame.index.get_level_values("dim1"), expected_dim1 + ) def test_no_x_coord(self): cube = Cube( np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" ) - y_coord = DimCoord([10, 11], long_name="bar") - cube.add_dim_coord(y_coord, 0) - expected_index = [10, 11] - expected_columns = [0, 1, 2, 3, 4] + dim0 = DimCoord([10, 11], long_name="bar") + cube.add_dim_coord(dim0, 0) + expected_bar = np.repeat([10, 11], 5) + expected_dim1 = np.tile([0, 1, 2, 3, 4], 2) + expected_foo = np.arange(0, 10) data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) + self.assertArrayEqual(data_frame.foo, expected_foo) + self.assertArrayEqual( + data_frame.index.get_level_values("bar"), expected_bar + ) + self.assertArrayEqual( + data_frame.index.get_level_values("dim1"), expected_dim1 + ) def test_no_y_coord(self): cube = Cube( np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" ) - x_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar") - cube.add_dim_coord(x_coord, 1) - expected_index = [0, 1] - expected_columns = [10, 11, 12, 13, 14] + dim1 = DimCoord([10, 11, 12, 13, 14], long_name="bar") + cube.add_dim_coord(dim1, 1) + expected_dim0 = np.repeat([0, 1], 5) + expected_bar = np.tile([10, 11, 12, 13, 14], 2) + expected_foo = np.arange(0, 10) data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) + self.assertArrayEqual(data_frame.foo, expected_foo.data) + self.assertArrayEqual( + data_frame.index.get_level_values("dim0"), expected_dim0 + ) + self.assertArrayEqual( + data_frame.index.get_level_values("bar"), expected_bar + ) - def test_simple(self): - cube = Cube( - np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" + def test_simple1D(self): + cube = Cube(np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), long_name="foo") + dim_coord = DimCoord( + [10, 11, 12, 13, 14, 15, 16, 17, 18, 19], long_name="bar" ) - x_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar") - y_coord = DimCoord([15, 16], long_name="milk") - cube.add_dim_coord(x_coord, 1) - cube.add_dim_coord(y_coord, 0) - expected_index = [15, 16] - expected_columns = [10, 11, 12, 13, 14] - data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) - - def test_masked(self): - data = np.ma.MaskedArray( - [[0, 1, 2, 3, 4.4], [5, 6, 7, 8, 9]], - mask=[[0, 1, 0, 1, 0], [1, 0, 1, 0, 1]], - ) - cube = Cube(data, long_name="foo") - expected_index = [0, 1] - expected_columns = [0, 1, 2, 3, 4] + cube.add_dim_coord(dim_coord, 0) + expected_bar = np.arange(10, 20) + expected_foo = np.arange(0, 10) data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data.astype("f").filled(np.nan)) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) + self.assertArrayEqual(data_frame.foo, expected_foo) + self.assertArrayEqual( + data_frame.index.get_level_values("bar"), expected_bar + ) + + def test_simple2D(self): + cube2d = Cube( + np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" + ) + dim0_coord = DimCoord([15, 16], long_name="milk") + dim1_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar") + cube2d.add_dim_coord(dim0_coord, 0) + cube2d.add_dim_coord(dim1_coord, 1) + expected_milk = np.repeat([15, 16], 5) + expected_bar = np.tile([10, 11, 12, 13, 14], 2) + expected_foo = np.arange(0, 10) + data_frame = iris.pandas.as_data_frame(cube2d) + self.assertArrayEqual(data_frame.foo, expected_foo) + self.assertArrayEqual( + data_frame.index.get_level_values("milk"), expected_milk + ) + self.assertArrayEqual( + data_frame.index.get_level_values("bar"), expected_bar + ) + + def test_simple3D(self): + cube3d = Cube( + np.array( + [ + [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], + [[10, 11, 12, 13, 14], [15, 16, 17, 18, 19]], + [[20, 21, 22, 23, 24], [25, 26, 27, 28, 29]], + ] + ), + long_name="foo", + ) + dim0_coord = DimCoord([1, 2, 3], long_name="milk") + dim1_coord = DimCoord([10, 11], long_name="bar") + dim2_coord = DimCoord([20, 21, 22, 23, 24], long_name="kid") + cube3d.add_dim_coord(dim0_coord, 0) + cube3d.add_dim_coord(dim1_coord, 1) + cube3d.add_dim_coord(dim2_coord, 2) + expected_milk = np.repeat([1, 2, 3], 10) + expected_bar = np.tile(np.repeat([10, 11], 5), 3) + expected_kid = np.tile([20, 21, 22, 23, 24], 6) + expected_foo = np.arange(0, 30) + data_frame = iris.pandas.as_data_frame(cube3d) + self.assertArrayEqual(data_frame.foo, expected_foo) + self.assertArrayEqual( + data_frame.index.get_level_values("milk"), expected_milk + ) + self.assertArrayEqual( + data_frame.index.get_level_values("bar"), expected_bar + ) + self.assertArrayEqual( + data_frame.index.get_level_values("kid"), expected_kid + ) + + def test_copy_false(self): + cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") + data_frame = iris.pandas.as_data_frame(cube, copy=False) + cube.data[2] = 99 + assert cube.data[2] == data_frame.foo[2] + + def test_copy_true(self): + cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") + data_frame = iris.pandas.as_data_frame(cube, copy=True) + cube.data[2] = 99 + assert cube.data[2] != data_frame.foo[2] def test_time_standard(self): cube = Cube( @@ -224,19 +186,47 @@ def test_time_standard(self): day_offsets, long_name="time", units="days since 2000-01-01 00:00" ) cube.add_dim_coord(time_coord, 1) + expected_ts = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + expected_time = np.array( + [ + cftime.DatetimeGregorian( + 2000, 1, 1, 0, 0, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2000, 4, 10, 2, 24, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2000, 7, 19, 4, 48, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2000, 10, 27, 7, 12, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2001, 2, 4, 9, 36, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2000, 1, 1, 0, 0, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2000, 4, 10, 2, 24, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2000, 7, 19, 4, 48, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2000, 10, 27, 7, 12, 0, 0, has_year_zero=False + ), + cftime.DatetimeGregorian( + 2001, 2, 4, 9, 36, 0, 0, has_year_zero=False + ), + ], + dtype=object, + ) data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - nanoseconds_per_day = 24 * 60 * 60 * 1000000000 - days_to_2000 = 365 * 30 + 7 - # pandas Timestamp class cannot handle floats in pandas Date: Wed, 9 Nov 2022 07:54:39 +0000 Subject: [PATCH 264/319] fix aggregated_by for derived coords (#4947) * fix aggregated_by for derived coords * fix and test for derived coords * improve tests, add whatsnew --- docs/src/whatsnew/latest.rst | 3 ++ lib/iris/cube.py | 15 +++++- .../unit/cube/test_Cube__aggregated_by.py | 48 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 5b23c38fe4..4a602f60dc 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -85,6 +85,9 @@ This document explains the changes made to Iris for this release :meth:`~iris.cube.Cube.cell_measure` and :meth:`~iris.cube.Cube.ancillary_variable`. (:issue:`4898`, :pull:`4928`) +#. `@stephenworsley`_ fixed a bug which caused derived coordinates to be realised + after calling :meth:`iris.cube.Cube.aggregated_by`. (:issue:`3637`, :pull:`4947`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/cube.py b/lib/iris/cube.py index 4e24c099fe..e45ac8ca2d 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -4076,8 +4076,9 @@ def aggregated_by( # coordinate dimension. shared_coords = list( filter( - lambda coord_: coord_ not in groupby_coords, - self.coords(contains_dimension=dimension_to_groupby), + lambda coord_: coord_ not in groupby_coords + and dimension_to_groupby in self.coord_dims(coord_), + self.dim_coords + self.aux_coords, ) ) @@ -4109,6 +4110,11 @@ def aggregated_by( for coord in groupby_coords + shared_coords: aggregateby_cube.remove_coord(coord) + coord_mapping = {} + for coord in aggregateby_cube.coords(): + orig_id = id(self.coord(coord)) + coord_mapping[orig_id] = coord + # Determine the group-by cube data shape. data_shape = list(self.shape + aggregator.aggregate_shape(**kwargs)) data_shape[dimension_to_groupby] = len(groupby) @@ -4237,6 +4243,11 @@ def aggregated_by( aggregateby_cube.add_aux_coord( new_coord, self.coord_dims(lookup_coord) ) + coord_mapping[id(self.coord(lookup_coord))] = new_coord + + aggregateby_cube._aux_factories = [] + for factory in self.aux_factories: + aggregateby_cube.add_aux_factory(factory.updated(coord_mapping)) # Attach the aggregate-by data into the aggregate-by cube. if aggregateby_weights is None: diff --git a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py index 3230e3de00..9e60631c33 100644 --- a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py +++ b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py @@ -22,6 +22,7 @@ from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord from iris.cube import Cube import iris.exceptions +from iris.tests.stock import realistic_4d class Test_aggregated_by(tests.IrisTest): @@ -841,5 +842,52 @@ def test_clim_in_no_clim_op(self): self.assertFalse(categorised_coord.climatological) +class Test_aggregated_by__derived(tests.IrisTest): + def setUp(self): + self.cube = realistic_4d()[:, :10, :6, :8] + self.time_cat_coord = AuxCoord( + [0, 0, 1, 1, 2, 2], long_name="time_cat" + ) + self.cube.add_aux_coord(self.time_cat_coord, 0) + height_data = np.zeros(self.cube.shape[1]) + height_data[5:] = 1 + self.height_cat_coord = AuxCoord(height_data, long_name="height_cat") + self.cube.add_aux_coord(self.height_cat_coord, 1) + self.aggregator = iris.analysis.MEAN + + def test_grouped_dim(self): + """ + Check that derived coordinates are maintained when the coordinates they + derive from are aggregated. + """ + result = self.cube.aggregated_by( + self.height_cat_coord, + self.aggregator, + ) + assert len(result.aux_factories) == 1 + altitude = result.coord("altitude") + assert altitude.shape == (2, 6, 8) + + # Check the bounds are derived as expected. + orig_alt_bounds = self.cube.coord("altitude").bounds + bounds_0 = orig_alt_bounds[0::5, :, :, 0] + bounds_1 = orig_alt_bounds[4::5, :, :, 1] + expected_bounds = np.stack([bounds_0, bounds_1], axis=-1) + assert np.array_equal(expected_bounds, result.coord("altitude").bounds) + + def test_ungrouped_dim(self): + """ + Check that derived coordinates are preserved when aggregating along a + different axis. + """ + result = self.cube.aggregated_by( + self.time_cat_coord, + self.aggregator, + ) + assert len(result.aux_factories) == 1 + altitude = result.coord("altitude") + assert altitude == self.cube.coord("altitude") + + if __name__ == "__main__": tests.main() From 8f5c69ee6977352021382fd259c72085e0784553 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 10 Nov 2022 10:21:02 +0000 Subject: [PATCH 265/319] Reference the RTD version switcher in the docs latest warning. (#5055) --- docs/src/_templates/layout.html | 5 ++--- docs/src/whatsnew/latest.rst | 6 ++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/src/_templates/layout.html b/docs/src/_templates/layout.html index 7377e866b7..9a6fe46d01 100644 --- a/docs/src/_templates/layout.html +++ b/docs/src/_templates/layout.html @@ -10,9 +10,8 @@ {% if on_rtd and rtd_version == 'latest' %}

    You are viewing the latest unreleased documentation - v{{ version }}. You may prefer a - stable - version. + v{{ version }}. You can switch to a stable version + via the flyout menu in the bottom-right of the screen.

    {%- endif %} diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 4a602f60dc..1d9e52dce7 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -152,10 +152,16 @@ This document explains the changes made to Iris for this release #. `@ESadek-MO`_, `@TTV-Intrepid`_ and `@trexfeathers`_ added a gallery example for zonal means plotted parallel to a cartographic plot. (:pull:`4871`) + #. `@Esadek-MO`_ added a key-terms :ref:`glossary` page into the user guide. (:pull:`4902`) + #. `@pp-mo`_ added a :ref:`code example ` for converting ORCA-gridded data to an unstructured cube. (:pull:`5013`) +#. `@trexfeathers`_ changed the warning header for the **latest** documentation + to reference Read the Docs' built-in version switcher, instead of generating + its own independent links. (:pull:`5055`) + 💼 Internal =========== From 10f517b93a25aed76a1afb34eb2f4703de3e7a4a Mon Sep 17 00:00:00 2001 From: Elias <110238618+ESadek-MO@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:15:54 +0000 Subject: [PATCH 266/319] Links to Gallery in docs (#5009) * Added zonal mean gallery example, continued from @TTV-Intrepid * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added TTVIntrepid to common links for What's New functionality * Fixed zonal mean plotting, add axh * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Minor visual changes, ax3 plotting fix, removed warning suppression. * updated imagerepo for zonal means * updated ci-tests yml to latest iris test data version * updated what's new * updated announcements to one line * added links to gallery in userguide where relevant * Added seealso tags for more visability, fixed typos * Added what's new entry * Added bullets to subsetting cube for readability Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/userguide/cube_maths.rst | 6 ++++++ docs/src/userguide/cube_statistics.rst | 5 +++++ docs/src/userguide/merge_and_concat.rst | 5 +++++ docs/src/userguide/navigating_a_cube.rst | 8 ++++++++ docs/src/userguide/plotting_a_cube.rst | 10 +++++++--- docs/src/userguide/subsetting_a_cube.rst | 7 +++++++ docs/src/whatsnew/latest.rst | 2 ++ 7 files changed, 40 insertions(+), 3 deletions(-) diff --git a/docs/src/userguide/cube_maths.rst b/docs/src/userguide/cube_maths.rst index fe9a5d63d2..9c0898b62c 100644 --- a/docs/src/userguide/cube_maths.rst +++ b/docs/src/userguide/cube_maths.rst @@ -165,6 +165,12 @@ broadcasting behaviour:: >>> print(result.summary(True)) unknown / (K) (time: 240; latitude: 37; longitude: 49) + +.. seealso:: + + Relevant gallery example: + :ref:`sphx_glr_generated_gallery_general_plot_anomaly_log_colouring.py` (Anomaly) + Combining Multiple Phenomena to Form a New One ---------------------------------------------- diff --git a/docs/src/userguide/cube_statistics.rst b/docs/src/userguide/cube_statistics.rst index 980f1e132f..08297c2a51 100644 --- a/docs/src/userguide/cube_statistics.rst +++ b/docs/src/userguide/cube_statistics.rst @@ -4,6 +4,11 @@ Cube Statistics =============== +.. seealso:: + + Relevant gallery example: + :ref:`sphx_glr_generated_gallery_general_plot_zonal_means.py` (Collapsing) + .. _cube-statistics-collapsing: Collapsing Entire Data Dimensions diff --git a/docs/src/userguide/merge_and_concat.rst b/docs/src/userguide/merge_and_concat.rst index 08c3ce9711..b521d49a59 100644 --- a/docs/src/userguide/merge_and_concat.rst +++ b/docs/src/userguide/merge_and_concat.rst @@ -253,6 +253,11 @@ which are described below. Using CubeList.concatenate ========================== +.. seealso:: + + Relevant gallery example: + :ref:`sphx_glr_generated_gallery_general_plot_projections_and_annotations.py` (Brief concatenating examples) + The :meth:`CubeList.concatenate ` method operates on a list of cubes and returns a new :class:`~iris.cube.CubeList` containing the cubes that have been concatenated. diff --git a/docs/src/userguide/navigating_a_cube.rst b/docs/src/userguide/navigating_a_cube.rst index c5924a61c6..b4c16b094b 100644 --- a/docs/src/userguide/navigating_a_cube.rst +++ b/docs/src/userguide/navigating_a_cube.rst @@ -110,6 +110,10 @@ cube with the :attr:`Cube.cell_methods ` attribute: print(cube.cell_methods) +.. seealso:: + + Relevant gallery example: + :ref:`sphx_glr_generated_gallery_meteorology_plot_wind_barbs.py` Accessing Coordinates on the Cube --------------------------------- @@ -176,6 +180,10 @@ We can add and remove coordinates via :func:`Cube.add_dim_coord` for converting ORCA-gridded data to an unstructured cube. (:pull:`5013`) +#. `@Esadek-MO`_ added links to relevant Gallery examples within the User Guide + to improve understanding. (:pull:`5009`) #. `@trexfeathers`_ changed the warning header for the **latest** documentation to reference Read the Docs' built-in version switcher, instead of generating From c1cb14cb4186b5ca335fda92276703bf89e85004 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 10 Nov 2022 11:41:48 +0000 Subject: [PATCH 267/319] `iris.pandas.as_data_frame()` doctests full width (#5057) * iris.pandas.as_data_frame doctests full width. * NetCDF4 pin to enable RTD build. * Revert "NetCDF4 pin to enable RTD build." This reverts commit 31f5ea6838717f7a1bd4557cadad65ba563346fe. --- lib/iris/pandas.py | 79 ++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index 1b4e444efc..1bf9509d1b 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -678,6 +678,9 @@ def as_data_frame( -------- >>> import iris >>> from iris.pandas import as_data_frame + >>> import pandas as pd + >>> pd.set_option('display.width', 1000) + >>> pd.set_option('display.max_columns', 1000) Convert a simple :class:`~iris.cube.Cube`: @@ -708,19 +711,19 @@ def as_data_frame( >>> df = as_data_frame(cube, add_aux_coords=True) >>> print(df) ... # doctest: +NORMALIZE_WHITESPACE - surface_temperature ... forecast_reference_time - time latitude longitude ... - 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 ... 2006-04-16 12:00:00 - 0.833333 301.785004 ... 2006-04-16 12:00:00 - 1.666667 301.820984 ... 2006-04-16 12:00:00 - 2.500000 301.865234 ... 2006-04-16 12:00:00 - 3.333333 301.926819 ... 2006-04-16 12:00:00 - ... ... ... ... - 2010-09-16 00:00:00 4.444450 355.833313 298.779938 ... 2010-09-16 12:00:00 - 356.666656 298.913147 ... 2010-09-16 12:00:00 - 357.500000 NaN ... 2010-09-16 12:00:00 - 358.333313 NaN ... 2010-09-16 12:00:00 - 359.166656 298.995148 ... 2010-09-16 12:00:00 + surface_temperature forecast_period forecast_reference_time + time latitude longitude + 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 0 2006-04-16 12:00:00 + 0.833333 301.785004 0 2006-04-16 12:00:00 + 1.666667 301.820984 0 2006-04-16 12:00:00 + 2.500000 301.865234 0 2006-04-16 12:00:00 + 3.333333 301.926819 0 2006-04-16 12:00:00 + ... ... ... ... + 2010-09-16 00:00:00 4.444450 355.833313 298.779938 0 2010-09-16 12:00:00 + 356.666656 298.913147 0 2010-09-16 12:00:00 + 357.500000 NaN 0 2010-09-16 12:00:00 + 358.333313 NaN 0 2010-09-16 12:00:00 + 359.166656 298.995148 0 2010-09-16 12:00:00 [419904 rows x 3 columns] @@ -730,19 +733,19 @@ def as_data_frame( >>> df['STASH'] = str(cube.attributes['STASH']) >>> print(df) ... # doctest: +NORMALIZE_WHITESPACE - surface_temperature ... STASH - time latitude longitude ... - 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 ... m01s00i024 - 0.833333 301.785004 ... m01s00i024 - 1.666667 301.820984 ... m01s00i024 - 2.500000 301.865234 ... m01s00i024 - 3.333333 301.926819 ... m01s00i024 - ... ... ... ... - 2010-09-16 00:00:00 4.444450 355.833313 298.779938 ... m01s00i024 - 356.666656 298.913147 ... m01s00i024 - 357.500000 NaN ... m01s00i024 - 358.333313 NaN ... m01s00i024 - 359.166656 298.995148 ... m01s00i024 + surface_temperature forecast_period forecast_reference_time STASH + time latitude longitude + 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 0 2006-04-16 12:00:00 m01s00i024 + 0.833333 301.785004 0 2006-04-16 12:00:00 m01s00i024 + 1.666667 301.820984 0 2006-04-16 12:00:00 m01s00i024 + 2.500000 301.865234 0 2006-04-16 12:00:00 m01s00i024 + 3.333333 301.926819 0 2006-04-16 12:00:00 m01s00i024 + ... ... ... ... ... + 2010-09-16 00:00:00 4.444450 355.833313 298.779938 0 2010-09-16 12:00:00 m01s00i024 + 356.666656 298.913147 0 2010-09-16 12:00:00 m01s00i024 + 357.500000 NaN 0 2010-09-16 12:00:00 m01s00i024 + 358.333313 NaN 0 2010-09-16 12:00:00 m01s00i024 + 359.166656 298.995148 0 2010-09-16 12:00:00 m01s00i024 [419904 rows x 4 columns] @@ -753,18 +756,18 @@ def as_data_frame( >>> df.reset_index(inplace=True) >>> print(df) ... # doctest: +NORMALIZE_WHITESPACE - time latitude ... forecast_reference_time STASH - 0 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 - 1 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 - 2 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 - 3 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 - 4 2006-04-16 00:00:00 -4.999992 ... 2006-04-16 12:00:00 m01s00i024 - ... ... ... ... ... ... - 419899 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 - 419900 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 - 419901 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 - 419902 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 - 419903 2010-09-16 00:00:00 4.444450 ... 2010-09-16 12:00:00 m01s00i024 + time latitude longitude surface_temperature forecast_period forecast_reference_time STASH + 0 2006-04-16 00:00:00 -4.999992 0.000000 301.659271 0 2006-04-16 12:00:00 m01s00i024 + 1 2006-04-16 00:00:00 -4.999992 0.833333 301.785004 0 2006-04-16 12:00:00 m01s00i024 + 2 2006-04-16 00:00:00 -4.999992 1.666667 301.820984 0 2006-04-16 12:00:00 m01s00i024 + 3 2006-04-16 00:00:00 -4.999992 2.500000 301.865234 0 2006-04-16 12:00:00 m01s00i024 + 4 2006-04-16 00:00:00 -4.999992 3.333333 301.926819 0 2006-04-16 12:00:00 m01s00i024 + ... ... ... ... ... ... ... + 419899 2010-09-16 00:00:00 4.444450 355.833313 298.779938 0 2010-09-16 12:00:00 m01s00i024 + 419900 2010-09-16 00:00:00 4.444450 356.666656 298.913147 0 2010-09-16 12:00:00 m01s00i024 + 419901 2010-09-16 00:00:00 4.444450 357.500000 NaN 0 2010-09-16 12:00:00 m01s00i024 + 419902 2010-09-16 00:00:00 4.444450 358.333313 NaN 0 2010-09-16 12:00:00 m01s00i024 + 419903 2010-09-16 00:00:00 4.444450 359.166656 298.995148 0 2010-09-16 12:00:00 m01s00i024 [419904 rows x 7 columns] From 5c9d9b30015003dc76f65c90db8e780c80a33062 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 11 Nov 2022 12:49:26 +0000 Subject: [PATCH 268/319] Update the `iris.experimental` module (#5056) * Migrate iris.experimental.animate to iris.plot. * Advertise iris.experimental.stratify. * IRIS_TEST_DATA_VERSION 2.18. * More IRIS_TEST_DATA_VERSION 2.18. --- .github/workflows/benchmark.yml | 2 +- .github/workflows/ci-tests.yml | 2 +- docs/src/common_links.inc | 1 + docs/src/whatsnew/latest.rst | 12 ++ lib/iris/experimental/animate.py | 120 +++--------------- lib/iris/plot.py | 113 +++++++++++++++++ .../plot}/test_animate.py | 5 +- lib/iris/tests/results/imagerepo.json | 6 +- 8 files changed, 150 insertions(+), 111 deletions(-) rename lib/iris/tests/{experimental => integration/plot}/test_animate.py (94%) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index c0baabe572..9ae3534c76 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -21,7 +21,7 @@ jobs: env: IRIS_TEST_DATA_LOC_PATH: benchmarks IRIS_TEST_DATA_PATH: benchmarks/iris-test-data - IRIS_TEST_DATA_VERSION: "2.15" + IRIS_TEST_DATA_VERSION: "2.18" # Lets us manually bump the cache to rebuild ENV_CACHE_BUILD: "0" TEST_DATA_CACHE_BUILD: "2" diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index 270046164e..cee98dc33d 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -46,7 +46,7 @@ jobs: session: "tests" env: - IRIS_TEST_DATA_VERSION: "2.17" + IRIS_TEST_DATA_VERSION: "2.18" ENV_NAME: "ci-tests" steps: diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index ec7e1efd6d..182c8ad59f 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -39,6 +39,7 @@ .. _requirements/ci/: https://github.com/SciTools/iris/tree/main/requirements/ci .. _CF-UGRID: https://ugrid-conventions.github.io/ugrid-conventions/ .. _issues on GitHub: https://github.com/SciTools/iris/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc +.. _python-stratify: https://github.com/SciTools/python-stratify .. comment diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f0245f5355..b198ce1b2f 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -27,6 +27,14 @@ This document explains the changes made to Iris for this release #. Welcome to `@ESadek-MO`_ and `@TTV-Intrepid`_ who made their first contributions to Iris 🎉 + .. _try_experimental_stratify: + +#. Do you enjoy `python-stratify`_? Did you know that Iris includes a + convenience for using `python-stratify`_ with :class:`~iris.cube.Cube`\s? + It has been 'experimental' for several years now, without receiving much + feedback, so it's **use it or lose it** time: please try out + :mod:`iris.experimental.stratify` and let us know what you think! + ✨ Features =========== @@ -102,6 +110,10 @@ This document explains the changes made to Iris for this release We do not expect this to affect typical user workflows but please get in touch if you need help. (:pull:`5041`) +#. `@trexfeathers`_ moved ``iris.experimental.animate.animate()`` to + :func:`iris.plot.animate`, in recognition of its successful use over several + years since introduction. (:pull:`5056`) + 🚀 Performance Enhancements =========================== diff --git a/lib/iris/experimental/animate.py b/lib/iris/experimental/animate.py index fb2e2af590..1b6c2d46be 100644 --- a/lib/iris/experimental/animate.py +++ b/lib/iris/experimental/animate.py @@ -6,118 +6,32 @@ """ Wrapper for animating iris cubes using iris or matplotlib plotting functions -""" - -import warnings +Notes +----- +.. deprecated:: 3.4.0 -import matplotlib.animation as animation -import matplotlib.pyplot as plt +``iris.experimental.animate.animate()`` has been moved to +:func:`iris.plot.animate`. This module will therefore be removed in a future +release. -import iris +""" def animate(cube_iterator, plot_func, fig=None, **kwargs): """ Animates the given cube iterator. - Args: - - * cube_iterator (iterable of :class:`iris.cube.Cube` objects): - Each animation frame corresponds to each :class:`iris.cube.Cube` - object. See :meth:`iris.cube.Cube.slices`. - - * plot_func (:mod:`iris.plot` or :mod:`iris.quickplot` plotting function): - Plotting function used to animate. Must accept the signature - ``plot_func(cube, vmin=vmin, vmax=vmax, coords=coords)``. - :func:`~iris.plot.contourf`, :func:`~iris.plot.contour`, - :func:`~iris.plot.pcolor` and :func:`~iris.plot.pcolormesh` - all conform to this signature. - - Kwargs: - - * fig (:class:`matplotlib.figure.Figure` instance): - By default, the current figure will be used or a new figure instance - created if no figure is available. See :func:`matplotlib.pyplot.gcf`. - - * coords (list of :class:`~iris.coords.Coord` objects or coordinate names): - Use the given coordinates as the axes for the plot. The order of the - given coordinates indicates which axis to use for each, where the first - element is the horizontal axis of the plot and the second element is - the vertical axis of the plot. - - * interval (int, float or long): - Defines the time interval in milliseconds between successive frames. - A default interval of 100ms is set. - - * vmin, vmax (int, float or long): - Color scaling values, see :class:`matplotlib.colors.Normalize` for - further details. Default values are determined by the min-max across - the data set over the entire sequence. - - See :class:`matplotlib.animation.FuncAnimation` for details of other valid - keyword arguments. + Warnings + -------- + This function is now **disabled**. - Returns: - :class:`~matplotlib.animation.FuncAnimation` object suitable for - saving and or plotting. - - For example, to animate along a set of cube slices:: - - cube_iter = cubes.slices(('grid_longitude', 'grid_latitude')) - ani = animate(cube_iter, qplt.contourf) - plt.show() + The functionality has been moved to :func:`iris.plot.animate`. """ - kwargs.setdefault("interval", 100) - coords = kwargs.pop("coords", None) - - if fig is None: - fig = plt.gcf() - - def update_animation_iris(i, cubes, vmin, vmax, coords): - # Clearing the figure is currently necessary for compatibility with - # the iris quickploting module - due to the colorbar. - plt.gcf().clf() - plot_func(cubes[i], vmin=vmin, vmax=vmax, coords=coords) - - # Turn cube iterator into a list to determine plot ranges. - # NOTE: we check that we are not providing a cube as this has a deprecated - # iter special method. - if hasattr(cube_iterator, "__iter__") and not isinstance( - cube_iterator, iris.cube.Cube - ): - cubes = iris.cube.CubeList(cube_iterator) - else: - msg = "iterable type object required for animation, {} given".format( - type(cube_iterator) - ) - raise TypeError(msg) - - supported = ["iris.plot", "iris.quickplot"] - if plot_func.__module__ not in supported: - msg = ( - 'Given plotting module "{}" may not be supported, intended ' - "use: {}." - ) - msg = msg.format(plot_func.__module__, supported) - warnings.warn(msg, UserWarning) - - supported = ["contour", "contourf", "pcolor", "pcolormesh"] - if plot_func.__name__ not in supported: - msg = ( - 'Given plotting function "{}" may not be supported, intended ' - "use: {}." - ) - msg = msg.format(plot_func.__name__, supported) - warnings.warn(msg, UserWarning) - - # Determine plot range. - vmin = kwargs.pop("vmin", min([cc.data.min() for cc in cubes])) - vmax = kwargs.pop("vmax", max([cc.data.max() for cc in cubes])) - - update = update_animation_iris - frames = range(len(cubes)) - - return animation.FuncAnimation( - fig, update, frames=frames, fargs=(cubes, vmin, vmax, coords), **kwargs + msg = ( + "The function 'iris.experimental.animate.animate()' has been moved, " + "and is now at 'iris.plot.animate()'.\n" + "Please replace 'iris.experimental.animate.animate' with " + "'iris.plot.animate'." ) + raise Exception(msg) diff --git a/lib/iris/plot.py b/lib/iris/plot.py index 4acb38b859..8cd849b716 100644 --- a/lib/iris/plot.py +++ b/lib/iris/plot.py @@ -13,11 +13,13 @@ import collections import datetime +import warnings import cartopy.crs as ccrs from cartopy.geodesic import Geodesic import cartopy.mpl.geoaxes import cftime +import matplotlib.animation as animation import matplotlib.axes import matplotlib.collections as mpl_collections import matplotlib.dates as mpl_dates @@ -1803,3 +1805,114 @@ def citation(text, figure=None, axes=None): anchor.patch.set_boxstyle("round, pad=0, rounding_size=0.2") axes = axes if axes else figure.gca() axes.add_artist(anchor) + + +def animate(cube_iterator, plot_func, fig=None, **kwargs): + """ + Animates the given cube iterator. + + Parameters + ---------- + cube_iterator : iterable of :class:`iris.cube.Cube` objects + Each animation frame corresponds to each :class:`iris.cube.Cube` + object. See :meth:`iris.cube.Cube.slices`. + plot_func : :mod:`iris.plot` or :mod:`iris.quickplot` plotting function + Plotting function used to animate. Must accept the signature + ``plot_func(cube, vmin=vmin, vmax=vmax, coords=coords)``. + :func:`~iris.plot.contourf`, :func:`~iris.plot.contour`, + :func:`~iris.plot.pcolor` and :func:`~iris.plot.pcolormesh` + all conform to this signature. + fig : :class:`matplotlib.figure.Figure` instance, optional + By default, the current figure will be used or a new figure instance + created if no figure is available. See :func:`matplotlib.pyplot.gcf`. + **kwargs : dict, optional + Valid keyword arguments: + + coords: list of :class:`~iris.coords.Coord` objects or coordinate names + Use the given coordinates as the axes for the plot. The order of the + given coordinates indicates which axis to use for each, where the first + element is the horizontal axis of the plot and the second element is + the vertical axis of the plot. + interval: int, float or long + Defines the time interval in milliseconds between successive frames. + A default interval of 100ms is set. + vmin, vmax: int, float or long + Color scaling values, see :class:`matplotlib.colors.Normalize` for + further details. Default values are determined by the min-max across + the data set over the entire sequence. + + See :class:`matplotlib.animation.FuncAnimation` for details of other + valid keyword arguments. + + Returns + ------- + :class:`~matplotlib.animation.FuncAnimation` object suitable for + saving and or plotting. + + Examples + -------- + >>> import iris + >>> from iris import plot as iplt + >>> from iris import quickplot as qplt + >>> my_cube = iris.load_cube(iris.sample_data_path("A1B_north_america.nc")) + + To animate along a set of :class:`~iris.cube.Cube` slices : + + >>> cube_iter = my_cube.slices(("longitude", "latitude")) + >>> ani = iplt.animate(cube_iter, qplt.contourf) + >>> iplt.show() + + """ + kwargs.setdefault("interval", 100) + coords = kwargs.pop("coords", None) + + if fig is None: + fig = plt.gcf() + + def update_animation_iris(i, cubes, vmin, vmax, coords): + # Clearing the figure is currently necessary for compatibility with + # the iris quickploting module - due to the colorbar. + plt.gcf().clf() + plot_func(cubes[i], vmin=vmin, vmax=vmax, coords=coords) + + # Turn cube iterator into a list to determine plot ranges. + # NOTE: we check that we are not providing a cube as this has a deprecated + # iter special method. + if hasattr(cube_iterator, "__iter__") and not isinstance( + cube_iterator, iris.cube.Cube + ): + cubes = iris.cube.CubeList(cube_iterator) + else: + msg = "iterable type object required for animation, {} given".format( + type(cube_iterator) + ) + raise TypeError(msg) + + supported = ["iris.plot", "iris.quickplot"] + if plot_func.__module__ not in supported: + msg = ( + 'Given plotting module "{}" may not be supported, intended ' + "use: {}." + ) + msg = msg.format(plot_func.__module__, supported) + warnings.warn(msg, UserWarning) + + supported = ["contour", "contourf", "pcolor", "pcolormesh"] + if plot_func.__name__ not in supported: + msg = ( + 'Given plotting function "{}" may not be supported, intended ' + "use: {}." + ) + msg = msg.format(plot_func.__name__, supported) + warnings.warn(msg, UserWarning) + + # Determine plot range. + vmin = kwargs.pop("vmin", min([cc.data.min() for cc in cubes])) + vmax = kwargs.pop("vmax", max([cc.data.max() for cc in cubes])) + + update = update_animation_iris + frames = range(len(cubes)) + + return animation.FuncAnimation( + fig, update, frames=frames, fargs=(cubes, vmin, vmax, coords), **kwargs + ) diff --git a/lib/iris/tests/experimental/test_animate.py b/lib/iris/tests/integration/plot/test_animate.py similarity index 94% rename from lib/iris/tests/experimental/test_animate.py rename to lib/iris/tests/integration/plot/test_animate.py index d8010767b8..ef19dbb108 100644 --- a/lib/iris/tests/experimental/test_animate.py +++ b/lib/iris/tests/integration/plot/test_animate.py @@ -4,7 +4,7 @@ # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. """ -Test the animation of cubes within iris. +Integration tests for :func:`iris.plot.animate`. """ @@ -19,7 +19,6 @@ # Run tests in no graphics mode if matplotlib is not available. if tests.MPL_AVAILABLE: - import iris.experimental.animate as animate import iris.plot as iplt @@ -57,7 +56,7 @@ def test_cube_animation(self): # the animation. cube_iter = self.cube.slices(("latitude", "longitude")) - ani = animate.animate(cube_iter, iplt.contourf) + ani = iplt.animate(cube_iter, iplt.contourf) # Disconnect the first draw callback to stop the animation. ani._fig.canvas.mpl_disconnect(ani._first_draw_id) diff --git a/lib/iris/tests/results/imagerepo.json b/lib/iris/tests/results/imagerepo.json index e5c2ad863a..92f0d8fc20 100644 --- a/lib/iris/tests/results/imagerepo.json +++ b/lib/iris/tests/results/imagerepo.json @@ -35,9 +35,9 @@ "gallery_tests.test_plot_wind_speed.0": "e9e960e996169306c1fe9e96c29e36739e03c06c3d61c07f3da139e1c07f3f01", "gallery_tests.test_plot_wind_speed.1": "e9e960e996169306c1ee9f96c29e36739653c06c3d61c07f39a139e1c07f3f01", "gallery_tests.test_plot_zonal_means.0": "b45b3071c9a4c9a6c69c363cc327cbb3cb9634d8c9e63cf336738c6634d8c384", - "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e", - "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e", - "iris.tests.experimental.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f", + "iris.tests.integration.plot.test_animate.IntegrationTest.test_cube_animation.0": "fe81c17e817e3e81817e3e81857e7a817e81c17e7e81c17e7a81817e817e8c2e", + "iris.tests.integration.plot.test_animate.IntegrationTest.test_cube_animation.1": "fe81857e817e7a85817e7a81857e7e817e81917a7e81817e7a81817e817e843e", + "iris.tests.integration.plot.test_animate.IntegrationTest.test_cube_animation.2": "be81817ec17e7a81c17e7e81857e3e803e81817a3e81c17e7a81c17ec97e2c2f", "iris.tests.integration.plot.test_plot_2d_coords.Test.test_2d_coord_bounds_northpolarstereo.0": "e59661969e699659c0f719a6c967339a1992c07f3649c09c3f612669c07b3f66", "iris.tests.integration.plot.test_plot_2d_coords.Test.test_2d_coord_bounds_platecarree.0": "ee856299954a1da699b6915ec25b6e419729c42c3f84bd9fa6d262d1d1dac076", "iris.tests.integration.plot.test_plot_2d_coords.Test2dContour.test_2d_coords_contour.0": "b4b2643ecb05cb43b0f23d80c53c4e1d3e5990eb1f81c19f2f983cb1c4ff3e42", From 3da5341429586568c5c3e5a7dfd3fe7795ce5754 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 11 Nov 2022 15:12:42 +0000 Subject: [PATCH 269/319] WSTARBAR is upward velocity (#5060) * WSTARBAR is upward flux * whatsnew --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/fileformats/um_cf_map.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index b198ce1b2f..d6ff5d1418 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -95,6 +95,10 @@ This document explains the changes made to Iris for this release #. `@stephenworsley`_ fixed a bug which caused derived coordinates to be realised after calling :meth:`iris.cube.Cube.aggregated_by`. (:issue:`3637`, :pull:`4947`) + +#. `@rcomer`_ corrected the ``standard_name`` mapping from UM stash code ``m01s30i311`` + to indicate that this is the upward, rather than northward part of the flow. + (:pull:`5060`) 💣 Incompatible Changes diff --git a/lib/iris/fileformats/um_cf_map.py b/lib/iris/fileformats/um_cf_map.py index 9091507668..01539960a5 100644 --- a/lib/iris/fileformats/um_cf_map.py +++ b/lib/iris/fileformats/um_cf_map.py @@ -906,7 +906,7 @@ 'm01s30i301': CFName(None, 'Heavyside function on pressure levels', '1'), 'm01s30i302': CFName('virtual_temperature', None, 'K'), 'm01s30i310': CFName('northward_transformed_eulerian_mean_air_velocity', None, 'm s-1'), - 'm01s30i311': CFName('northward_transformed_eulerian_mean_air_velocity', None, 'm s-1'), + 'm01s30i311': CFName('upward_transformed_eulerian_mean_air_velocity', None, 'm s-1'), 'm01s30i312': CFName('northward_eliassen_palm_flux_in_air', None, 'kg s-2'), 'm01s30i313': CFName('upward_eliassen_palm_flux_in_air', None, 'kg s-2'), 'm01s30i314': CFName('tendency_of_eastward_wind_due_to_eliassen_palm_flux_divergence', None, 'm s-2'), From f815437500e0aa7726fc08fc89cccdc3ad716e16 Mon Sep 17 00:00:00 2001 From: stephenworsley <49274989+stephenworsley@users.noreply.github.com> Date: Fri, 11 Nov 2022 15:56:56 +0000 Subject: [PATCH 270/319] (Regridder unification) improve curvilinear regridding, generalise _create_cube (#4807) * make _create_cube more generic * reinstate original _create_cube * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix tests * fix tests * fix curvilinear regridding * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix curvilinear regridding * fix curvilinear regridding * fix 1D regridding * improve support for scalar lat-lon * fix regridding for circular case * fix scalar handling * fix mask handling * fix handling of higher dimensional cases * include new derived coord/scalar coord support in tests * remove old _create_cube method * update docstrings * fix regrid_conservative * make _create_cube more robust for extra dims * fix and test for derived coords * add benchmark * address review comments * address review comments * add whatsnew Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: lbdreyer --- benchmarks/benchmarks/regridding.py | 50 +- docs/src/whatsnew/latest.rst | 7 + lib/iris/analysis/_area_weighted.py | 36 +- lib/iris/analysis/_regrid.py | 529 ++++++++++-------- lib/iris/experimental/regrid_conservative.py | 27 +- .../const_lat_cross_section.cml | 60 ++ .../const_lon_cross_section.cml | 64 +++ .../regrid/test__CurvilinearRegridder.py | 87 ++- 8 files changed, 589 insertions(+), 271 deletions(-) diff --git a/benchmarks/benchmarks/regridding.py b/benchmarks/benchmarks/regridding.py index c315119c11..44bd1b6c95 100644 --- a/benchmarks/benchmarks/regridding.py +++ b/benchmarks/benchmarks/regridding.py @@ -12,8 +12,11 @@ # importing anything else from iris import tests # isort:skip +import numpy as np + import iris -from iris.analysis import AreaWeighted +from iris.analysis import AreaWeighted, PointInCell +from iris.coords import AuxCoord class HorizontalChunkedRegridding: @@ -53,3 +56,48 @@ def time_regrid_area_w_new_grid(self) -> None: out = self.chunked_cube.regrid(self.template_cube, self.scheme_area_w) # Realise data out.data + + +class CurvilinearRegridding: + def setup(self) -> None: + # Prepare a cube and a template + + cube_file_path = tests.get_data_path( + ["NetCDF", "regrid", "regrid_xyt.nc"] + ) + self.cube = iris.load_cube(cube_file_path) + + # Make the source cube curvilinear + x_coord = self.cube.coord("longitude") + y_coord = self.cube.coord("latitude") + xx, yy = np.meshgrid(x_coord.points, y_coord.points) + self.cube.remove_coord(x_coord) + self.cube.remove_coord(y_coord) + x_coord_2d = AuxCoord( + xx, + standard_name=x_coord.standard_name, + units=x_coord.units, + coord_system=x_coord.coord_system, + ) + y_coord_2d = AuxCoord( + yy, + standard_name=y_coord.standard_name, + units=y_coord.units, + coord_system=y_coord.coord_system, + ) + self.cube.add_aux_coord(x_coord_2d, (1, 2)) + self.cube.add_aux_coord(y_coord_2d, (1, 2)) + + template_file_path = tests.get_data_path( + ["NetCDF", "regrid", "regrid_template_global_latlon.nc"] + ) + self.template_cube = iris.load_cube(template_file_path) + + # Prepare a regridding scheme + self.scheme_pic = PointInCell() + + def time_regrid_pic(self) -> None: + # Regrid the cube onto the template. + out = self.cube.regrid(self.template_cube, self.scheme_pic) + # Realise the data + out.data diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d6ff5d1418..9ee6826f67 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -62,6 +62,9 @@ This document explains the changes made to Iris for this release both dim and aux coords of the same type e.g. ``longitude`` and ``grid_longitude``. (:issue:`3916`, :pull:`5029`). +#. `@stephenworsley`_ added the ability to regrid derived coordinates with the + :obj:`~iris.analysis.PointInCell` regridding scheme. (:pull:`4807`) + 🐛 Bugs Fixed ============= @@ -135,6 +138,10 @@ This document explains the changes made to Iris for this release :meth:`iris.coords.Coord.intersect`. (:pull:`4955`) +#. `@stephenworsley`_ improved the speed of the :obj:`~iris.analysis.PointInCell` + regridding scheme. (:pull:`4807`) + + 🔥 Deprecations =============== diff --git a/lib/iris/analysis/_area_weighted.py b/lib/iris/analysis/_area_weighted.py index 8381185e58..edbfd41ef9 100644 --- a/lib/iris/analysis/_area_weighted.py +++ b/lib/iris/analysis/_area_weighted.py @@ -11,7 +11,7 @@ from iris._lazy_data import map_complete_blocks from iris.analysis._interpolation import get_xy_dim_coords, snapshot_grid -from iris.analysis._regrid import RectilinearRegridder +from iris.analysis._regrid import RectilinearRegridder, _create_cube import iris.analysis.cartography import iris.coord_systems from iris.util import _meshgrid @@ -1111,18 +1111,32 @@ def _regrid_area_weighted_rectilinear_src_and_grid__perform( ) # Wrap up the data as a Cube. - regrid_callback = RectilinearRegridder._regrid - new_cube = RectilinearRegridder._create_cube( + + _regrid_callback = functools.partial( + RectilinearRegridder._regrid, + src_x_coord=src_x, + src_y_coord=src_y, + sample_grid_x=meshgrid_x, + sample_grid_y=meshgrid_y, + ) + # TODO: investigate if an area weighted callback would be more appropriate. + # _regrid_callback = functools.partial( + # _regrid_area_weighted_array, + # weights_info=weights_info, + # index_info=index_info, + # mdtol=mdtol, + # ) + + def regrid_callback(*args, **kwargs): + _data, dims = args + return _regrid_callback(_data, *dims, **kwargs) + + new_cube = _create_cube( new_data, src_cube, - src_x_dim, - src_y_dim, - src_x, - src_y, - grid_x, - grid_y, - meshgrid_x, - meshgrid_y, + [src_x_dim, src_y_dim], + [grid_x, grid_y], + 2, regrid_callback, ) diff --git a/lib/iris/analysis/_regrid.py b/lib/iris/analysis/_regrid.py index 5c7439b0ce..f1891a48e4 100644 --- a/lib/iris/analysis/_regrid.py +++ b/lib/iris/analysis/_regrid.py @@ -11,7 +11,6 @@ import numpy as np import numpy.ma as ma from scipy.sparse import csc_matrix -from scipy.sparse import diags as sparse_diags from iris._lazy_data import map_complete_blocks from iris.analysis._interpolation import ( @@ -21,7 +20,7 @@ snapshot_grid, ) from iris.analysis._scipy_interpolate import _RegularGridInterpolator -from iris.util import _meshgrid +from iris.util import _meshgrid, guess_coord_axis def _transform_xy_arrays(crs_from, x, y, crs_to): @@ -52,18 +51,20 @@ def _regrid_weighted_curvilinear_to_rectilinear__prepare( First (setup) part of 'regrid_weighted_curvilinear_to_rectilinear'. Check inputs and calculate the sparse regrid matrix and related info. - The 'regrid info' returned can be re-used over many 2d slices. + The 'regrid info' returned can be re-used over many cubes. """ - if src_cube.aux_factories: - msg = "All source cube derived coordinates will be ignored." - warnings.warn(msg) # Get the source cube x and y 2D auxiliary coordinates. sx, sy = src_cube.coord(axis="x"), src_cube.coord(axis="y") # Get the target grid cube x and y dimension coordinates. tx, ty = get_xy_dim_coords(grid_cube) + sl = [0] * grid_cube.ndim + sl[grid_cube.coord_dims(tx)[0]] = np.s_[:] + sl[grid_cube.coord_dims(ty)[0]] = np.s_[:] + grid_cube = grid_cube[tuple(sl)] + if sx.units != sy.units: msg = ( "The source cube x ({!r}) and y ({!r}) coordinates must " @@ -287,83 +288,108 @@ def _regrid_indices(cells, depth, points): return regrid_info -def _regrid_weighted_curvilinear_to_rectilinear__perform( - src_cube, regrid_info +def _curvilinear_to_rectilinear_regrid_data( + data, + dims, + regrid_info, ): """ - Second (regrid) part of 'regrid_weighted_curvilinear_to_rectilinear'. + Part of 'regrid_weighted_curvilinear_to_rectilinear' which acts on the data. - Perform the prepared regrid calculation on a single 2d cube. + Perform the prepared regrid calculation on an array. """ - from iris.cube import Cube - sparse_matrix, sum_weights, rows, grid_cube = regrid_info + inds = list(range(-len(dims), 0)) + data = np.moveaxis(data, dims, inds) + data_shape = data.shape + grid_size = np.prod([data_shape[ind] for ind in inds]) + # Calculate the numerator of the weighted mean (M, 1). - is_masked = ma.isMaskedArray(src_cube.data) + is_masked = ma.isMaskedArray(data) + sum_weights = None if not is_masked: - data = src_cube.data + data = data else: # Use raw data array - data = src_cube.data.data + r_data = data.data # Check if there are any masked source points to take account of. - is_masked = np.ma.is_masked(src_cube.data) + is_masked = ma.is_masked(data) if is_masked: # Zero any masked source points so they add nothing in output sums. - mask = src_cube.data.mask - data[mask] = 0.0 + mask = data.mask + r_data[mask] = 0.0 # Calculate a new 'sum_weights' to allow for missing source points. # N.B. it is more efficient to use the original once-calculated # sparse matrix, but in this case we can't. # Hopefully, this post-multiplying by the validities is less costly # than repeating the whole sparse calculation. - valid_src_cells = ~mask.flat[:] - src_cell_validity_factors = sparse_diags( - np.array(valid_src_cells, dtype=int), 0 - ) - valid_weights = sparse_matrix * src_cell_validity_factors - sum_weights = valid_weights.sum(axis=1).getA() - # Work out where output cells are missing all contributions. - # This allows for where 'rows' contains output cells that have no - # data because of missing input points. - zero_sums = sum_weights == 0.0 - # Make sure we can still divide by sum_weights[rows]. - sum_weights[zero_sums] = 1.0 + valid_src_cells = ~mask.reshape(-1, grid_size) + sum_weights = valid_src_cells @ sparse_matrix.T + data = r_data + if sum_weights is None: + sum_weights = ( + np.ones(data_shape).reshape(-1, grid_size) @ sparse_matrix.T + ) + # Work out where output cells are missing all contributions. + # This allows for where 'rows' contains output cells that have no + # data because of missing input points. + zero_sums = sum_weights == 0.0 + # Make sure we can still divide by sum_weights[rows]. + sum_weights[zero_sums] = 1.0 # Calculate sum in each target cell, over contributions from each source # cell. - numerator = sparse_matrix * data.reshape(-1, 1) - - # Create a template for the weighted mean result. - weighted_mean = ma.masked_all(numerator.shape, dtype=numerator.dtype) - - # Calculate final results in all relevant places. - weighted_mean[rows] = numerator[rows] / sum_weights[rows] - if is_masked: - # Ensure masked points where relevant source cells were all missing. - if np.any(zero_sums): - # Make masked if it wasn't. - weighted_mean = np.ma.asarray(weighted_mean) - # Mask where contributing sums were zero. - weighted_mean[zero_sums] = np.ma.masked - - # Construct the final regridded weighted mean cube. + numerator = data.reshape(-1, grid_size) @ sparse_matrix.T + + weighted_mean = numerator / sum_weights + # Ensure masked points where relevant source cells were all missing. + weighted_mean = ma.asarray(weighted_mean) + if np.any(zero_sums): + # Mask where contributing sums were zero. + weighted_mean[zero_sums] = ma.masked + + new_data_shape = list(data_shape) + for dim, length in zip(inds, grid_cube.shape): + new_data_shape[dim] = length + if len(dims) == 1: + new_data_shape.append(grid_cube.shape[1]) + dims = (dims[0], dims[0] + 1) + if len(dims) > 2: + new_data_shape = new_data_shape[: 2 - len(dims)] + dims = dims[:2] + + result = weighted_mean.reshape(new_data_shape) + result = np.moveaxis(result, [-2, -1], dims) + return result + + +def _regrid_weighted_curvilinear_to_rectilinear__perform( + src_cube, regrid_info +): + """ + Second (regrid) part of 'regrid_weighted_curvilinear_to_rectilinear'. + + Perform the prepared regrid calculation on a single cube. + + """ + dims = src_cube.coord_dims( + CurvilinearRegridder._get_horizontal_coord(src_cube, "x") + ) + result_data = _curvilinear_to_rectilinear_regrid_data( + src_cube.data, dims, regrid_info + ) + grid_cube = regrid_info[-1] tx = grid_cube.coord(axis="x", dim_coords=True) ty = grid_cube.coord(axis="y", dim_coords=True) - (tx_dim,) = grid_cube.coord_dims(tx) - (ty_dim,) = grid_cube.coord_dims(ty) - dim_coords_and_dims = list(zip((ty.copy(), tx.copy()), (ty_dim, tx_dim))) - cube = Cube( - weighted_mean.reshape(grid_cube.shape), - dim_coords_and_dims=dim_coords_and_dims, + regrid_callback = functools.partial( + _curvilinear_to_rectilinear_regrid_data, regrid_info=regrid_info ) - cube.metadata = copy.deepcopy(src_cube.metadata) - - for coord in src_cube.coords(dimensions=()): - cube.add_aux_coord(coord.copy()) - - return cube + result = _create_cube( + result_data, src_cube, dims, (ty.copy(), tx.copy()), 2, regrid_callback + ) + return result class CurvilinearRegridder: @@ -457,7 +483,7 @@ def __call__(self, src): point-in-cell regridding. """ - from iris.cube import Cube, CubeList + from iris.cube import Cube # Validity checks. if not isinstance(src, Cube): @@ -473,30 +499,18 @@ def __call__(self, src): "The given cube is not defined on the same " "source grid as this regridder." ) - - # Call the regridder function. - # This includes repeating over any non-XY dimensions, because the - # underlying routine does not support this. - # FOR NOW: we will use cube.slices and merge to achieve this, - # though that is not a terribly efficient method ... - # TODO: create a template result cube and paste data slices into it, - # which would be more efficient. - result_slices = CubeList([]) - for slice_cube in src.slices(sx): - if self._regrid_info is None: - # Calculate the basic regrid info just once. - self._regrid_info = ( - _regrid_weighted_curvilinear_to_rectilinear__prepare( - slice_cube, self.weights, self._target_cube - ) - ) - slice_result = ( - _regrid_weighted_curvilinear_to_rectilinear__perform( - slice_cube, self._regrid_info + slice_cube = next(src.slices(sx)) + if self._regrid_info is None: + # Calculate the basic regrid info just once. + self._regrid_info = ( + _regrid_weighted_curvilinear_to_rectilinear__prepare( + slice_cube, self.weights, self._target_cube ) ) - result_slices.append(slice_result) - result = result_slices.merge_cube() + result = _regrid_weighted_curvilinear_to_rectilinear__perform( + src, self._regrid_info + ) + return result @@ -688,11 +702,23 @@ def _regrid( # Prepare the result data array shape = list(src_data.shape) - assert shape[x_dim] == src_x_coord.shape[0] - assert shape[y_dim] == src_y_coord.shape[0] - - shape[y_dim] = sample_grid_x.shape[0] - shape[x_dim] = sample_grid_x.shape[1] + final_shape = shape.copy() + if x_dim is not None: + assert shape[x_dim] == src_x_coord.shape[0] + shape[x_dim] = sample_grid_x.shape[1] + final_shape[x_dim] = shape[x_dim] + else: + shape.append(1) + x_dim = len(shape) - 1 + src_data = np.expand_dims(src_data, -1) + if y_dim is not None: + assert shape[y_dim] == src_y_coord.shape[0] + shape[y_dim] = sample_grid_x.shape[0] + final_shape[y_dim] = shape[y_dim] + else: + shape.append(1) + y_dim = len(shape) - 1 + src_data = np.expand_dims(src_data, -1) dtype = src_data.dtype if method == "linear": @@ -714,7 +740,11 @@ def _regrid( if src_x_coord.points.size > 1 else False ) - reverse_y = src_y_coord.points[0] > src_y_coord.points[1] + reverse_y = ( + src_y_coord.points[0] > src_y_coord.points[1] + if src_y_coord.points.size > 1 + else False + ) flip_index = [slice(None)] * src_data.ndim if reverse_x: src_x_coord = src_x_coord[::-1] @@ -733,7 +763,7 @@ def _regrid( # Slice out the first full 2D piece of data for construction of the # interpolator. - index = [0] * src_data.ndim + index = [0] * len(shape) index[x_dim] = index[y_dim] = slice(None) initial_data = src_data[tuple(index)] if y_dim < x_dim: @@ -808,166 +838,21 @@ def interpolate(data): if ma.isMaskedArray(data) or mode.force_mask: # NB. np.ma.getmaskarray returns an array of `False` if # `src_subset` is not a masked array. - src_mask = np.ma.getmaskarray(src_subset) + src_mask = ma.getmaskarray(src_subset) interpolator.fill_value = mode.mask_fill_value mask_fraction = interpolate(src_mask) new_mask = mask_fraction > 0 - if np.ma.isMaskedArray(data): + if ma.isMaskedArray(data): data.mask[tuple(index)] = new_mask elif np.any(new_mask): # Set mask=False to ensure we have an expanded mask array. - data = np.ma.MaskedArray(data, mask=False) + data = ma.MaskedArray(data, mask=False) data.mask[tuple(index)] = new_mask + data = data.reshape(final_shape) return data - @staticmethod - def _create_cube( - data, - src, - x_dim, - y_dim, - src_x_coord, - src_y_coord, - grid_x_coord, - grid_y_coord, - sample_grid_x, - sample_grid_y, - regrid_callback, - ): - """ - Return a new Cube for the result of regridding the source Cube onto - the new grid. - - All the metadata and coordinates of the result Cube are copied from - the source Cube, with two exceptions: - - Grid dimension coordinates are copied from the grid Cube. - - Auxiliary coordinates which span the grid dimensions are - ignored, except where they provide a reference surface for an - :class:`iris.aux_factory.AuxCoordFactory`. - - Args: - - * data: - The regridded data as an N-dimensional NumPy array. - * src: - The source Cube. - * x_dim: - The X dimension within the source Cube. - * y_dim: - The Y dimension within the source Cube. - * src_x_coord: - The X :class:`iris.coords.DimCoord`. - * src_y_coord: - The Y :class:`iris.coords.DimCoord`. - * grid_x_coord: - The :class:`iris.coords.DimCoord` for the new grid's X - coordinate. - * grid_y_coord: - The :class:`iris.coords.DimCoord` for the new grid's Y - coordinate. - * sample_grid_x: - A 2-dimensional array of sample X values. - * sample_grid_y: - A 2-dimensional array of sample Y values. - * regrid_callback: - The routine that will be used to calculate the interpolated - values of any reference surfaces. - - Returns: - The new, regridded Cube. - - """ - from iris.cube import Cube - - # - # XXX: At the moment requires to be a static method as used by - # experimental regrid_area_weighted_rectilinear_src_and_grid - # - # Create a result cube with the appropriate metadata - result = Cube(data) - result.metadata = copy.deepcopy(src.metadata) - - # Copy across all the coordinates which don't span the grid. - # Record a mapping from old coordinate IDs to new coordinates, - # for subsequent use in creating updated aux_factories. - coord_mapping = {} - - def copy_coords(src_coords, add_method): - for coord in src_coords: - dims = src.coord_dims(coord) - if coord == src_x_coord: - coord = grid_x_coord - elif coord == src_y_coord: - coord = grid_y_coord - elif x_dim in dims or y_dim in dims: - continue - result_coord = coord.copy() - add_method(result_coord, dims) - coord_mapping[id(coord)] = result_coord - - copy_coords(src.dim_coords, result.add_dim_coord) - copy_coords(src.aux_coords, result.add_aux_coord) - - def regrid_reference_surface( - src_surface_coord, - surface_dims, - x_dim, - y_dim, - src_x_coord, - src_y_coord, - sample_grid_x, - sample_grid_y, - regrid_callback, - ): - # Determine which of the reference surface's dimensions span the X - # and Y dimensions of the source cube. - surface_x_dim = surface_dims.index(x_dim) - surface_y_dim = surface_dims.index(y_dim) - surface = regrid_callback( - src_surface_coord.points, - surface_x_dim, - surface_y_dim, - src_x_coord, - src_y_coord, - sample_grid_x, - sample_grid_y, - ) - surface_coord = src_surface_coord.copy(surface) - return surface_coord - - # Copy across any AuxFactory instances, and regrid their reference - # surfaces where required. - for factory in src.aux_factories: - for coord in factory.dependencies.values(): - if coord is None: - continue - dims = src.coord_dims(coord) - if x_dim in dims and y_dim in dims: - result_coord = regrid_reference_surface( - coord, - dims, - x_dim, - y_dim, - src_x_coord, - src_y_coord, - sample_grid_x, - sample_grid_y, - regrid_callback, - ) - result.add_aux_coord(result_coord, dims) - coord_mapping[id(coord)] = result_coord - try: - result.add_aux_factory(factory.updated(coord_mapping)) - except KeyError: - msg = ( - "Cannot update aux_factory {!r} because of dropped" - " coordinates.".format(factory.name()) - ) - warnings.warn(msg) - return result - def _check_units(self, coord): from iris.coord_systems import GeogCS, RotatedGeogCS @@ -1089,20 +974,168 @@ def __call__(self, src): ) # Wrap up the data as a Cube. - regrid_callback = functools.partial( - self._regrid, method=self._method, extrapolation_mode="nan" + _regrid_callback = functools.partial( + self._regrid, + src_x_coord=src_x_coord, + src_y_coord=src_y_coord, + sample_grid_x=sample_grid_x, + sample_grid_y=sample_grid_y, + method=self._method, + extrapolation_mode="nan", ) - result = self._create_cube( + + def regrid_callback(*args, **kwargs): + _data, dims = args + return _regrid_callback(_data, *dims, **kwargs) + + result = _create_cube( data, src, - x_dim, - y_dim, - src_x_coord, - src_y_coord, - grid_x_coord, - grid_y_coord, - sample_grid_x, - sample_grid_y, + [x_dim, y_dim], + [grid_x_coord, grid_y_coord], + 2, regrid_callback, ) return result + + +def _create_cube( + data, src, src_dims, tgt_coords, num_tgt_dims, regrid_callback +): + r""" + Return a new cube for the result of regridding. + Returned cube represents the result of regridding the source cube + onto the horizontal coordinates (e.g. latitude) of the target cube. + All the metadata and coordinates of the result cube are copied from + the source cube, with two exceptions: + - Horizontal coordinates are copied from the target cube. + - Auxiliary coordinates which span the grid dimensions are + ignored. + Parameters + ---------- + data : array + The regridded data as an N-dimensional NumPy array. + src : cube + The source Cube. + src_dims : tuple of int + The dimensions of the X and Y coordinate within the source Cube. + tgt_coords : tuple of :class:`iris.coords.Coord`\\ 's + Either two 1D :class:`iris.coords.DimCoord`\\ 's, two 1D + :class:`iris.experimental.ugrid.DimCoord`\\ 's or two ND + :class:`iris.coords.AuxCoord`\\ 's representing the new grid's + X and Y coordinates. + num_tgt_dims : int + The number of dimensions that the `tgt_coords` span. + regrid_callback : callable + The routine that will be used to calculate the interpolated + values of any reference surfaces. + Returns + ------- + cube + A new iris.cube.Cube instance. + """ + from iris.coords import DimCoord + from iris.cube import Cube + + result = Cube(data) + + if len(src_dims) >= 2: + grid_dim_x, grid_dim_y = src_dims[:2] + elif len(src_dims) == 1: + grid_dim_x = src_dims[0] + grid_dim_y = grid_dim_x + 1 + + if num_tgt_dims == 1: + grid_dim_x = grid_dim_y = min(src_dims) + for tgt_coord, dim in zip(tgt_coords, (grid_dim_x, grid_dim_y)): + if len(tgt_coord.shape) == 1: + if isinstance(tgt_coord, DimCoord) and dim is not None: + result.add_dim_coord(tgt_coord, dim) + else: + result.add_aux_coord(tgt_coord, dim) + else: + result.add_aux_coord(tgt_coord, (grid_dim_y, grid_dim_x)) + + result.metadata = copy.deepcopy(src.metadata) + + # Copy across all the coordinates which don't span the grid. + # Record a mapping from old coordinate IDs to new coordinates, + # for subsequent use in creating updated aux_factories. + + coord_mapping = {} + + def copy_coords(src_coords, add_method): + for coord in src_coords: + dims = src.coord_dims(coord) + if set(src_dims).intersection(set(dims)): + continue + if guess_coord_axis(coord) in ["X", "Y"]: + continue + + def dim_offset(dim): + offset = sum( + [ + d <= dim + for d in (grid_dim_x, grid_dim_y) + if d is not None + ] + ) + if offset and num_tgt_dims == 1: + offset -= 1 + offset -= sum([d <= dim for d in src_dims if d is not None]) + return dim + offset + + dims = [dim_offset(dim) for dim in dims] + result_coord = coord.copy() + # Add result_coord to the owner of add_method. + add_method(result_coord, dims) + coord_mapping[id(coord)] = result_coord + + copy_coords(src.dim_coords, result.add_dim_coord) + copy_coords(src.aux_coords, result.add_aux_coord) + + def regrid_reference_surface( + src_surface_coord, + surface_dims, + src_dims, + regrid_callback, + ): + # Determine which of the reference surface's dimensions span the X + # and Y dimensions of the source cube. + relative_surface_dims = [ + surface_dims.index(dim) if dim is not None else None + for dim in src_dims + ] + surface = regrid_callback( + src_surface_coord.points, + relative_surface_dims, + ) + surface_coord = src_surface_coord.copy(surface) + return surface_coord + + # Copy across any AuxFactory instances, and regrid their reference + # surfaces where required. + for factory in src.aux_factories: + for coord in factory.dependencies.values(): + if coord is None: + continue + dims = src.coord_dims(coord) + if set(src_dims).intersection(dims): + result_coord = regrid_reference_surface( + coord, + dims, + src_dims, + regrid_callback, + ) + result.add_aux_coord(result_coord, dims) + coord_mapping[id(coord)] = result_coord + try: + result.add_aux_factory(factory.updated(coord_mapping)) + except KeyError: + msg = ( + "Cannot update aux_factory {!r} because of dropped" + " coordinates.".format(factory.name()) + ) + warnings.warn(msg) + + return result diff --git a/lib/iris/experimental/regrid_conservative.py b/lib/iris/experimental/regrid_conservative.py index bfa048ddf0..fdc23c7bc4 100644 --- a/lib/iris/experimental/regrid_conservative.py +++ b/lib/iris/experimental/regrid_conservative.py @@ -17,13 +17,15 @@ """ +import functools + import cartopy.crs as ccrs import numpy as np import iris from iris._deprecation import warn_deprecated from iris.analysis._interpolation import get_xy_dim_coords -from iris.analysis._regrid import RectilinearRegridder +from iris.analysis._regrid import RectilinearRegridder, _create_cube from iris.util import _meshgrid wmsg = ( @@ -329,16 +331,23 @@ def _valid_units(coord): # Return result as a new cube based on the source. # TODO: please tidy this interface !!! - return RectilinearRegridder._create_cube( - fullcube_data, - src=source_cube, - x_dim=src_dims_xy[0], - y_dim=src_dims_xy[1], + _regrid_callback = functools.partial( + RectilinearRegridder._regrid, src_x_coord=src_coords[0], src_y_coord=src_coords[1], - grid_x_coord=dst_coords[0], - grid_y_coord=dst_coords[1], sample_grid_x=sample_grid_x, sample_grid_y=sample_grid_y, - regrid_callback=RectilinearRegridder._regrid, + ) + + def regrid_callback(*args, **kwargs): + _data, dims = args + return _regrid_callback(_data, *dims, **kwargs) + + return _create_cube( + fullcube_data, + source_cube, + [src_dims_xy[0], src_dims_xy[1]], + [dst_coords[0], dst_coords[1]], + 2, + regrid_callback, ) diff --git a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml index b41c0e48c7..cc9deb4260 100644 --- a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml +++ b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lat_cross_section.cml @@ -5,6 +5,60 @@ + + + + + + + @@ -65,6 +119,12 @@ [0.993097, 0.989272], [0.989272, 0.984692]]" id="a5c170db" long_name="sigma" points="[0.999424, 0.997504, 0.99482, 0.991375, 0.987171]" shape="(5,)" units="Unit('1')" value_type="float32"/> + + + diff --git a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml index 8617be9372..fb3d2cdbcf 100644 --- a/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml +++ b/lib/iris/tests/results/experimental/regrid/regrid_area_weighted_rectilinear_src_and_grid/const_lon_cross_section.cml @@ -5,6 +5,65 @@ + + + + + + + @@ -59,6 +118,11 @@ [0.993097, 0.989272], [0.989272, 0.984692]]" id="a5c170db" long_name="sigma" points="[0.999424, 0.997504, 0.99482, 0.991375, 0.987171]" shape="(5,)" units="Unit('1')" value_type="float32"/> + + + diff --git a/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py b/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py index 68db839d06..9b0160aee4 100644 --- a/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py +++ b/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py @@ -15,11 +15,12 @@ from iris.analysis._regrid import CurvilinearRegridder as Regridder from iris.analysis.cartography import rotate_pole +from iris.aux_factory import HybridHeightFactory from iris.coord_systems import GeogCS, RotatedGeogCS from iris.coords import AuxCoord, DimCoord from iris.cube import Cube from iris.fileformats.pp import EARTH_RADIUS -from iris.tests.stock import global_pp, lat_lon_cube +from iris.tests.stock import global_pp, lat_lon_cube, realistic_4d RESULT_DIR = ("analysis", "regrid") @@ -169,6 +170,88 @@ def test_caching(self): ) +class Test__derived_coord(tests.IrisTest): + def setUp(self): + src = realistic_4d()[0] + tgt = realistic_4d() + new_lon, new_lat = np.meshgrid( + src.coord("grid_longitude").points, + src.coord("grid_latitude").points, + ) + coord_system = src.coord("grid_latitude").coord_system + lat = AuxCoord( + new_lat, standard_name="latitude", coord_system=coord_system + ) + lon = AuxCoord( + new_lon, standard_name="longitude", coord_system=coord_system + ) + lat_t = AuxCoord( + new_lat.T, standard_name="latitude", coord_system=coord_system + ) + lon_t = AuxCoord( + new_lon.T, standard_name="longitude", coord_system=coord_system + ) + + src.remove_coord("grid_latitude") + src.remove_coord("grid_longitude") + src_t = src.copy() + src.add_aux_coord(lat, [1, 2]) + src.add_aux_coord(lon, [1, 2]) + src_t.add_aux_coord(lat_t, [2, 1]) + src_t.add_aux_coord(lon_t, [2, 1]) + self.src = src.copy() + self.src_t = src_t + self.tgt = tgt + self.altitude = src.coord("altitude") + transposed_src = src.copy() + transposed_src.transpose([0, 2, 1]) + self.altitude_transposed = transposed_src.coord("altitude") + + def test_no_transpose(self): + rg = Regridder(self.src, self.tgt) + res = rg(self.src) + + assert len(res.aux_factories) == 1 and isinstance( + res.aux_factories[0], HybridHeightFactory + ) + assert np.allclose(res.coord("altitude").points, self.altitude.points) + + def test_cube_transposed(self): + rg = Regridder(self.src, self.tgt) + transposed_cube = self.src.copy() + transposed_cube.transpose([0, 2, 1]) + res = rg(transposed_cube) + + assert len(res.aux_factories) == 1 and isinstance( + res.aux_factories[0], HybridHeightFactory + ) + assert np.allclose( + res.coord("altitude").points, self.altitude_transposed.points + ) + + def test_coord_transposed(self): + rg = Regridder(self.src_t, self.tgt) + res = rg(self.src_t) + + assert len(res.aux_factories) == 1 and isinstance( + res.aux_factories[0], HybridHeightFactory + ) + assert np.allclose( + res.coord("altitude").points, self.altitude_transposed.points + ) + + def test_both_transposed(self): + rg = Regridder(self.src_t, self.tgt) + transposed_cube = self.src_t.copy() + transposed_cube.transpose([0, 2, 1]) + res = rg(transposed_cube) + + assert len(res.aux_factories) == 1 and isinstance( + res.aux_factories[0], HybridHeightFactory + ) + assert np.allclose(res.coord("altitude").points, self.altitude.points) + + @tests.skip_data class Test___call____bad_src(tests.IrisTest): def setUp(self): @@ -219,7 +302,7 @@ def test_multidim(self): grid_cube.add_dim_coord(grid_y_coord, 0) grid_cube.add_dim_coord(grid_x_coord, 1) - # Define some key points in true-lat/lon thta have known positions + # Define some key points in true-lat/lon that have known positions # First 3x2 points in the centre of each output cell. x_centres, y_centres = np.meshgrid( grid_x_coord.points, grid_y_coord.points From e7a8f312816508b08418ea476a151eacbfa999cf Mon Sep 17 00:00:00 2001 From: Bill Little Date: Mon, 14 Nov 2022 10:34:37 +0000 Subject: [PATCH 271/319] PP field extra data words fix (#5058) * fix mule pp field data padding * add test coverage * update exception message * use units modulus * parametrize pp save test * assert dimensionality for save test * add whatsnew entry * parametrize dtype for load test * update whatnew entry * Update lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * review actions * add comment * add reviewer to whatnew entry Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 4 ++ lib/iris/fileformats/pp.py | 12 ++++++ lib/iris/fileformats/pp_save_rules.py | 11 +++++- .../pp/test__data_bytes_to_shaped_array.py | 39 +++++++++++++++++++ .../tests/unit/fileformats/pp/test_save.py | 23 +++++++++++ 5 files changed, 88 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9ee6826f67..8332b1444d 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -103,6 +103,10 @@ This document explains the changes made to Iris for this release to indicate that this is the upward, rather than northward part of the flow. (:pull:`5060`) +#. `@bjlittle`_ and `@trexfeathers`_ (reviewer) fixed an issue which prevented + uncompressed PP fields with additional trailing padded words in the field + data to be loaded and saved. (:pull:`5058`) + 💣 Incompatible Changes ======================= diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index 1fb7d4e178..bc35acb3b3 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -767,6 +767,18 @@ def _data_bytes_to_shaped_array( else: # Reform in row-column order + actual_length = np.prod(data.shape) + if (expected_length := np.prod(data_shape)) != actual_length: + if (expected_length < actual_length) and (data.ndim == 1): + # known use case where mule adds padding to data payload + # for a collapsed field. + data = data[:expected_length] + else: + emsg = ( + f"PP field data containing {actual_length} words does not " + f"match expected length of {expected_length} words." + ) + raise ValueError(emsg) data.shape = data_shape # Mask the array diff --git a/lib/iris/fileformats/pp_save_rules.py b/lib/iris/fileformats/pp_save_rules.py index e6b3748f9b..0369fc9fd0 100644 --- a/lib/iris/fileformats/pp_save_rules.py +++ b/lib/iris/fileformats/pp_save_rules.py @@ -422,7 +422,16 @@ def _grid_and_pole_rules(cube, pp): lat_coord = vector_coord(cube, "latitude") grid_lat_coord = vector_coord(cube, "grid_latitude") - if lon_coord and not is_regular(lon_coord): + scalar_lon_coord = scalar_coord(cube, "longitude") + + if lon_coord is None and grid_lon_coord is None and scalar_lon_coord: + # default value of 360.0 degrees to specify a circular wrap of + # the collapsed scalar longitude coordinate, based on examples + # of model output for several different diagnostics + pp.bdx = (unit := scalar_lon_coord.units) and unit.modulus or 360.0 + pp.bzx = scalar_lon_coord.points[0] - pp.bdx + pp.lbnpt = scalar_lon_coord.shape[0] + elif lon_coord and not is_regular(lon_coord): pp.bzx = 0 pp.bdx = 0 pp.lbnpt = lon_coord.shape[0] diff --git a/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py b/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py index 83475c6782..73913c6219 100644 --- a/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py +++ b/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py @@ -17,10 +17,49 @@ import numpy as np import numpy.ma as ma +import pytest import iris.fileformats.pp as pp +@pytest.mark.parametrize("data_shape", [(2, 3)]) +@pytest.mark.parametrize( + "expected_shape", [(2, 3), (3, 2), (1, 3), (2, 2), (3, 3), (2, 4)] +) +@pytest.mark.parametrize( + "data_type", [np.float32, np.int32, np.int16, np.int8] +) +def test_data_padding__no_compression(data_shape, expected_shape, data_type): + data = np.empty(data_shape, dtype=data_type) + + # create the field data buffer + buffer = io.BytesIO() + buffer.write(data) + buffer.seek(0) + data_bytes = buffer.read() + + lbpack = pp.SplittableInt(0, dict(n1=0, n2=1)) + boundary_packing = None + mdi = -1 + args = ( + data_bytes, + lbpack, + boundary_packing, + expected_shape, + data_type, + mdi, + ) + data_length, expected_length = np.prod(data_shape), np.prod(expected_shape) + + if expected_length <= data_length: + result = pp._data_bytes_to_shaped_array(*args) + assert result.shape == expected_shape + else: + emsg = r"data containing \d+ words does not match expected length" + with pytest.raises(ValueError, match=emsg): + _ = pp._data_bytes_to_shaped_array(*args) + + class Test__data_bytes_to_shaped_array__lateral_boundary_compression( tests.IrisTest ): diff --git a/lib/iris/tests/unit/fileformats/pp/test_save.py b/lib/iris/tests/unit/fileformats/pp/test_save.py index 45012dc8bd..8200259cca 100644 --- a/lib/iris/tests/unit/fileformats/pp/test_save.py +++ b/lib/iris/tests/unit/fileformats/pp/test_save.py @@ -13,6 +13,8 @@ import cf_units import cftime +import numpy as np +import pytest from iris.coords import CellMethod, DimCoord from iris.fileformats._ff_cross_references import STASH_TRANS @@ -21,6 +23,27 @@ import iris.tests.stock as stock +@pytest.mark.parametrize( + "unit,modulus", + [ + (cf_units.Unit("radians"), 2 * np.pi), + (cf_units.Unit("degrees"), 360.0), + (None, 360.0), + ], +) +def test_grid_and_pole__scalar_dim_longitude(unit, modulus): + cube = stock.lat_lon_cube()[:, -1:] + assert cube.ndim == 2 + lon = cube.coord("longitude") + lon.units = unit + + field = _pp_save_ppfield_values(cube) + bdx = modulus + assert field.bdx == bdx + assert field.bzx == (lon.points[0] - bdx) + assert field.lbnpt == lon.points.size + + def _pp_save_ppfield_values(cube): """ Emulate saving a cube as PP, and capture the resulting PP field values. From c0ab2e76356fe99095cb4f4cabbddd7e37fbee7c Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Mon, 14 Nov 2022 12:18:25 +0000 Subject: [PATCH 272/319] added link to the docs archive. (#5064) * added link to the docs archive. * added whatsnew --- docs/src/common_links.inc | 2 +- docs/src/index.rst | 8 +++++++- docs/src/whatsnew/latest.rst | 2 ++ docs/src/why_iris.rst | 3 +-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index 182c8ad59f..530ebc4877 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -21,7 +21,7 @@ .. _isort: https://pycqa.github.io/isort/ .. _issue: https://github.com/SciTools/iris/issues .. _issues: https://github.com/SciTools/iris/issues -.. _legacy documentation: https://scitools.org.uk/iris/docs/v2.4.0/ +.. _legacy documentation: https://github.com/SciTools/scitools.org.uk/tree/master/iris/docs/archive .. _matplotlib: https://matplotlib.org/stable/ .. _napolean: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/sphinxcontrib.napoleon.html .. _nox: https://nox.thea.codes/en/stable/ diff --git a/docs/src/index.rst b/docs/src/index.rst index b9f7faaa03..c5d654ed31 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -88,6 +88,8 @@ Icons made by `FreePik `_ from `Flaticon `_ +.. _iris_support: + Support ~~~~~~~ @@ -101,7 +103,11 @@ The legacy support resources: * `Users Google Group `_ * `Developers Google Group `_ -* `Legacy Documentation`_ (Iris 2.4 or earlier) +* `Legacy Documentation`_ (Iris 2.4 or earlier). This is an archive of zip + files of past documentation. You can download, unzip and view the + documentation locally (index.html). There may be some incorrect rendering + and older javascvript (.js) files may show a warning when uncompressing, in + which case we suggest you use a different unzip tool. .. toctree:: diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 8332b1444d..770242d4ca 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -191,6 +191,8 @@ This document explains the changes made to Iris for this release to reference Read the Docs' built-in version switcher, instead of generating its own independent links. (:pull:`5055`) +#. `@tkknight`_ updated the links for the Iris documentation to v2.4 and + earlier to point to the archive of zip files instead. (:pull:`5064`) 💼 Internal =========== diff --git a/docs/src/why_iris.rst b/docs/src/why_iris.rst index 63a515f68e..82b791b4bd 100644 --- a/docs/src/why_iris.rst +++ b/docs/src/why_iris.rst @@ -40,5 +40,4 @@ Interoperability with packages from the wider scientific Python ecosystem comes from Iris' use of standard NumPy/dask arrays as its underlying data storage. Iris is part of SciTools, for more information see https://scitools.org.uk/. -For **Iris 2.4** and earlier documentation please see the -:link-badge:`https://scitools.org.uk/iris/docs/v2.4.0/,"legacy documentation",cls=badge-info text-white`. +For **Iris 2.4** and earlier documentation please see :ref:`iris_support`. \ No newline at end of file From b2da2d73fd54666784e481d972bc95533cc8e174 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 16 Nov 2022 15:17:18 +0000 Subject: [PATCH 273/319] More tolerant NetCDF4 loading - skip un-addable dimensional metadata (#5054) * More tolerant NetCDF loading by skipping invalid coords etc. * Tolerant loading tests. * What's New entry. * self.monkeypatch comments. * Add integration test. * Include TODO comment. * Use monkeypatch context manager. * Mention CannotAddError in the Whats New. * Another reference to SciTools/iris#5068. --- docs/src/whatsnew/latest.rst | 10 ++- lib/iris/cube.py | 42 +++++---- lib/iris/exceptions.py | 6 ++ .../fileformats/_nc_load_rules/helpers.py | 86 +++++++++++++------ lib/iris/tests/integration/test_netcdf.py | 51 +++++++++++ .../helpers/test_build_ancil_var.py | 77 +++++++++++++++++ .../test_build_auxiliary_coordinate.py | 20 +++++ .../helpers/test_build_cell_measure.py | 74 ++++++++++++++++ .../test_build_dimension_coordinate.py | 42 +++++++++ 9 files changed, 363 insertions(+), 45 deletions(-) create mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py create mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 770242d4ca..19395869be 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -65,6 +65,14 @@ This document explains the changes made to Iris for this release #. `@stephenworsley`_ added the ability to regrid derived coordinates with the :obj:`~iris.analysis.PointInCell` regridding scheme. (:pull:`4807`) +#. `@trexfeathers`_ made NetCDF loading more tolerant by enabling skipping of + :class:`~iris.coords.DimCoord`\s, :class:`~iris.coords.AuxCoord`\s, + :class:`~iris.coords.CellMeasure`\s and + :class:`~iris.coords.AncillaryVariable`\s if they cannot be added to a + :class:`~iris.cube.Cube` (e.g. due to CF non-compliance). This is done via + a new error class: :class:`~iris.exceptions.CannotAddError` (subclass of + :class:`ValueError`). (:pull:`5054`) + 🐛 Bugs Fixed ============= @@ -98,7 +106,7 @@ This document explains the changes made to Iris for this release #. `@stephenworsley`_ fixed a bug which caused derived coordinates to be realised after calling :meth:`iris.cube.Cube.aggregated_by`. (:issue:`3637`, :pull:`4947`) - + #. `@rcomer`_ corrected the ``standard_name`` mapping from UM stash code ``m01s30i311`` to indicate that this is the upward, rather than northward part of the flow. (:pull:`5060`) diff --git a/lib/iris/cube.py b/lib/iris/cube.py index e45ac8ca2d..be01bc9e5d 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -1158,7 +1158,9 @@ def add_aux_coord(self, coord, data_dims=None): """ if self.coords(coord): # TODO: just fail on duplicate object - raise ValueError("Duplicate coordinates are not permitted.") + raise iris.exceptions.CannotAddError( + "Duplicate coordinates are not permitted." + ) self._add_unique_aux_coord(coord, data_dims) def _check_multi_dim_metadata(self, metadata, data_dims): @@ -1178,7 +1180,7 @@ def _check_multi_dim_metadata(self, metadata, data_dims): len(data_dims), metadata.ndim, metadata.name() ) ) - raise ValueError(msg) + raise iris.exceptions.CannotAddError(msg) # Check compatibility with the shape of the data for i, dim in enumerate(data_dims): if metadata.shape[i] != self.shape[dim]: @@ -1186,7 +1188,7 @@ def _check_multi_dim_metadata(self, metadata, data_dims): "Unequal lengths. Cube dimension {} => {};" " metadata {!r} dimension {} => {}." ) - raise ValueError( + raise iris.exceptions.CannotAddError( msg.format( dim, self.shape[dim], @@ -1198,7 +1200,7 @@ def _check_multi_dim_metadata(self, metadata, data_dims): elif metadata.shape != (1,): msg = "Missing data dimensions for multi-valued {} {!r}" msg = msg.format(metadata.__class__.__name__, metadata.name()) - raise ValueError(msg) + raise iris.exceptions.CannotAddError(msg) return data_dims def _add_unique_aux_coord(self, coord, data_dims): @@ -1212,7 +1214,7 @@ def _add_unique_aux_coord(self, coord, data_dims): "cube {item} of {ownval!r}." ) if coord.mesh != mesh: - raise ValueError( + raise iris.exceptions.CannotAddError( msg.format( item="mesh", coord=coord, @@ -1222,7 +1224,7 @@ def _add_unique_aux_coord(self, coord, data_dims): ) location = self.location if coord.location != location: - raise ValueError( + raise iris.exceptions.CannotAddError( msg.format( item="location", coord=coord, @@ -1232,7 +1234,7 @@ def _add_unique_aux_coord(self, coord, data_dims): ) mesh_dims = (self.mesh_dim(),) if data_dims != mesh_dims: - raise ValueError( + raise iris.exceptions.CannotAddError( msg.format( item="mesh dimension", coord=coord, @@ -1272,7 +1274,9 @@ def coordsonly(coords_and_dims): ref_coord = aux_factory.dependencies[dependency] if ref_coord is not None and ref_coord not in cube_coords: msg = "{} coordinate for factory is not present on cube {}" - raise ValueError(msg.format(ref_coord.name(), self.name())) + raise iris.exceptions.CannotAddError( + msg.format(ref_coord.name(), self.name()) + ) self._aux_factories.append(aux_factory) def add_cell_measure(self, cell_measure, data_dims=None): @@ -1299,7 +1303,9 @@ def add_cell_measure(self, cell_measure, data_dims=None): """ if self.cell_measures(cell_measure): - raise ValueError("Duplicate cell_measures are not permitted.") + raise iris.exceptions.CannotAddError( + "Duplicate cell_measures are not permitted." + ) data_dims = self._check_multi_dim_metadata(cell_measure, data_dims) self._cell_measures_and_dims.append((cell_measure, data_dims)) self._cell_measures_and_dims.sort( @@ -1327,7 +1333,9 @@ def add_ancillary_variable(self, ancillary_variable, data_dims=None): """ if self.ancillary_variables(ancillary_variable): - raise ValueError("Duplicate ancillary variables not permitted") + raise iris.exceptions.CannotAddError( + "Duplicate ancillary variables not permitted" + ) data_dims = self._check_multi_dim_metadata( ancillary_variable, data_dims @@ -1358,13 +1366,13 @@ def add_dim_coord(self, dim_coord, data_dim): """ if self.coords(dim_coord): - raise ValueError( + raise iris.exceptions.CannotAddError( "The coordinate already exists on the cube. " "Duplicate coordinates are not permitted." ) # Check dimension is available if self.coords(dimensions=data_dim, dim_coords=True): - raise ValueError( + raise iris.exceptions.CannotAddError( "A dim_coord is already associated with " "dimension %d." % data_dim ) @@ -1372,12 +1380,14 @@ def add_dim_coord(self, dim_coord, data_dim): def _add_unique_dim_coord(self, dim_coord, data_dim): if isinstance(dim_coord, iris.coords.AuxCoord): - raise ValueError("The dim_coord may not be an AuxCoord instance.") + raise iris.exceptions.CannotAddError( + "The dim_coord may not be an AuxCoord instance." + ) # Convert data_dim to a single integer if isinstance(data_dim, Container): if len(data_dim) != 1: - raise ValueError( + raise iris.exceptions.CannotAddError( "The supplied data dimension must be a" " single number." ) data_dim = int(list(data_dim)[0]) @@ -1386,7 +1396,7 @@ def _add_unique_dim_coord(self, dim_coord, data_dim): # Check data_dim value is valid if data_dim < 0 or data_dim >= self.ndim: - raise ValueError( + raise iris.exceptions.CannotAddError( "The cube does not have the specified dimension " "(%d)" % data_dim ) @@ -1394,7 +1404,7 @@ def _add_unique_dim_coord(self, dim_coord, data_dim): # Check compatibility with the shape of the data if dim_coord.shape[0] != self.shape[data_dim]: msg = "Unequal lengths. Cube dimension {} => {}; coord {!r} => {}." - raise ValueError( + raise iris.exceptions.CannotAddError( msg.format( data_dim, self.shape[data_dim], diff --git a/lib/iris/exceptions.py b/lib/iris/exceptions.py index 12d24ef70f..5d3da3349e 100644 --- a/lib/iris/exceptions.py +++ b/lib/iris/exceptions.py @@ -174,3 +174,9 @@ class UnitConversionError(IrisError): """Raised when Iris is unable to convert a unit.""" pass + + +class CannotAddError(ValueError): + """Raised when an object (e.g. coord) cannot be added to a :class:`~iris.cube.Cube`.""" + + pass diff --git a/lib/iris/fileformats/_nc_load_rules/helpers.py b/lib/iris/fileformats/_nc_load_rules/helpers.py index c075a659ac..35163c47d5 100644 --- a/lib/iris/fileformats/_nc_load_rules/helpers.py +++ b/lib/iris/fileformats/_nc_load_rules/helpers.py @@ -13,7 +13,6 @@ build routines, and which it does not use. """ - import warnings import cf_units @@ -37,6 +36,8 @@ import iris.std_names import iris.util +# TODO: should un-addable coords / cell measures / etcetera be skipped? iris#5068. + # # UD Units Constants (based on Unidata udunits.dat definition file) # @@ -853,6 +854,12 @@ def build_dimension_coordinate( cf_coord_var, coord_name, attributes ) + coord_skipped_msg = ( + f"{cf_coord_var.cf_name} coordinate not added to Cube: " + ) + coord_skipped_msg += "{error}" + coord_skipped = False + # Create the coordinate. try: coord = iris.coords.DimCoord( @@ -869,6 +876,11 @@ def build_dimension_coordinate( ) except ValueError as e_msg: # Attempt graceful loading. + msg = ( + "Failed to create {name!r} dimension coordinate: {error}\n" + "Gracefully creating {name!r} auxiliary coordinate instead." + ) + warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg)) coord = iris.coords.AuxCoord( points_data, standard_name=standard_name, @@ -880,22 +892,26 @@ def build_dimension_coordinate( coord_system=coord_system, climatological=climatological, ) - cube.add_aux_coord(coord, data_dims) - msg = ( - "Failed to create {name!r} dimension coordinate: {error}\n" - "Gracefully creating {name!r} auxiliary coordinate instead." - ) - warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg)) + try: + cube.add_aux_coord(coord, data_dims) + except iris.exceptions.CannotAddError as e_msg: + warnings.warn(coord_skipped_msg.format(error=e_msg)) + coord_skipped = True else: # Add the dimension coordinate to the cube. - if data_dims: - cube.add_dim_coord(coord, data_dims) - else: - # Scalar coords are placed in the aux_coords container. - cube.add_aux_coord(coord, data_dims) + try: + if data_dims: + cube.add_dim_coord(coord, data_dims) + else: + # Scalar coords are placed in the aux_coords container. + cube.add_aux_coord(coord, data_dims) + except iris.exceptions.CannotAddError as e_msg: + warnings.warn(coord_skipped_msg.format(error=e_msg)) + coord_skipped = True - # Update the coordinate to CF-netCDF variable mapping. - engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name)) + if not coord_skipped: + # Update the coordinate to CF-netCDF variable mapping. + engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name)) ################################################################################ @@ -964,10 +980,14 @@ def build_auxiliary_coordinate( ) # Add it to the cube - cube.add_aux_coord(coord, data_dims) - - # Make a list with names, stored on the engine, so we can find them all later. - engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name)) + try: + cube.add_aux_coord(coord, data_dims) + except iris.exceptions.CannotAddError as e_msg: + msg = "{name!r} coordinate not added to Cube: {error}" + warnings.warn(msg.format(name=str(cf_coord_var.cf_name), error=e_msg)) + else: + # Make a list with names, stored on the engine, so we can find them all later. + engine.cube_parts["coordinates"].append((coord, cf_coord_var.cf_name)) ################################################################################ @@ -1011,12 +1031,16 @@ def build_cell_measures(engine, cf_cm_var): ) # Add it to the cube - cube.add_cell_measure(cell_measure, data_dims) - - # Make a list with names, stored on the engine, so we can find them all later. - engine.cube_parts["cell_measures"].append( - (cell_measure, cf_cm_var.cf_name) - ) + try: + cube.add_cell_measure(cell_measure, data_dims) + except iris.exceptions.CannotAddError as e_msg: + msg = "{name!r} cell measure not added to Cube: {error}" + warnings.warn(msg.format(name=str(cf_cm_var.cf_name), error=e_msg)) + else: + # Make a list with names, stored on the engine, so we can find them all later. + engine.cube_parts["cell_measures"].append( + (cell_measure, cf_cm_var.cf_name) + ) ################################################################################ @@ -1056,10 +1080,16 @@ def build_ancil_var(engine, cf_av_var): ) # Add it to the cube - cube.add_ancillary_variable(av, data_dims) - - # Make a list with names, stored on the engine, so we can find them all later. - engine.cube_parts["ancillary_variables"].append((av, cf_av_var.cf_name)) + try: + cube.add_ancillary_variable(av, data_dims) + except iris.exceptions.CannotAddError as e_msg: + msg = "{name!r} ancillary variable not added to Cube: {error}" + warnings.warn(msg.format(name=str(cf_av_var.cf_name), error=e_msg)) + else: + # Make a list with names, stored on the engine, so we can find them all later. + engine.cube_parts["ancillary_variables"].append( + (av, cf_av_var.cf_name) + ) ################################################################################ diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py index 3feb637bf8..851c539ade 100644 --- a/lib/iris/tests/integration/test_netcdf.py +++ b/lib/iris/tests/integration/test_netcdf.py @@ -21,11 +21,13 @@ import netCDF4 as nc import numpy as np import numpy.ma as ma +import pytest import iris import iris.coord_systems from iris.coords import CellMethod, DimCoord from iris.cube import Cube, CubeList +import iris.exceptions from iris.fileformats.netcdf import ( CF_CONVENTIONS_VERSION, Saver, @@ -903,5 +905,54 @@ def test_netcdf_with_no_constraint(self): self.assertEqual(len(cubes), 3) +class TestSkippedCoord: + # If a coord/cell measure/etcetera cannot be added to the loaded Cube, a + # Warning is raised and the coord is skipped. + # This 'catching' is generic to all CannotAddErrors, but currently the only + # such problem that can exist in a NetCDF file is a mismatch of dimensions + # between phenomenon and coord. + + cdl_core = """ +dimensions: + length_scale = 1 ; + lat = 3 ; +variables: + float lat(lat) ; + lat:standard_name = "latitude" ; + lat:units = "degrees_north" ; + short lst_unc_sys(length_scale) ; + lst_unc_sys:long_name = "uncertainty from large-scale systematic + errors" ; + lst_unc_sys:units = "kelvin" ; + lst_unc_sys:coordinates = "lat" ; + +data: + lat = 0, 1, 2; + """ + + @pytest.fixture(autouse=True) + def create_nc_file(self, tmp_path): + file_name = "dim_mismatch" + cdl = f"netcdf {file_name}" + "{\n" + self.cdl_core + "\n}" + self.nc_path = (tmp_path / file_name).with_suffix(".nc") + ncgen_from_cdl( + cdl_str=cdl, + cdl_path=None, + nc_path=str(self.nc_path), + ) + yield + self.nc_path.unlink() + + def test_lat_not_loaded(self): + # iris#5068 includes discussion of possible retention of the skipped + # coords in the future. + with pytest.warns( + match="Missing data dimensions for multi-valued DimCoord" + ): + cube = iris.load_cube(self.nc_path) + with pytest.raises(iris.exceptions.CoordinateNotFoundError): + _ = cube.coord("lat") + + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py new file mode 100644 index 0000000000..b057a41a3e --- /dev/null +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py @@ -0,0 +1,77 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Test function :func:`iris.fileformats._nc_load_rules.helpers.build_ancil_var`. + +""" + +from unittest import mock + +import numpy as np +import pytest + +from iris.exceptions import CannotAddError +from iris.fileformats._nc_load_rules.helpers import build_ancil_var + + +@pytest.fixture +def mock_engine(): + return mock.Mock( + cube=mock.Mock(), + cf_var=mock.Mock(dimensions=("foo", "bar")), + filename="DUMMY", + cube_parts=dict(ancillary_variables=[]), + ) + + +@pytest.fixture +def mock_cf_av_var(monkeypatch): + data = np.arange(6) + output = mock.Mock( + dimensions=("foo",), + scale_factor=1, + add_offset=0, + cf_name="wibble", + cf_data=mock.MagicMock(chunking=mock.Mock(return_value=None), spec=[]), + standard_name=None, + long_name="wibble", + units="m2", + shape=data.shape, + dtype=data.dtype, + __getitem__=lambda self, key: data[key], + ) + + # Create patch for deferred loading that prevents attempted + # file access. This assumes that output is defined in the test case. + def patched__getitem__(proxy_self, keys): + if proxy_self.variable_name == output.cf_name: + return output[keys] + raise RuntimeError() + + monkeypatch.setattr( + "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", + patched__getitem__, + ) + + return output + + +def test_not_added(monkeypatch, mock_engine, mock_cf_av_var): + # Confirm that the ancillary variable will be skipped if a CannotAddError + # is raised when attempting to add. + def mock_add_ancillary_variable(_, __): + raise CannotAddError("foo") + + with monkeypatch.context() as m: + m.setattr( + mock_engine.cube, + "add_ancillary_variable", + mock_add_ancillary_variable, + ) + with pytest.warns(match="ancillary variable not added to Cube: foo"): + build_ancil_var(mock_engine, mock_cf_av_var) + + assert mock_engine.cube_parts["ancillary_variables"] == [] diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py index 95f892454b..13622b72e2 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py @@ -16,8 +16,10 @@ from unittest import mock import numpy as np +import pytest from iris.coords import AuxCoord +from iris.exceptions import CannotAddError from iris.fileformats._nc_load_rules.helpers import build_auxiliary_coordinate from iris.fileformats.cf import CFVariable @@ -280,6 +282,11 @@ def get_cf_bounds_var(coord_var): new=get_cf_bounds_var, ) + # test_not_added() has been written in pytest-style, but the rest of + # the class is pending migration. Defining self.monkeypatch (not the + # typical practice in pure pytest) allows this transitional state. + self.monkeypatch = pytest.MonkeyPatch() + def check_case_aux_coord_construction(self, climatology=False): # Test a generic auxiliary coordinate, with or without # a climatological coord. @@ -305,6 +312,19 @@ def test_aux_coord_construction(self): def test_aux_coord_construction__climatology(self): self.check_case_aux_coord_construction(climatology=True) + def test_not_added(self): + # Confirm that the coord will be skipped if a CannotAddError is raised + # when attempting to add. + def mock_add_aux_coord(_, __): + raise CannotAddError("foo") + + with self.monkeypatch.context() as m: + m.setattr(self.engine.cube, "add_aux_coord", mock_add_aux_coord) + with pytest.warns(match="coordinate not added to Cube: foo"): + build_auxiliary_coordinate(self.engine, self.cf_coord_var) + + assert self.engine.cube_parts["coordinates"] == [] + if __name__ == "__main__": tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py new file mode 100644 index 0000000000..efbb0649c9 --- /dev/null +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py @@ -0,0 +1,74 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Test function :func:`iris.fileformats._nc_load_rules.helpers.build_cell_measure`. + +""" + +from unittest import mock + +import numpy as np +import pytest + +from iris.exceptions import CannotAddError +from iris.fileformats._nc_load_rules.helpers import build_cell_measures + + +@pytest.fixture +def mock_engine(): + return mock.Mock( + cube=mock.Mock(), + cf_var=mock.Mock(dimensions=("foo", "bar")), + filename="DUMMY", + cube_parts=dict(cell_measures=[]), + ) + + +@pytest.fixture +def mock_cf_cm_var(monkeypatch): + data = np.arange(6) + output = mock.Mock( + dimensions=("foo",), + scale_factor=1, + add_offset=0, + cf_name="wibble", + cf_data=mock.MagicMock(chunking=mock.Mock(return_value=None), spec=[]), + standard_name=None, + long_name="wibble", + units="m2", + shape=data.shape, + dtype=data.dtype, + __getitem__=lambda self, key: data[key], + cf_measure="area", + ) + + # Create patch for deferred loading that prevents attempted + # file access. This assumes that output is defined in the test case. + def patched__getitem__(proxy_self, keys): + if proxy_self.variable_name == output.cf_name: + return output[keys] + raise RuntimeError() + + monkeypatch.setattr( + "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", + patched__getitem__, + ) + + return output + + +def test_not_added(monkeypatch, mock_engine, mock_cf_cm_var): + # Confirm that the cell measure will be skipped if a CannotAddError is + # raised when attempting to add. + def mock_add_cell_measure(_, __): + raise CannotAddError("foo") + + with monkeypatch.context() as m: + m.setattr(mock_engine.cube, "add_cell_measure", mock_add_cell_measure) + with pytest.warns(match="cell measure not added to Cube: foo"): + build_cell_measures(mock_engine, mock_cf_cm_var) + + assert mock_engine.cube_parts["cell_measures"] == [] diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py index a75678d923..b485937cb1 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py @@ -17,8 +17,10 @@ import warnings import numpy as np +import pytest from iris.coords import AuxCoord, DimCoord +from iris.exceptions import CannotAddError from iris.fileformats._nc_load_rules.helpers import build_dimension_coordinate @@ -73,6 +75,12 @@ def setUp(self): ) self.bounds = bounds + # test_dimcoord_not_added() and test_auxcoord_not_added have been + # written in pytest-style, but the rest of the class is pending + # migration. Defining self.monkeypatch (not the + # typical practice in pure pytest) allows this transitional state. + self.monkeypatch = pytest.MonkeyPatch() + def _set_cf_coord_var(self, points): self.cf_coord_var = mock.Mock( dimensions=("foo",), @@ -233,6 +241,40 @@ def test_aux_coord_construction(self): warnings.warn.call_args[0][0], ) + def test_dimcoord_not_added(self): + # Confirm that the coord will be skipped if a CannotAddError is raised + # when attempting to add. + def mock_add_dim_coord(_, __): + raise CannotAddError("foo") + + with self.monkeypatch.context() as m: + m.setattr(self.engine.cube, "add_dim_coord", mock_add_dim_coord) + + self._set_cf_coord_var(np.arange(6)) + + with self.deferred_load_patch, self.get_cf_bounds_var_patch: + with pytest.warns(match="coordinate not added to Cube: foo"): + build_dimension_coordinate(self.engine, self.cf_coord_var) + + assert self.engine.cube_parts["coordinates"] == [] + + def test_auxcoord_not_added(self): + # Confirm that a gracefully-created auxiliary coord will also be + # skipped if a CannotAddError is raised when attempting to add. + def mock_add_aux_coord(_, __): + raise CannotAddError("foo") + + with self.monkeypatch.context() as m: + m.setattr(self.engine.cube, "add_aux_coord", mock_add_aux_coord) + + self._set_cf_coord_var(np.array([1, 3, 2, 4, 6, 5])) + + with self.deferred_load_patch, self.get_cf_bounds_var_patch: + with pytest.warns(match="coordinate not added to Cube: foo"): + build_dimension_coordinate(self.engine, self.cf_coord_var) + + assert self.engine.cube_parts["coordinates"] == [] + class TestBoundsVertexDim(tests.IrisTest, RulesTestMixin): def setUp(self): From f9c158c17124fc10bd6664a5435d9b6015db5a62 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Wed, 16 Nov 2022 16:57:47 +0000 Subject: [PATCH 274/319] [CI Bot] environment lockfiles auto-update (#5051) * Updated environment lockfiles * Fix auto update lockfiles (#5070) * introduce graphviz min pin * update lock files Co-authored-by: Lockfile bot Co-authored-by: Bill Little --- requirements/ci/nox.lock/py310-linux-64.lock | 63 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 63 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 63 ++++++++++---------- requirements/ci/py310.yml | 4 ++ requirements/ci/py38.yml | 4 ++ requirements/ci/py39.yml | 4 ++ 6 files changed, 105 insertions(+), 96 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index b4a9107fbe..6cea5880aa 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 5bb7d18990d558bee14aa553199bb8ef5e346b16214bf8df00042789e3f420e8 +# input_hash: e755261bf8cb4508d0b291e9efd406c55fdcebf113bdeb4ea14bfccf3f623e56 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 @@ -26,7 +26,7 @@ https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 @@ -47,12 +47,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/linux-64/libudev1-251-h166bdaf_0.tar.bz2#2fbd69c19564f7609d92ad887d11df98 +https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc -https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 @@ -84,7 +84,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 -https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 @@ -96,9 +96,9 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h7a41b64_0.tar.bz2#5635a2490d52955dc3192d901434c26d +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.3-h63197d8_1.tar.bz2#556ee34231614bb1d196e1c0ee693bb8 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.4-h63197d8_1.tar.bz2#d1b93417274d24b751a831c21169669a https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b @@ -111,7 +111,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bd https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d @@ -122,14 +122,14 @@ https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 -https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.0-pyhd8ed1ab_0.tar.bz2#da409b864dc21631820c81973df42587 +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.10.0-pyhd8ed1ab_0.tar.bz2#ee4d78b97e857cb8d845c8fa4c898433 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_0.tar.bz2#df71cc96499592e632046ee20c2c3bd0 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 @@ -137,7 +137,7 @@ https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.3-default_h3a83d3e_0.tar.bz2#736feeb04fac846a0e2cb5babec0b8c8 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.4-default_h3a83d3e_0.tar.bz2#d60d2d91b5991e364fd95cba5ec8ab96 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 @@ -155,7 +155,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.0-pyhd8ed1ab_0.tar.bz2#462466739c786f85f9ed555f87099fe1 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -169,35 +169,35 @@ https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a -https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 +https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 -https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda +https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_2.tar.bz2#6bb8063dd08f9724c18744b0e040cfe2 https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3.tar.bz2#785160da087cf1d70e989afbb761f01c -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_0.tar.bz2#2addd7ce0b4dffd5818d29f174c2d5d8 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.3-default_h2e3cab8_0.tar.bz2#09f01447ebab86a3e4f9dbf8cc72c94e +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.4-default_h2e3cab8_0.tar.bz2#248a013d3e7248c5c5f4ff69ffc307e8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py310h37cc914_3.tar.bz2#a12684b4b8bcc3c700a00d0365b0b9f7 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py310h53a5b5f_1.tar.bz2#0b7d4c8253f7191030adf34e2768c412 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310h454ad03_3.tar.bz2#eb354ff791f505b1d6f13f776359d88e -https://conda.anaconda.org/conda-forge/noarch/pip-22.3-pyhd8ed1ab_0.tar.bz2#6f4c6de9fed2a9bd8043283611b09587 +https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.3-py310h5764c6d_1.tar.bz2#9bfec73f587caed65c74e2219895f723 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0d2025b_11.tar.bz2#316026c5f80d8b5b9666e87b8614ac6c https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 @@ -206,12 +206,12 @@ https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_5.tar.b https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_1.tar.bz2#be4a201ac582c11d89ed7d15b3157cc3 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py310h5764c6d_0.tar.bz2#e972c5a1f472561cf4a91962cb01f4b4 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py310hff52083_1.tar.bz2#f41231b142c5adcd84dbf3c9a93a8a80 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py310hff52083_0.tar.bz2#02600c102a32274e20fc0604ef35af3c https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.2-py310h597c629_2.tar.bz2#e63caeba7d88f0bc7c4d6c04ba1599da -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.10.2-pyhd8ed1ab_0.tar.bz2#6f837aa0cbc910b39207fe5d97dfdf1e +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py310h597c629_0.tar.bz2#aa5aad596f9d5ef091364c4dad789094 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.0-pyhd8ed1ab_0.tar.bz2#1ab924fb3c44ed49796cc85eb1315cdc https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e @@ -223,24 +223,24 @@ https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_2.tar https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2.tar.bz2#61e2f2f7befaf45f47d1da449a9a0aca -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_1.tar.bz2#4140058701f7a3bad773f31496586256 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_2.tar.bz2#0582a434d03f6b06d5defbb142c96f4f https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5e49deb_1.tar.bz2#e5519576751b59d67164b965a4eb4406 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.3-py310hd8f1fbe_0.tar.bz2#2c6397f4fdfd0e2818c946adcff79c02 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5b266fc_2.tar.bz2#c4a3707d6a630facb6cf7ed8e0d37326 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.4-py310hd8f1fbe_0.tar.bz2#5c54faf5327d5d7ef993dd0f6f25e123 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_1.tar.bz2#f334893ef6856cb89786d2e0c6fb5a53 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.1-py310h8d5ebf3_1.tar.bz2#bc8d8dcad6b921b0996df46f0e7f120d +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py310h8d5ebf3_0.tar.bz2#da51ddb20c0f99d672eb756c3abf27e7 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_2.tar.bz2#0d815f1b2258d3d4c17cc80fd01e0f36 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310hcda3f9e_1.tar.bz2#5f9997662e6b2d3918149b65904d600c +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310h83f2385_3.tar.bz2#4ec35f7eebe4221c1c00fdd6540db4dc https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a @@ -252,10 +252,9 @@ https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c8 https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_2.tar.bz2#1e2c49215b17e6cf06edf100c9869ebe https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.1-py310hff52083_1.tar.bz2#51fbce233e5680a4258db5a16e2c1832 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py310hff52083_0.tar.bz2#aa78d12708912cd34135e6694a046ba0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a - diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 83fb2e641c..1cb20c2cce 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: a434c868ae6df31789fd61bd87bd1c2bc217fccf612eabfe62832dbf48266923 +# input_hash: 0adff1c1aa6a49a3d784fd9be06fb43f1a450cf0f1871db2d38d071ffa6efbcc @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 @@ -25,7 +25,7 @@ https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 @@ -46,12 +46,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/linux-64/libudev1-251-h166bdaf_0.tar.bz2#2fbd69c19564f7609d92ad887d11df98 +https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc -https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 @@ -83,7 +83,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 -https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 @@ -95,9 +95,9 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h7a41b64_0.tar.bz2#5635a2490d52955dc3192d901434c26d +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.3-h63197d8_1.tar.bz2#556ee34231614bb1d196e1c0ee693bb8 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.4-h63197d8_1.tar.bz2#d1b93417274d24b751a831c21169669a https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b @@ -108,16 +108,16 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_0.tar.bz2#df71cc96499592e632046ee20c2c3bd0 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.3-default_h3a83d3e_0.tar.bz2#736feeb04fac846a0e2cb5babec0b8c8 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.4-default_h3a83d3e_0.tar.bz2#d60d2d91b5991e364fd95cba5ec8ab96 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 @@ -139,18 +139,18 @@ https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 -https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.0-pyhd8ed1ab_0.tar.bz2#da409b864dc21631820c81973df42587 +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.10.0-pyhd8ed1ab_0.tar.bz2#ee4d78b97e857cb8d845c8fa4c898433 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_0.tar.bz2#2addd7ce0b4dffd5818d29f174c2d5d8 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.3-default_h2e3cab8_0.tar.bz2#09f01447ebab86a3e4f9dbf8cc72c94e +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.4-default_h2e3cab8_0.tar.bz2#248a013d3e7248c5c5f4ff69ffc307e8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 @@ -165,7 +165,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.0-pyhd8ed1ab_0.tar.bz2#462466739c786f85f9ed555f87099fe1 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -179,10 +179,10 @@ https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a -https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 +https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940 https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a -https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda +https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_2.tar.bz2#2276b1f4d1ede3f5f14cc7e4ae6f9a33 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 @@ -192,15 +192,15 @@ https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c7 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_2.tar.bz2#c342a370480791db83d5dd20f2d8899f -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py38h97ac3a3_3.tar.bz2#1fb632adee369c41a36de4426f49bad6 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar.bz2#0c469687a517052c0d581fc6e1a4189d https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py38h7042d01_1.tar.bz2#7fa0e9ed4e8a096e2f00b2426ebba0de https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h9eb91d8_3.tar.bz2#61dc7b3140b7b79b1985b53d52726d74 -https://conda.anaconda.org/conda-forge/noarch/pip-22.3-pyhd8ed1ab_0.tar.bz2#6f4c6de9fed2a9bd8043283611b09587 +https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.3-py38h0a891b7_1.tar.bz2#7bf4c28494eaa7de44b7dcdc55d45a89 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py38h0a891b7_0.tar.bz2#fe2ef279417faa1af0adf178de2032f7 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hce0a2d1_2.tar.bz2#be61a535f279bffdf7f449a654eaa19d https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 @@ -209,12 +209,12 @@ https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_5.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_1.tar.bz2#358beb228a53b5e1031862de3525d1d3 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py38h0a891b7_0.tar.bz2#44421904760e9f5ae2035193e04360f0 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py38h578d9bd_1.tar.bz2#cc87260724704301cc768386aa51e801 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py38h578d9bd_0.tar.bz2#fc6d74114bb0006224f252393bdacee6 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.2-py38h2b5fc30_2.tar.bz2#188b896c30c5b8f6825451fc0db8185e -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.10.2-pyhd8ed1ab_0.tar.bz2#6f837aa0cbc910b39207fe5d97dfdf1e +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py38h2b5fc30_0.tar.bz2#218274e4a04630a977b4da2b45eff593 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.0-pyhd8ed1ab_0.tar.bz2#1ab924fb3c44ed49796cc85eb1315cdc https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -225,36 +225,35 @@ https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2.tar.bz2#d30399a3c636c75cfd3460c92effa960 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_1.tar.bz2#7b69c1e959b9f2ef360d2927284042cc +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_2.tar.bz2#dfd81898f0c6e9ee0c22305da6aa443e https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hc9bb657_1.tar.bz2#1d08682ad092550a728bf45fce568f4e -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.3-py38hfa26641_0.tar.bz2#be1c99ff98790fe5179aa82f088ff505 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hafd38ec_2.tar.bz2#8df75c6a8c1deac4e99583ec624ff327 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.4-py38hfa26641_0.tar.bz2#a39a1d696fbe108d705c0ac584b937e3 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar.bz2#82b3797d08a43a101b645becbb938e65 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_1.tar.bz2#d21ffab1bfd053f55087695d5a78c449 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.1-py38hb021067_1.tar.bz2#c4d6a40038af11440cdb21170669a42b +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py38hb021067_0.tar.bz2#72422499195d8aded0dfd461c6e3e86f https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_2.tar.bz2#ad6437509a14f1e8e5b8a354f93f340c https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38hd475de4_1.tar.bz2#fee89fda76666a7cafa20ec721aa9d5e +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38hf6c3373_3.tar.bz2#1dc477fef9b0b1080af3e7c7ecb4aff7 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_1.tar.bz2#38d9029214399e4bfc378b62b0171bf0 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_2.tar.bz2#cfa725eff634872f90dcd5ebf8e8dc1a https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.1-py38h578d9bd_1.tar.bz2#cd81b06cc1d9f631b969bf59633c95b0 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py38h578d9bd_0.tar.bz2#e1a19f0d4686a701d4a4acce2b625acb https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a - diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index da844ecd29..bec758d380 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 52411479c54ecfa70b3c975c0d86da72b762d12fce2a094c134e69adca9f3f48 +# input_hash: 8a7c28a9309987aef78235650068290b0ecd5ac9e6ab522f4fad962cc9b1fc6c @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 @@ -26,7 +26,7 @@ https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 -https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.0-h27087fc_0.tar.bz2#a583d0bc9a85c48e8b07a588d1ac8a80 +https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 @@ -47,12 +47,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee -https://conda.anaconda.org/conda-forge/linux-64/libudev1-251-h166bdaf_0.tar.bz2#2fbd69c19564f7609d92ad887d11df98 +https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc -https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.2-h846660c_100.tar.bz2#36a36fe04b932d4b327e7e81c5c43696 +https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 @@ -84,7 +84,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 -https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.37-hc3806b6_1.tar.bz2#dfd26f27a9d5de96cec1d007b9aeb964 +https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 @@ -96,9 +96,9 @@ https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 -https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h7a41b64_0.tar.bz2#5635a2490d52955dc3192d901434c26d +https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.3-h63197d8_1.tar.bz2#556ee34231614bb1d196e1c0ee693bb8 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.4-h63197d8_1.tar.bz2#d1b93417274d24b751a831c21169669a https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b @@ -109,16 +109,16 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.36.0-h3371d22_4.tar.bz2#661e1ed5d92552785d9f8c781ce68685 +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_0.tar.bz2#df71cc96499592e632046ee20c2c3bd0 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.3-default_h3a83d3e_0.tar.bz2#736feeb04fac846a0e2cb5babec0b8c8 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.4-default_h3a83d3e_0.tar.bz2#d60d2d91b5991e364fd95cba5ec8ab96 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 @@ -140,18 +140,18 @@ https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb -https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.5-pyhd8ed1ab_0.tar.bz2#f15c3912378a07726093cc94d1e13251 -https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.0-pyhd8ed1ab_0.tar.bz2#da409b864dc21631820c81973df42587 +https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.10.0-pyhd8ed1ab_0.tar.bz2#ee4d78b97e857cb8d845c8fa4c898433 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_0.tar.bz2#2addd7ce0b4dffd5818d29f174c2d5d8 +https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.3-default_h2e3cab8_0.tar.bz2#09f01447ebab86a3e4f9dbf8cc72c94e +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.4-default_h2e3cab8_0.tar.bz2#248a013d3e7248c5c5f4ff69ffc307e8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 @@ -166,7 +166,7 @@ https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.0-pyhd8ed1ab_0.tar.bz2#462466739c786f85f9ed555f87099fe1 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -180,10 +180,10 @@ https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a -https://conda.anaconda.org/conda-forge/noarch/wheel-0.37.1-pyhd8ed1ab_0.tar.bz2#1ca02aaf78d9c70d9a81a3bed5752022 +https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940 https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb -https://conda.anaconda.org/conda-forge/noarch/babel-2.10.3-pyhd8ed1ab_0.tar.bz2#72f1c6d03109d7a70087bc1d029a8eda +https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_2.tar.bz2#fc70a133e8162f51e363cff3b6dc741c https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c @@ -193,15 +193,15 @@ https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c7 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_2.tar.bz2#c678e07e7862b3157fb9f6d908233ffa -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.3-py39h32b9844_3.tar.bz2#278dadf837f97b29c78439f468bfb563 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar.bz2#b035b507f55bb6a967d86d4b7e059437 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py39h3d75532_1.tar.bz2#ba4f1c0466d4ba7f53090c150fc88434 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hf3a2cdf_3.tar.bz2#2bd111c38da69056e5fe25a51b832eba -https://conda.anaconda.org/conda-forge/noarch/pip-22.3-pyhd8ed1ab_0.tar.bz2#6f4c6de9fed2a9bd8043283611b09587 +https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.3-py39hb9d737c_1.tar.bz2#0f50870227259af1edbb7b8bd04f2e07 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py39hb9d737c_0.tar.bz2#12184951da572828fb986b06ffb63eed https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h14a8356_2.tar.bz2#5d93c781338ff274a0b3dc3d901e19a6 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 @@ -210,12 +210,12 @@ https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_1.tar.bz2#8a7d309b08cff6386fe384aa10dd3748 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py39hb9d737c_0.tar.bz2#230d65004135bf312504a1bbcb0c7a08 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.5-py39hf3d152e_1.tar.bz2#f72ebd9b581b7d091a4d509a45ece280 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py39hf3d152e_0.tar.bz2#242b09f574d87f15a1d1479970037594 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.2-py39hd97740a_2.tar.bz2#2f615a211058c94af786b92cefbc3d5b -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.10.2-pyhd8ed1ab_0.tar.bz2#6f837aa0cbc910b39207fe5d97dfdf1e +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py39hd97740a_0.tar.bz2#be40f2e5698bd0497ddee8a63f8fb4a6 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.0-pyhd8ed1ab_0.tar.bz2#1ab924fb3c44ed49796cc85eb1315cdc https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -226,36 +226,35 @@ https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2 https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2.tar.bz2#234ad9828eca1caf0f2fdcb4a24ad816 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_1.tar.bz2#f26da935bc8e8ede63e075ffb5fa8e45 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_2.tar.bz2#0615ac8191c6ccf7d40860aff645f774 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h5b5020f_1.tar.bz2#9d969b4f06adee93e9cf4a62d8056cb6 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.3-py39h5a03fae_0.tar.bz2#85e7fc94451c6b86db6e2789fe92288d +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h76a96b7_2.tar.bz2#10bea68a9dd064b703743d210e679408 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.4-py39h5a03fae_0.tar.bz2#1d06fe7a83e1ac8340c7b483f6ee00e5 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar.bz2#0f11bcdf9669a5ae0f39efd8c830209a -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_1.tar.bz2#26e4d1f6a01fb44597431eacd6e0fe96 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.1-py39hf9fd14e_1.tar.bz2#f8d7b01f5cc777cbd671e86c530deba9 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py39hf9fd14e_0.tar.bz2#78ce32061e0be12deb8e0f11ffb76906 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_2.tar.bz2#306f1a018668f06a0bd89350a3f62c07 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39hfafebef_1.tar.bz2#f6777b70ffcae0820aa10027610875c8 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39h91bfd65_3.tar.bz2#7d10a2e14c08f383baae00e77bf890e5 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_1.tar.bz2#921f8a7c2a16d18d7168fdac88b2adfe https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_2.tar.bz2#384809c51fb2adc04773f6fa097cd051 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.1-py39hf3d152e_1.tar.bz2#b1df0bf44b0652eb428409d1c16c8ef4 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py39hf3d152e_0.tar.bz2#03225b4745d1dee7bb19d81e41c773a0 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a - diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index ae0090881d..87a16ba18a 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -49,3 +49,7 @@ dependencies: - sphinx-gallery >=0.11.0 - sphinx-panels - pydata-sphinx-theme = 0.8.1 + +# Temporary minimum pins. +# See https://github.com/SciTools/iris/pull/5051 + - graphviz >=6.0.0 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index c0ed574c92..e2d696d30c 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -49,3 +49,7 @@ dependencies: - sphinx-gallery >=0.11.0 - sphinx-panels - pydata-sphinx-theme = 0.8.1 + +# Temporary minimum pins. +# See https://github.com/SciTools/iris/pull/5051 + - graphviz >=6.0.0 diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index c79b0ede1d..500ec2f80f 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -49,3 +49,7 @@ dependencies: - sphinx-gallery >=0.11.0 - sphinx-panels - pydata-sphinx-theme = 0.8.1 + +# Temporary minimum pins. +# See https://github.com/SciTools/iris/pull/5051 + - graphviz >=6.0.0 From add1365f3803a4d64a2ac5dcc4c5ed70f01edca0 Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 16 Nov 2022 16:58:58 +0000 Subject: [PATCH 275/319] Fix handling of data in "nearest" trajectory interpolate (#5062) * Add trajectory interpolate tests for checking mask and dtype * Fix trajectory nearest interpolate data * Fixtures return values; whats new --- docs/src/whatsnew/latest.rst | 5 + lib/iris/analysis/trajectory.py | 16 +- .../results/trajectory/hybrid_height.cml | 4 +- .../trajectory/tri_polar_latitude_slice.cml | 4 +- .../analysis/trajectory/test_interpolate.py | 178 +++++++++++------- 5 files changed, 125 insertions(+), 82 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 19395869be..06443cd344 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -114,6 +114,11 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ and `@trexfeathers`_ (reviewer) fixed an issue which prevented uncompressed PP fields with additional trailing padded words in the field data to be loaded and saved. (:pull:`5058`) + +#. `@lbdreyer`_ and `@trexfeathers`_ (reviewer) fixed the handling of data when + regridding with :class:`~iris.analysis.UnstructuredNearest` or calling + :func:`~iris.analysis.trajectory.interpolate` such that the data type and mask is + preserved. (:issue:`4463`, :pull:`5062`) 💣 Incompatible Changes diff --git a/lib/iris/analysis/trajectory.py b/lib/iris/analysis/trajectory.py index 946ae1cb2c..87efc7112e 100644 --- a/lib/iris/analysis/trajectory.py +++ b/lib/iris/analysis/trajectory.py @@ -443,21 +443,7 @@ def interpolate(cube, sample_points, method=None): ] # Apply the fancy indexing to get all the result data points. - source_data = source_data[tuple(fancy_source_indices)] - - # "Fix" problems with missing datapoints producing odd values - # when copied from a masked into an unmasked array. - # TODO: proper masked data handling. - if np.ma.isMaskedArray(source_data): - # This is **not** proper mask handling, because we cannot produce a - # masked result, but it ensures we use a "filled" version of the - # input in this case. - source_data = source_data.filled() - new_cube.data[:] = source_data - # NOTE: we assign to "new_cube.data[:]" and *not* just "new_cube.data", - # because the existing code produces a default dtype from 'np.empty' - # instead of preserving the input dtype. - # TODO: maybe this should be fixed -- i.e. to preserve input dtype ?? + new_cube.data = source_data[tuple(fancy_source_indices)] # Fill in the empty squashed (non derived) coords. column_coords = [ diff --git a/lib/iris/tests/results/trajectory/hybrid_height.cml b/lib/iris/tests/results/trajectory/hybrid_height.cml index 972fa7b330..28e821b900 100644 --- a/lib/iris/tests/results/trajectory/hybrid_height.cml +++ b/lib/iris/tests/results/trajectory/hybrid_height.cml @@ -60,7 +60,7 @@ - + + diff --git a/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml b/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml index 750d597493..7b5bbfc086 100644 --- a/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml +++ b/lib/iris/tests/results/trajectory/tri_polar_latitude_slice.cml @@ -1,6 +1,6 @@ - + @@ -144,6 +144,6 @@ - + diff --git a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py b/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py index dad781ed74..f1b9711068 100644 --- a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py +++ b/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py @@ -12,7 +12,10 @@ # importing anything else. import iris.tests as tests # isort:skip +from collections import namedtuple + import numpy as np +import pytest from iris.analysis.trajectory import interpolate from iris.coords import AuxCoord, DimCoord @@ -38,15 +41,15 @@ def test_unknown_method(self): interpolate(cube, sample_point, method="linekar") -class TestNearest(tests.IrisTest): +class TestNearest: # Test interpolation with 'nearest' method. # This is basically a wrapper to the routine: # 'analysis._interpolate_private._nearest_neighbour_indices_ndcoords'. # That has its own test, so we don't test the basic calculation # exhaustively here. Instead we check the way it handles the source and # result cubes (especially coordinates). - - def setUp(self): + @pytest.fixture + def src_cube(self): cube = iris.tests.stock.simple_3d() # Actually, this cube *isn't* terribly realistic, as the lat+lon coords # have integer type, which in this case produces some peculiar results. @@ -54,46 +57,43 @@ def setUp(self): for coord_name in ("longitude", "latitude"): coord = cube.coord(coord_name) coord.points = coord.points.astype(float) - self.test_cube = cube + return cube + + @pytest.fixture + def single_point(self, src_cube): # Define coordinates for a single-point testcase. y_val, x_val = 0, -90 - # Work out cube indices of the testpoint. - self.single_point_iy = np.where( - cube.coord("latitude").points == y_val - )[0][0] - self.single_point_ix = np.where( - cube.coord("longitude").points == x_val - )[0][0] + # Use slightly-different values to test nearest-neighbour operation. - self.single_sample_point = [ + sample_point = [ ("latitude", [y_val + 19.23]), ("longitude", [x_val - 17.54]), ] - def test_single_point_same_cube(self): - # Check exact result matching for a single point. - cube = self.test_cube - result = interpolate(cube, self.single_sample_point, method="nearest") - # Check that the result is a single trajectory point, exactly equal to - # the expected part of the original data. - self.assertEqual(result.shape[-1], 1) - result = result[..., 0] - expected = cube[:, self.single_point_iy, self.single_point_ix] - self.assertEqual(result, expected) + # Work out cube indices of the testpoint. + single_point_iy = np.where(src_cube.coord("latitude").points == y_val)[ + 0 + ][0] + single_point_ix = np.where( + src_cube.coord("longitude").points == x_val + )[0][0] - def test_multi_point_same_cube(self): - # Check an exact result for multiple points. - cube = self.test_cube + point = namedtuple("point", "ix iy sample_point") + return point(single_point_ix, single_point_iy, sample_point) + + @pytest.fixture + def multi_sample_points(self): # Use latitude selection to recreate a whole row of the original cube. - sample_points = [ + return [ ("longitude", [-180, -90, 0, 90]), ("latitude", [0, 0, 0, 0]), ] - result = interpolate(cube, sample_points, method="nearest") + @pytest.fixture + def expected_multipoint_cube(self, src_cube): # The result should be identical to a single latitude section of the # original, but with modified coords (latitude has 4 repeated zeros). - expected = cube[:, 1, :] + expected = src_cube[:, 1, :] # Result 'longitude' is now an aux coord. co_x = expected.coord("longitude") expected.remove_coord(co_x) @@ -104,36 +104,86 @@ def test_multi_point_same_cube(self): [0, 0, 0, 0], standard_name="latitude", units="degrees" ) expected.add_aux_coord(co_y, 1) - self.assertEqual(result, expected) - def test_aux_coord_noninterpolation_dim(self): + return expected + + def test_single_point_same_cube(self, src_cube, single_point): + # Check exact result matching for a single point. + result = interpolate( + src_cube, single_point.sample_point, method="nearest" + ) + # Check that the result is a single trajectory point, exactly equal to + # the expected part of the original data. + assert result.shape[-1] == 1 + result = result[..., 0] + expected = src_cube[:, single_point.iy, single_point.ix] + assert result == expected + + def test_multi_point_same_cube( + self, src_cube, multi_sample_points, expected_multipoint_cube + ): + # Check an exact result for multiple points. + result = interpolate(src_cube, multi_sample_points, method="nearest") + assert result == expected_multipoint_cube + + def test_mask_preserved( + self, src_cube, multi_sample_points, expected_multipoint_cube + ): + mask = np.zeros_like(src_cube.data) + mask[:, :, 1] = 1 + src_cube.data = np.ma.array(src_cube.data, mask=mask) + + expected_multipoint_cube.data = np.ma.array( + expected_multipoint_cube.data, mask=mask[:, 0] + ) + + result = interpolate(src_cube, multi_sample_points, method="nearest") + assert result == expected_multipoint_cube + assert np.allclose( + result.data.mask, expected_multipoint_cube.data.mask + ) + + def test_dtype_preserved( + self, src_cube, multi_sample_points, expected_multipoint_cube + ): + src_cube.data = src_cube.data.astype(np.int16) + + result = interpolate(src_cube, multi_sample_points, method="nearest") + assert result == expected_multipoint_cube + assert np.allclose(result.data, expected_multipoint_cube.data) + assert result.data.dtype == np.int16 + + def test_aux_coord_noninterpolation_dim(self, src_cube, single_point): # Check exact result with an aux-coord mapped to an uninterpolated dim. - cube = self.test_cube - cube.add_aux_coord(DimCoord([17, 19], long_name="aux0"), 0) + src_cube.add_aux_coord(DimCoord([17, 19], long_name="aux0"), 0) # The result cube should exactly equal a single source point. - result = interpolate(cube, self.single_sample_point, method="nearest") - self.assertEqual(result.shape[-1], 1) + result = interpolate( + src_cube, single_point.sample_point, method="nearest" + ) + assert result.shape[-1] == 1 result = result[..., 0] - expected = cube[:, self.single_point_iy, self.single_point_ix] - self.assertEqual(result, expected) + expected = src_cube[:, single_point.iy, single_point.ix] + assert result == expected - def test_aux_coord_one_interp_dim(self): + def test_aux_coord_one_interp_dim(self, src_cube, single_point): # Check exact result with an aux-coord over one interpolation dims. - cube = self.test_cube - cube.add_aux_coord(AuxCoord([11, 12, 13, 14], long_name="aux_x"), 2) + src_cube.add_aux_coord( + AuxCoord([11, 12, 13, 14], long_name="aux_x"), 2 + ) # The result cube should exactly equal a single source point. - result = interpolate(cube, self.single_sample_point, method="nearest") - self.assertEqual(result.shape[-1], 1) + result = interpolate( + src_cube, single_point.sample_point, method="nearest" + ) + assert result.shape[-1] == 1 result = result[..., 0] - expected = cube[:, self.single_point_iy, self.single_point_ix] - self.assertEqual(result, expected) + expected = src_cube[:, single_point.iy, single_point.ix] + assert result == expected - def test_aux_coord_both_interp_dims(self): + def test_aux_coord_both_interp_dims(self, src_cube, single_point): # Check exact result with an aux-coord over both interpolation dims. - cube = self.test_cube - cube.add_aux_coord( + src_cube.add_aux_coord( AuxCoord( [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]], long_name="aux_xy", @@ -142,17 +192,18 @@ def test_aux_coord_both_interp_dims(self): ) # The result cube should exactly equal a single source point. - result = interpolate(cube, self.single_sample_point, method="nearest") - self.assertEqual(result.shape[-1], 1) + result = interpolate( + src_cube, single_point.sample_point, method="nearest" + ) + assert result.shape[-1] == 1 result = result[..., 0] - expected = cube[:, self.single_point_iy, self.single_point_ix] - self.assertEqual(result, expected) + expected = src_cube[:, single_point.iy, single_point.ix] + assert result == expected - def test_aux_coord_fail_mixed_dims(self): + def test_aux_coord_fail_mixed_dims(self, src_cube, single_point): # Check behaviour with an aux-coord mapped over both interpolation and # non-interpolation dims : not supported. - cube = self.test_cube - cube.add_aux_coord( + src_cube.add_aux_coord( AuxCoord( [[111, 112, 113, 114], [211, 212, 213, 214]], long_name="aux_0x", @@ -163,22 +214,23 @@ def test_aux_coord_fail_mixed_dims(self): "Coord aux_0x at one x-y position has the shape.*" "instead of being a single point" ) - with self.assertRaisesRegex(ValueError, msg): - interpolate(cube, self.single_sample_point, method="nearest") + with pytest.raises(ValueError, match=msg): + interpolate(src_cube, single_point.sample_point, method="nearest") - def test_metadata(self): + def test_metadata(self, src_cube, single_point): # Check exact result matching for a single point, with additional # attributes and cell-methods. - cube = self.test_cube - cube.attributes["ODD_ATTR"] = "string-value-example" - cube.add_cell_method(iris.coords.CellMethod("mean", "area")) - result = interpolate(cube, self.single_sample_point, method="nearest") + src_cube.attributes["ODD_ATTR"] = "string-value-example" + src_cube.add_cell_method(iris.coords.CellMethod("mean", "area")) + result = interpolate( + src_cube, single_point.sample_point, method="nearest" + ) # Check that the result is a single trajectory point, exactly equal to # the expected part of the original data. - self.assertEqual(result.shape[-1], 1) + assert result.shape[-1] == 1 result = result[..., 0] - expected = cube[:, self.single_point_iy, self.single_point_ix] - self.assertEqual(result, expected) + expected = src_cube[:, single_point.iy, single_point.ix] + assert result == expected class TestLinear(tests.IrisTest): From 6443ac5bc4a7c83c529877e21c64d5faf6d5bb31 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 16 Nov 2022 17:04:02 +0000 Subject: [PATCH 276/319] Make `iris.pandas.as_data_frame()` n-dimensional behaviour opt-in (#5059) * Restore original as_data_frame behaviour, with new behaviour as opt-in. * Document and test opt-in nature of mulit-d as_data_frame behaviour. * Test fixes. * Update for Pandas 1.5 deprecation. * More explicit What's New. * Fix int64 test. * added link to the docs archive. (#5064) * added link to the docs archive. * added whatsnew Co-authored-by: tkknight <2108488+tkknight@users.noreply.github.com> --- docs/src/common_links.inc | 2 +- docs/src/index.rst | 8 +- docs/src/whatsnew/latest.rst | 12 +- docs/src/why_iris.rst | 3 +- lib/iris/__init__.py | 27 ++- lib/iris/pandas.py | 126 +++++++++---- lib/iris/tests/test_pandas.py | 325 +++++++++++++++++++++++++++++++++- 7 files changed, 442 insertions(+), 61 deletions(-) diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index ec7e1efd6d..0b1017a7d8 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -21,7 +21,7 @@ .. _isort: https://pycqa.github.io/isort/ .. _issue: https://github.com/SciTools/iris/issues .. _issues: https://github.com/SciTools/iris/issues -.. _legacy documentation: https://scitools.org.uk/iris/docs/v2.4.0/ +.. _legacy documentation: https://github.com/SciTools/scitools.org.uk/tree/master/iris/docs/archive .. _matplotlib: https://matplotlib.org/stable/ .. _napolean: https://sphinxcontrib-napoleon.readthedocs.io/en/latest/sphinxcontrib.napoleon.html .. _nox: https://nox.thea.codes/en/stable/ diff --git a/docs/src/index.rst b/docs/src/index.rst index b9f7faaa03..c5d654ed31 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -88,6 +88,8 @@ Icons made by `FreePik `_ from `Flaticon `_ +.. _iris_support: + Support ~~~~~~~ @@ -101,7 +103,11 @@ The legacy support resources: * `Users Google Group `_ * `Developers Google Group `_ -* `Legacy Documentation`_ (Iris 2.4 or earlier) +* `Legacy Documentation`_ (Iris 2.4 or earlier). This is an archive of zip + files of past documentation. You can download, unzip and view the + documentation locally (index.html). There may be some incorrect rendering + and older javascvript (.js) files may show a warning when uncompressing, in + which case we suggest you use a different unzip tool. .. toctree:: diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d7f340737f..8ea27d7667 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -35,10 +35,12 @@ This document explains the changes made to Iris for this release non-existing paths, and added expansion functionality to :func:`~iris.io.save`. (:issue:`4772`, :pull:`4913`) -#. `@hsteptoe`_ and `@trexfeathers`_ (reviewer) added :func:`iris.pandas.as_data_frame`, - which provides improved conversion of :class:`~iris.cube.Cube`\s to - :class:`~pandas.DataFrame`\s. This includes better handling of multiple - :class:`~iris.cube.Cube` dimensions, auxiliary coordinates and attribute information. +#. `@hsteptoe`_ and `@trexfeathers`_ improved + :func:`iris.pandas.as_data_frame`\'s conversion of :class:`~iris.cube.Cube`\s to + :class:`~pandas.DataFrame`\s. This includes better handling of multiple + :class:`~iris.cube.Cube` dimensions, auxiliary coordinates and attribute + information. **Note:** the improvements are opt-in, by setting the + :obj:`iris.FUTURE.pandas_ndim` flag (see :class:`iris.Future` for more). (:issue:`4526`, :pull:`4669`) @@ -89,6 +91,8 @@ This document explains the changes made to Iris for this release #. N/A +#. `@tkknight`_ updated the links for the Iris documentation to v2.4 and + earlier to point to the archive of zip files instead. (:pull:`5064`) 💼 Internal =========== diff --git a/docs/src/why_iris.rst b/docs/src/why_iris.rst index 63a515f68e..82b791b4bd 100644 --- a/docs/src/why_iris.rst +++ b/docs/src/why_iris.rst @@ -40,5 +40,4 @@ Interoperability with packages from the wider scientific Python ecosystem comes from Iris' use of standard NumPy/dask arrays as its underlying data storage. Iris is part of SciTools, for more information see https://scitools.org.uk/. -For **Iris 2.4** and earlier documentation please see the -:link-badge:`https://scitools.org.uk/iris/docs/v2.4.0/,"legacy documentation",cls=badge-info text-white`. +For **Iris 2.4** and earlier documentation please see :ref:`iris_support`. \ No newline at end of file diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 70dcaa60de..896b850541 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -147,21 +147,21 @@ def __init__(self, datum_support=False, pandas_ndim=False): To adjust the values simply update the relevant attribute from within your code. For example:: + # example_future_flag is a fictional example. iris.FUTURE.example_future_flag = False If Iris code is executed with multiple threads, note the values of these options are thread-specific. - .. note:: - - iris.FUTURE.example_future_flag does not exist. It is provided - as an example. - - .. todo:: - - Document the ``pandas_ndim`` flag once iris#4669 is merged - can - add cross-referencing documentation both here and in - iris.pandas.as_dataframe(). + Parameters + ---------- + datum_support : bool, default=False + Opts in to loading coordinate system datum information from NetCDF + files into :class:`~iris.coord_systems.CoordSystem`\\ s, wherever + this information is present. + pandas_ndim : bool, default=False + See :func:`iris.pandas.as_data_frame` for details - opts in to the + newer n-dimensional behaviour. """ # The flag 'example_future_flag' is provided as a reference for the @@ -218,14 +218,11 @@ def context(self, **kwargs): statement, the previous state is restored. For example:: + + # example_future_flag is a fictional example. with iris.FUTURE.context(example_future_flag=False): # ... code that expects some past behaviour - .. note:: - - iris.FUTURE.example_future_flag does not exist and is - provided only as an example. - """ # Save the current context current_state = self.__dict__.copy() diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index 1bf9509d1b..faa250285e 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -378,7 +378,10 @@ def as_cubes( ) raise ValueError(message) - if not pandas_index.is_monotonic: + if not ( + pandas_index.is_monotonic_increasing + or pandas_index.is_monotonic_decreasing + ): # Need monotonic index for use in DimCoord(s). # This function doesn't sort_index itself since that breaks the # option to return a data view instead of a copy. @@ -627,7 +630,7 @@ def as_data_frame( add_ancillary_variables=False, ): """ - Convert a 2D cube to a Pandas DataFrame. + Convert a :class:`~iris.cube.Cube` to a :class:`pandas.DataFrame`. :attr:`~iris.cube.Cube.dim_coords` and :attr:`~iris.cube.Cube.data` are flattened into a long-style :class:`~pandas.DataFrame`. Other @@ -658,6 +661,29 @@ def as_data_frame( A :class:`~pandas.DataFrame` with :class:`~iris.cube.Cube` dimensions forming a :class:`~pandas.MultiIndex` + Warnings + -------- + #. This documentation is for the new ``as_data_frame()`` behaviour, which + is **currently opt-in** to preserve backwards compatibility. The default + legacy behaviour is documented in pre-``v3.4`` documentation (summary: + limited to 2-dimensional :class:`~iris.cube.Cube`\\ s, with only the + :attr:`~iris.cube.Cube.data` and :attr:`~iris.cube.Cube.dim_coords` + being added). The legacy behaviour will be removed in a future version + of Iris, so please opt-in to the new behaviour at your earliest + convenience, via :class:`iris.Future`: + + >>> iris.FUTURE.pandas_ndim = True + + **Breaking change:** to enable the improvements, the new opt-in + behaviour flattens multi-dimensional data into a single + :class:`~pandas.DataFrame` column (the legacy behaviour preserves 2 + dimensions via rows and columns). + + | + + #. Where the :class:`~iris.cube.Cube` contains masked values, these become + :data:`numpy.nan` in the returned :class:`~pandas.DataFrame`. + Notes ----- Dask ``DataFrame``\\s are not supported. @@ -669,11 +695,6 @@ def as_data_frame( :class:`~iris.cube.Cube` data `dtype` is preserved. - Warnings - -------- - Where the :class:`~iris.cube.Cube` contains masked values, these become - :data:`numpy.nan` in the returned :class:`~pandas.DataFrame`. - Examples -------- >>> import iris @@ -817,37 +838,72 @@ def merge_metadata(meta_var_list): ) return data_frame - # Checks - if not isinstance(cube, iris.cube.Cube): - raise TypeError( - f"Expected input to be iris.cube.Cube instance, got: {type(cube)}" + if iris.FUTURE.pandas_ndim: + # Checks + if not isinstance(cube, iris.cube.Cube): + raise TypeError( + f"Expected input to be iris.cube.Cube instance, got: {type(cube)}" + ) + if copy: + data = cube.data.copy() + else: + data = cube.data + if ma.isMaskedArray(data): + if not copy: + raise ValueError("Masked arrays must always be copied.") + data = data.astype("f").filled(np.nan) + + # Extract dim coord information: separate lists for dim names and dim values + coord_names, coords = _make_dim_coord_list(cube) + # Make base DataFrame + index = pandas.MultiIndex.from_product(coords, names=coord_names) + data_frame = pandas.DataFrame( + data.ravel(), columns=[cube.name()], index=index ) - if copy: - data = cube.data.copy() + + if add_aux_coords: + data_frame = merge_metadata(_make_aux_coord_list(cube)) + if add_ancillary_variables: + data_frame = merge_metadata(_make_ancillary_variables_list(cube)) + if add_cell_measures: + data_frame = merge_metadata(_make_cell_measures_list(cube)) + + if copy: + result = data_frame.reorder_levels(coord_names).sort_index() + else: + data_frame.reorder_levels(coord_names).sort_index(inplace=True) + result = data_frame + else: + message = ( + "You are using legacy 2-dimensional behaviour in" + "'iris.pandas.as_data_frame()'. This will be removed in a future" + "version of Iris. Please opt-in to the improved " + "n-dimensional behaviour at your earliest convenience by setting: " + "'iris.FUTURE.pandas_ndim = True'. More info is in the " + "documentation." + ) + warnings.warn(message, FutureWarning) + + # The legacy behaviour. data = cube.data - if ma.isMaskedArray(data): + if ma.isMaskedArray(data): + if not copy: + raise ValueError("Masked arrays must always be copied.") + data = data.astype("f").filled(np.nan) + elif copy: + data = data.copy() + + index = columns = None + if cube.coords(dimensions=[0]): + index = _as_pandas_coord(cube.coord(dimensions=[0])) + if cube.coords(dimensions=[1]): + columns = _as_pandas_coord(cube.coord(dimensions=[1])) + + data_frame = pandas.DataFrame(data, index, columns) if not copy: - raise ValueError("Masked arrays must always be copied.") - data = data.astype("f").filled(np.nan) - - # Extract dim coord information: separate lists for dim names and dim values - coord_names, coords = _make_dim_coord_list(cube) - # Make base DataFrame - index = pandas.MultiIndex.from_product(coords, names=coord_names) - data_frame = pandas.DataFrame( - data.ravel(), columns=[cube.name()], index=index - ) + _assert_shared(data, data_frame) - if add_aux_coords: - data_frame = merge_metadata(_make_aux_coord_list(cube)) - if add_ancillary_variables: - data_frame = merge_metadata(_make_ancillary_variables_list(cube)) - if add_cell_measures: - data_frame = merge_metadata(_make_cell_measures_list(cube)) + result = data_frame - if copy: - return data_frame.reorder_levels(coord_names).sort_index() - else: - data_frame.reorder_levels(coord_names).sort_index(inplace=True) - return data_frame + return result diff --git a/lib/iris/tests/test_pandas.py b/lib/iris/tests/test_pandas.py index 4841108aa2..60a271c53b 100644 --- a/lib/iris/tests/test_pandas.py +++ b/lib/iris/tests/test_pandas.py @@ -11,6 +11,7 @@ import copy import datetime from termios import IXOFF # noqa: F401 +import warnings import cf_units import cftime @@ -42,10 +43,306 @@ import iris.pandas +@pytest.fixture +def activate_pandas_ndim(): + iris.FUTURE.pandas_ndim = True + yield None + iris.FUTURE.pandas_ndim = False + + @skip_pandas +@pytest.mark.filterwarnings( + "ignore:.*as_series has been deprecated.*:iris._deprecation.IrisDeprecation" +) +class TestAsSeries(tests.IrisTest): + """Test conversion of 1D cubes to Pandas using as_series()""" + + def test_no_dim_coord(self): + cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") + series = iris.pandas.as_series(cube) + expected_index = np.array([0, 1, 2, 3, 4]) + self.assertArrayEqual(series, cube.data) + self.assertArrayEqual(series.index, expected_index) + + def test_simple(self): + cube = Cube(np.array([0, 1, 2, 3, 4.4]), long_name="foo") + dim_coord = DimCoord([5, 6, 7, 8, 9], long_name="bar") + cube.add_dim_coord(dim_coord, 0) + expected_index = np.array([5, 6, 7, 8, 9]) + series = iris.pandas.as_series(cube) + self.assertArrayEqual(series, cube.data) + self.assertArrayEqual(series.index, expected_index) + + def test_masked(self): + data = np.ma.MaskedArray([0, 1, 2, 3, 4.4], mask=[0, 1, 0, 1, 0]) + cube = Cube(data, long_name="foo") + series = iris.pandas.as_series(cube) + self.assertArrayEqual(series, cube.data.astype("f").filled(np.nan)) + + def test_time_standard(self): + cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") + time_coord = DimCoord( + [0, 100.1, 200.2, 300.3, 400.4], + long_name="time", + units="days since 2000-01-01 00:00", + ) + cube.add_dim_coord(time_coord, 0) + expected_index = [ + datetime.datetime(2000, 1, 1, 0, 0), + datetime.datetime(2000, 4, 10, 2, 24), + datetime.datetime(2000, 7, 19, 4, 48), + datetime.datetime(2000, 10, 27, 7, 12), + datetime.datetime(2001, 2, 4, 9, 36), + ] + series = iris.pandas.as_series(cube) + self.assertArrayEqual(series, cube.data) + assert list(series.index) == expected_index + + def test_time_360(self): + cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") + time_unit = cf_units.Unit( + "days since 2000-01-01 00:00", calendar=cf_units.CALENDAR_360_DAY + ) + time_coord = DimCoord( + [0, 100.1, 200.2, 300.3, 400.4], long_name="time", units=time_unit + ) + cube.add_dim_coord(time_coord, 0) + expected_index = [ + cftime.Datetime360Day(2000, 1, 1, 0, 0), + cftime.Datetime360Day(2000, 4, 11, 2, 24), + cftime.Datetime360Day(2000, 7, 21, 4, 48), + cftime.Datetime360Day(2000, 11, 1, 7, 12), + cftime.Datetime360Day(2001, 2, 11, 9, 36), + ] + + series = iris.pandas.as_series(cube) + self.assertArrayEqual(series, cube.data) + self.assertArrayEqual(series.index, expected_index) + + def test_copy_true(self): + cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") + series = iris.pandas.as_series(cube) + series[0] = 99 + assert cube.data[0] == 0 + + def test_copy_int32_false(self): + cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int32), long_name="foo") + series = iris.pandas.as_series(cube, copy=False) + series[0] = 99 + assert cube.data[0] == 99 + + def test_copy_int64_false(self): + cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int64), long_name="foo") + series = iris.pandas.as_series(cube, copy=False) + series[0] = 99 + assert cube.data[0] == 99 + + def test_copy_float_false(self): + cube = Cube(np.array([0, 1, 2, 3.3, 4]), long_name="foo") + series = iris.pandas.as_series(cube, copy=False) + series[0] = 99 + assert cube.data[0] == 99 + + def test_copy_masked_true(self): + data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) + cube = Cube(data, long_name="foo") + series = iris.pandas.as_series(cube) + series[0] = 99 + assert cube.data[0] == 0 + + def test_copy_masked_false(self): + data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) + cube = Cube(data, long_name="foo") + with pytest.raises(ValueError): + _ = iris.pandas.as_series(cube, copy=False) + + +@skip_pandas +@pytest.mark.filterwarnings( + "ignore:You are using legacy 2-dimensional behaviour.*:FutureWarning" +) class TestAsDataFrame(tests.IrisTest): """Test conversion of 2D cubes to Pandas using as_data_frame()""" + def test_no_dim_coords(self): + cube = Cube( + np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" + ) + expected_index = [0, 1] + expected_columns = [0, 1, 2, 3, 4] + data_frame = iris.pandas.as_data_frame(cube) + self.assertArrayEqual(data_frame, cube.data) + self.assertArrayEqual(data_frame.index, expected_index) + self.assertArrayEqual(data_frame.columns, expected_columns) + + def test_no_x_coord(self): + cube = Cube( + np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" + ) + y_coord = DimCoord([10, 11], long_name="bar") + cube.add_dim_coord(y_coord, 0) + expected_index = [10, 11] + expected_columns = [0, 1, 2, 3, 4] + data_frame = iris.pandas.as_data_frame(cube) + self.assertArrayEqual(data_frame, cube.data) + self.assertArrayEqual(data_frame.index, expected_index) + self.assertArrayEqual(data_frame.columns, expected_columns) + + def test_no_y_coord(self): + cube = Cube( + np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" + ) + x_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar") + cube.add_dim_coord(x_coord, 1) + expected_index = [0, 1] + expected_columns = [10, 11, 12, 13, 14] + data_frame = iris.pandas.as_data_frame(cube) + self.assertArrayEqual(data_frame, cube.data) + self.assertArrayEqual(data_frame.index, expected_index) + self.assertArrayEqual(data_frame.columns, expected_columns) + + def test_simple(self): + cube = Cube( + np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" + ) + x_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar") + y_coord = DimCoord([15, 16], long_name="milk") + cube.add_dim_coord(x_coord, 1) + cube.add_dim_coord(y_coord, 0) + expected_index = [15, 16] + expected_columns = [10, 11, 12, 13, 14] + data_frame = iris.pandas.as_data_frame(cube) + self.assertArrayEqual(data_frame, cube.data) + self.assertArrayEqual(data_frame.index, expected_index) + self.assertArrayEqual(data_frame.columns, expected_columns) + + def test_masked(self): + data = np.ma.MaskedArray( + [[0, 1, 2, 3, 4.4], [5, 6, 7, 8, 9]], + mask=[[0, 1, 0, 1, 0], [1, 0, 1, 0, 1]], + ) + cube = Cube(data, long_name="foo") + expected_index = [0, 1] + expected_columns = [0, 1, 2, 3, 4] + data_frame = iris.pandas.as_data_frame(cube) + self.assertArrayEqual(data_frame, cube.data.astype("f").filled(np.nan)) + self.assertArrayEqual(data_frame.index, expected_index) + self.assertArrayEqual(data_frame.columns, expected_columns) + + def test_time_standard(self): + cube = Cube( + np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="ts" + ) + day_offsets = [0, 100.1, 200.2, 300.3, 400.4] + time_coord = DimCoord( + day_offsets, long_name="time", units="days since 2000-01-01 00:00" + ) + cube.add_dim_coord(time_coord, 1) + data_frame = iris.pandas.as_data_frame(cube) + self.assertArrayEqual(data_frame, cube.data) + nanoseconds_per_day = 24 * 60 * 60 * 1000000000 + days_to_2000 = 365 * 30 + 7 + # pandas Timestamp class cannot handle floats in pandas Date: Wed, 16 Nov 2022 18:24:06 +0000 Subject: [PATCH 277/319] Clarify Laziness in Iris Functions (#5066) * cube and io lazy data notes added * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Added comments within analysis, as well as palette and iterate, and what's new * fixed docstrings as requested in @trexfeathers review * reverted cube.py for time being * fixed flake8 issue Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 3 ++ lib/iris/analysis/__init__.py | 34 +++++++++++------- lib/iris/analysis/_grid_angles.py | 5 +++ lib/iris/analysis/calculus.py | 23 ++++++++++++ lib/iris/analysis/cartography.py | 27 ++++++++++++++ lib/iris/analysis/geometry.py | 5 +++ lib/iris/analysis/maths.py | 58 +++++++++++++++++++++++++++++++ lib/iris/analysis/trajectory.py | 4 +++ lib/iris/io/__init__.py | 10 ++++++ lib/iris/iterate.py | 4 +++ lib/iris/palette.py | 5 +++ 11 files changed, 166 insertions(+), 12 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 06443cd344..df71c2dd3f 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -207,6 +207,9 @@ This document explains the changes made to Iris for this release #. `@tkknight`_ updated the links for the Iris documentation to v2.4 and earlier to point to the archive of zip files instead. (:pull:`5064`) +#. `@Esadek-MO`_ added notes at the bottom of functions to + to clarify if the function preserves laziness or not. (:pull:`5066`) + 💼 Internal =========== diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 85b49ece4e..c0f9b5b0f8 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -1778,7 +1778,7 @@ def interp_order(length): .. seealso:: The :func:`~iris.analysis.PROPORTION` aggregator. -This aggregator handles masked data. +This aggregator handles masked data and lazy data. """ @@ -1808,7 +1808,7 @@ def interp_order(length): result = precip_cube.collapsed('time', iris.analysis.MAX_RUN, function=lambda values: values > 10) -This aggregator handles masked data, which it treats as interrupting a run. +This aggregator handles masked data, which it treats as interrupting a run, and lazy data. """ MAX_RUN.name = lambda: "max_run" @@ -1826,7 +1826,7 @@ def interp_order(length): result = cube.collapsed('longitude', iris.analysis.GMEAN) -This aggregator handles masked data. +This aggregator handles masked data, but NOT lazy data. """ @@ -1848,7 +1848,7 @@ def interp_order(length): The harmonic mean is only valid if all data values are greater than zero. -This aggregator handles masked data. +This aggregator handles masked data, but NOT lazy data. """ @@ -1914,7 +1914,7 @@ def interp_order(length): result = cube.collapsed('longitude', iris.analysis.MEDIAN) -This aggregator handles masked data. +This aggregator handles masked data, but NOT lazy data. """ @@ -1933,7 +1933,7 @@ def interp_order(length): result = cube.collapsed('longitude', iris.analysis.MIN) -This aggregator handles masked data. +This aggregator handles masked data and lazy data. """ @@ -1952,7 +1952,7 @@ def interp_order(length): result = cube.collapsed('longitude', iris.analysis.MAX) -This aggregator handles masked data. +This aggregator handles masked data and lazy data. """ @@ -1978,7 +1978,7 @@ def interp_order(length): result = cube.collapsed('time', iris.analysis.PEAK) -This aggregator handles masked data. +This aggregator handles masked data but NOT lazy data. """ @@ -2058,7 +2058,7 @@ def interp_order(length): .. seealso:: The :func:`~iris.analysis.COUNT` aggregator. -This aggregator handles masked data. +This aggregator handles masked data, but NOT lazy data. """ @@ -2084,7 +2084,7 @@ def interp_order(length): result = cube.collapsed('longitude', iris.analysis.RMS) -This aggregator handles masked data. +This aggregator handles masked data and lazy data. """ @@ -2157,7 +2157,7 @@ def interp_order(length): result = cube.rolling_window('time', iris.analysis.SUM, len(weights), weights=weights) -This aggregator handles masked data. +This aggregator handles masked data and lazy data. """ @@ -2194,7 +2194,7 @@ def interp_order(length): Lazy operation is supported, via :func:`dask.array.var`. -This aggregator handles masked data. +This aggregator handles masked data and lazy data. """ @@ -2226,6 +2226,11 @@ def interp_order(length): :func:`scipy.interpolate.interp1d` Defaults to "linear", which is equivalent to alphap=0.5, betap=0.5 in `iris.analysis.PERCENTILE` +Notes +------ +This function does not maintain laziness when called; it realises data. +See more at :doc:`/userguide/real_and_lazy_data`. + """ @@ -2619,6 +2624,11 @@ def clear_phenomenon_identity(cube): Helper function to clear the standard_name, attributes, and cell_methods of a cube. + + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ cube.rename(None) cube.attributes.clear() diff --git a/lib/iris/analysis/_grid_angles.py b/lib/iris/analysis/_grid_angles.py index 0b52f54568..4cb449ae51 100644 --- a/lib/iris/analysis/_grid_angles.py +++ b/lib/iris/analysis/_grid_angles.py @@ -449,6 +449,11 @@ def rotate_grid_vectors( Vector magnitudes will always be the same as the inputs. + .. note:: + + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ u_out, v_out = (cube.copy() for cube in (u_cube, v_cube)) if not grid_angles_cube: diff --git a/lib/iris/analysis/calculus.py b/lib/iris/analysis/calculus.py index 4630f47967..c530dbd216 100644 --- a/lib/iris/analysis/calculus.py +++ b/lib/iris/analysis/calculus.py @@ -147,6 +147,12 @@ def cube_delta(cube, coord): .. note:: Missing data support not yet implemented. + .. note:: + + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. + + """ # handle the case where a user passes a coordinate name if isinstance(coord, str): @@ -251,6 +257,11 @@ def differentiate(cube, coord_to_differentiate): .. note:: Spherical differentiation does not occur in this routine. + .. note:: + + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ # Get the delta cube in the required differential direction. # This operation results in a copy of the original cube. @@ -532,6 +543,12 @@ def curl(i_cube, j_cube, k_cube=None): where phi is longitude, theta is latitude. + .. note:: + + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. + + """ # Get the vector quantity names. # (i.e. ['easterly', 'northerly', 'vertical']) @@ -741,6 +758,12 @@ def spatial_vectors_with_phenom_name(i_cube, j_cube, k_cube=None): #doctest: +SKIP (['u', 'v', 'w'], 'wind') + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + + """ directional_names = ( ("u", "v", "w"), diff --git a/lib/iris/analysis/cartography.py b/lib/iris/analysis/cartography.py index 8dbad9c4e9..a8e90a63ad 100644 --- a/lib/iris/analysis/cartography.py +++ b/lib/iris/analysis/cartography.py @@ -66,6 +66,10 @@ def wrap_lons(lons, base, period): >>> print(wrap_lons(np.array([185, 30, -200, 75]), -180, 360)) [-175. 30. 160. 75.] + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ # It is important to use 64bit floating precision when changing a floats # numbers range. @@ -271,6 +275,10 @@ def get_xy_grids(cube): x, y = get_xy_grids(cube) + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ x_coord, y_coord = cube.coord(axis="X"), cube.coord(axis="Y") @@ -299,6 +307,11 @@ def get_xy_contiguous_bounded_grids(cube): xs, ys = get_xy_contiguous_bounded_grids(cube) + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ x_coord, y_coord = cube.coord(axis="X"), cube.coord(axis="Y") @@ -498,6 +511,10 @@ def cosine_latitude_weights(cube): cube = iris.load_cube(iris.sample_data_path('air_temp.pp')) weights = np.sqrt(cosine_latitude_weights(cube)) + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ # Find all latitude coordinates, we want one and only one. lat_coords = [ @@ -601,6 +618,11 @@ def project(cube, target_proj, nx=None, ny=None): resulting nearest neighbour values. If masked, the value in the resulting cube is set to 0. + .. note:: + + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. + .. warning:: This function uses a nearest neighbour approach rather than any form @@ -1075,6 +1097,11 @@ def rotate_winds(u_cube, v_cube, target_cs): The names of the output cubes are those of the inputs, prefixed with 'transformed\_' (e.g. 'transformed_x_wind'). + .. note:: + + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. + .. warning:: Conversion between rotated-pole and non-rotated systems can be diff --git a/lib/iris/analysis/geometry.py b/lib/iris/analysis/geometry.py index a412a26ebc..b246b518d4 100644 --- a/lib/iris/analysis/geometry.py +++ b/lib/iris/analysis/geometry.py @@ -160,6 +160,11 @@ def geometry_area_weights(cube, geometry, normalize=False): calculation might be wrong. In this case, a UserWarning will be issued. + .. note:: + + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. + Args: * cube (:class:`iris.cube.Cube`): diff --git a/lib/iris/analysis/maths.py b/lib/iris/analysis/maths.py index 468847bca2..09a02ad51c 100644 --- a/lib/iris/analysis/maths.py +++ b/lib/iris/analysis/maths.py @@ -115,6 +115,11 @@ def abs(cube, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ _assert_is_cube(cube) new_dtype = _output_dtype(np.abs, cube.dtype, in_place=in_place) @@ -160,6 +165,11 @@ def intersection_of_cubes(cube, other_cube): intersections = cubes.extract_overlapping(coords) cube1, cube2 = (intersections[0], intersections[1]) + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ wmsg = ( "iris.analysis.maths.intersection_of_cubes has been deprecated and will " @@ -243,6 +253,11 @@ def add(cube, other, dim=None, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ _assert_is_cube(cube) new_dtype = _output_dtype( @@ -292,6 +307,11 @@ def subtract(cube, other, dim=None, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ _assert_is_cube(cube) new_dtype = _output_dtype( @@ -383,6 +403,10 @@ def multiply(cube, other, dim=None, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ _assert_is_cube(cube) @@ -456,6 +480,10 @@ def divide(cube, other, dim=None, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ _assert_is_cube(cube) @@ -519,6 +547,10 @@ def exponentiate(cube, exponent, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ _assert_is_cube(cube) new_dtype = _output_dtype( @@ -567,6 +599,11 @@ def exp(cube, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ _assert_is_cube(cube) new_dtype = _output_dtype(np.exp, cube.dtype, in_place=in_place) @@ -593,6 +630,11 @@ def log(cube, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ _assert_is_cube(cube) new_dtype = _output_dtype(np.log, cube.dtype, in_place=in_place) @@ -623,6 +665,11 @@ def log2(cube, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ _assert_is_cube(cube) new_dtype = _output_dtype(np.log2, cube.dtype, in_place=in_place) @@ -649,6 +696,11 @@ def log10(cube, in_place=False): Returns: An instance of :class:`iris.cube.Cube`. + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ _assert_is_cube(cube) new_dtype = _output_dtype(np.log10, cube.dtype, in_place=in_place) @@ -703,6 +755,12 @@ def apply_ufunc( cube = apply_ufunc(numpy.sin, cube, in_place=True) + .. note:: + + This function maintains laziness when called; it does not realise data. This is dependent on `ufunc` argument + being a numpy operation that is compatible with lazy operation. + See more at :doc:`/userguide/real_and_lazy_data`. + """ if not isinstance(ufunc, np.ufunc): diff --git a/lib/iris/analysis/trajectory.py b/lib/iris/analysis/trajectory.py index 87efc7112e..c21d71d48c 100644 --- a/lib/iris/analysis/trajectory.py +++ b/lib/iris/analysis/trajectory.py @@ -216,6 +216,10 @@ def interpolate(cube, sample_points, method=None): ('longitude', [-60, -50, -40])] interpolated_cube = interpolate(cube, sample_points) + Notes + ------ + This function does not maintain laziness when called; it realises data. + See more at :doc:`/userguide/real_and_lazy_data`. """ from iris.analysis import Linear diff --git a/lib/iris/io/__init__.py b/lib/iris/io/__init__.py index 4659f70ae3..7dd08c723c 100644 --- a/lib/iris/io/__init__.py +++ b/lib/iris/io/__init__.py @@ -59,6 +59,11 @@ def run_callback(callback, cube, field, filename): It is possible that this function returns None for certain callbacks, the caller of this function should handle this case. + .. note:: + + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ from iris.cube import Cube @@ -424,6 +429,11 @@ def save(source, target, saver=None, **kwargs): >>> # Save a cube list to netCDF, using the NETCDF3_CLASSIC storage option >>> iris.save(my_cube_list, "myfile.nc", netcdf_format="NETCDF3_CLASSIC") + Notes + ------ + + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ from iris.cube import Cube, CubeList diff --git a/lib/iris/iterate.py b/lib/iris/iterate.py index 636635ee78..d6bac77d3b 100644 --- a/lib/iris/iterate.py +++ b/lib/iris/iterate.py @@ -58,6 +58,10 @@ def izip(*cubes, **kwargs): ... 'grid_longitude']): ... pass + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. """ if not cubes: raise TypeError("Expected one or more cubes.") diff --git a/lib/iris/palette.py b/lib/iris/palette.py index 5aa30a6b4e..a1c0a1e878 100644 --- a/lib/iris/palette.py +++ b/lib/iris/palette.py @@ -121,6 +121,11 @@ def cmap_norm(cube): Tuple of :class:`matplotlib.colors.LinearSegmentedColormap` and :class:`iris.palette.SymmetricNormalize` + Notes + ------ + This function maintains laziness when called; it does not realise data. + See more at :doc:`/userguide/real_and_lazy_data`. + """ args, kwargs = _default_cmap_norm((cube,), {}) return kwargs.get("cmap"), kwargs.get("norm") From c8b99a7081d34b8d3e73603808951b960ed99404 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Wed, 16 Nov 2022 18:33:15 +0000 Subject: [PATCH 278/319] Move test_pandas into unit tests. (#5071) --- lib/iris/tests/unit/pandas/__init__.py | 6 ++++++ lib/iris/tests/{ => unit/pandas}/test_pandas.py | 1 + 2 files changed, 7 insertions(+) create mode 100644 lib/iris/tests/unit/pandas/__init__.py rename lib/iris/tests/{ => unit/pandas}/test_pandas.py (99%) diff --git a/lib/iris/tests/unit/pandas/__init__.py b/lib/iris/tests/unit/pandas/__init__.py new file mode 100644 index 0000000000..103a264839 --- /dev/null +++ b/lib/iris/tests/unit/pandas/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for the :mod:`iris.pandas` module.""" diff --git a/lib/iris/tests/test_pandas.py b/lib/iris/tests/unit/pandas/test_pandas.py similarity index 99% rename from lib/iris/tests/test_pandas.py rename to lib/iris/tests/unit/pandas/test_pandas.py index 60a271c53b..fd716bd7c9 100644 --- a/lib/iris/tests/test_pandas.py +++ b/lib/iris/tests/unit/pandas/test_pandas.py @@ -3,6 +3,7 @@ # This file is part of Iris and is released under the LGPL license. # See COPYING and COPYING.LESSER in the root of the repository for full # licensing details. +"""All unit tests for the :mod:`iris.pandas` module.""" # import iris tests first so that some things can be initialised before # importing anything else From 2ed9b7e0db55897292ea5926d4dc3ea8d545f2ea Mon Sep 17 00:00:00 2001 From: Patrick Peglar Date: Thu, 17 Nov 2022 09:33:55 +0000 Subject: [PATCH 279/319] Implemented constraint equality. (#3749) * Implemented constraint equality. * Simplified. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Review changes. * Review changes. * Added whatsnew. Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/src/whatsnew/latest.rst | 7 + lib/iris/_constraints.py | 71 +++++ .../constraints/test_Constraint_equality.py | 274 ++++++++++++++++++ 3 files changed, 352 insertions(+) create mode 100644 lib/iris/tests/unit/constraints/test_Constraint_equality.py diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index df71c2dd3f..57540b9ffe 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -73,6 +73,13 @@ This document explains the changes made to Iris for this release a new error class: :class:`~iris.exceptions.CannotAddError` (subclass of :class:`ValueError`). (:pull:`5054`) +#. `@pp-mo`_ implemented == and != comparisons for :class:`~iris.Constraint` s. + A simple constraint is now == to another one constructed in the same way. + However, equality is limited for more complex cases : value-matching functions must + be the same identical function, and for &-combinations order is significant, + i.e. ``(c1 & c2) != (c2 & c1)``. + (:issue:`3616`, :pull:`3749`). + 🐛 Bugs Fixed ============= diff --git a/lib/iris/_constraints.py b/lib/iris/_constraints.py index 4e23793e1d..bfd4865f56 100644 --- a/lib/iris/_constraints.py +++ b/lib/iris/_constraints.py @@ -131,6 +131,30 @@ def latitude_bands(cell): _CoordConstraint(coord_name, coord_thing) ) + def __eq__(self, other): + # Equivalence is defined, but is naturally limited for any Constraints + # based on callables, i.e. "cube_func", or value functions for + # attributes/names/coords : These can only be == if they contain the + # *same* callable object (i.e. same object identity). + eq = ( + type(other) == Constraint + and self._name == other._name + and self._cube_func == other._cube_func + and self._coord_constraints == other._coord_constraints + ) + # NOTE: theoretically, you could compare coord constraints as a *set*, + # as order should not affect matching. + # Not totally sure, so for now let's not. + return eq + + def __hash__(self): + # We want constraints to have hashes, so they can act as e.g. + # dictionary keys or tuple elements. + # So, we *must* provide this, as overloading '__eq__' automatically + # disables it. + # Just use basic object identity. + return id(self) + def __repr__(self): args = [] if self._name: @@ -218,6 +242,19 @@ def __init__(self, lhs, rhs, operator): self.rhs = rhs_constraint self.operator = operator + def __eq__(self, other): + eq = ( + type(other) == ConstraintCombination + and self.lhs == other.lhs + and self.rhs == other.rhs + and self.operator == other.operator + ) + return eq + + def __hash__(self): + # Must re-define if you overload __eq__ : Use object identity. + return id(self) + def _coordless_match(self, cube): return self.operator( self.lhs._coordless_match(cube), self.rhs._coordless_match(cube) @@ -261,6 +298,18 @@ def __repr__(self): self._coord_thing, ) + def __eq__(self, other): + eq = ( + type(other) == _CoordConstraint + and self.coord_name == other.coord_name + and self._coord_thing == other._coord_thing + ) + return eq + + def __hash__(self): + # Must re-define if you overload __eq__ : Use object identity. + return id(self) + def extract(self, cube): """ Returns the the column based indices of the given cube which @@ -493,6 +542,17 @@ def __init__(self, **attributes): self._attributes = attributes super().__init__(cube_func=self._cube_func) + def __eq__(self, other): + eq = ( + type(other) == AttributeConstraint + and self._attributes == other._attributes + ) + return eq + + def __hash__(self): + # Must re-define if you overload __eq__ : Use object identity. + return id(self) + def _cube_func(self, cube): match = True for name, value in self._attributes.items(): @@ -577,6 +637,17 @@ def __init__( self._names = ("standard_name", "long_name", "var_name", "STASH") super().__init__(cube_func=self._cube_func) + def __eq__(self, other): + eq = type(other) == NameConstraint and all( + getattr(self, attname) == getattr(other, attname) + for attname in self._names + ) + return eq + + def __hash__(self): + # Must re-define if you overload __eq__ : Use object identity. + return id(self) + def _cube_func(self, cube): def matcher(target, value): if callable(value): diff --git a/lib/iris/tests/unit/constraints/test_Constraint_equality.py b/lib/iris/tests/unit/constraints/test_Constraint_equality.py new file mode 100644 index 0000000000..01e61b70a7 --- /dev/null +++ b/lib/iris/tests/unit/constraints/test_Constraint_equality.py @@ -0,0 +1,274 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Unit tests for equality testing of different constraint types.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +from iris._constraints import AttributeConstraint, Constraint, NameConstraint + + +class Test_Constraint__hash__(tests.IrisTest): + def test_empty(self): + c1 = Constraint() + c2 = Constraint() + self.assertEqual(hash(c1), hash(c1)) + self.assertNotEqual(hash(c1), hash(c2)) + + +class Test_Constraint__eq__(tests.IrisTest): + def test_empty_same(self): + c1 = Constraint() + c2 = Constraint() + self.assertEqual(c1, c2) + self.assertIsNot(c1, c2) + + def test_emptyname_same(self): + c1 = Constraint("") + c2 = Constraint("") + self.assertEqual(c1, c2) + + def test_empty_emptyname_differ(self): + c1 = Constraint() + c2 = Constraint("") + self.assertNotEqual(c1, c2) + + def test_names_same(self): + c1 = Constraint("a") + c2 = Constraint("a") + self.assertEqual(c1, c2) + + def test_names_differ(self): + c1 = Constraint("a") + c2 = Constraint("b") + self.assertNotEqual(c1, c2) + + def test_funcs_same(self): + # *Same* functions match + def func(cube): + return False + + c1 = Constraint(cube_func=func) + c2 = Constraint(cube_func=func) + self.assertEqual(c1, c2) + + def test_funcs_differ(self): + # Identical but different funcs do not match. + c1 = Constraint(cube_func=lambda c: False) + c2 = Constraint(cube_func=lambda c: False) + self.assertNotEqual(c1, c2) + + def test_coord_names_same(self): + c1 = Constraint(some_coordname=3) + c2 = Constraint(some_coordname=3) + self.assertEqual(c1, c2) + + def test_coord_names_differ(self): + c1 = Constraint(some_coordname_A=3) + c2 = Constraint(some_coordname_B=3) + self.assertNotEqual(c1, c2) + + def test_coord_values_differ(self): + c1 = Constraint(some_coordname=3) + c2 = Constraint(some_coordname=4) + self.assertNotEqual(c1, c2) + + def test_coord_orders_differ(self): + # We *could* maybe ignore Coordinate order, but at present we don't. + c1 = Constraint(coordname_1=1, coordname_2=2) + c2 = Constraint(coordname_2=2, coordname_1=1) + self.assertNotEqual(c1, c2) + + def test_coord_values_functions_same(self): + def func(coord): + return False + + c1 = Constraint(some_coordname=func) + c2 = Constraint(some_coordname=func) + self.assertEqual(c1, c2) + + def test_coord_values_functions_differ(self): + # Identical functions are not the same. + c1 = Constraint(some_coordname=lambda c: True) + c2 = Constraint(some_coordname=lambda c: True) + self.assertNotEqual(c1, c2) + + def test_coord_values_and_keys_same(self): + # **kwargs and 'coord_values=' are combined without distinction. + c1 = Constraint(coord_values={"a": [2, 3]}) + c2 = Constraint(a=[2, 3]) + self.assertEqual(c1, c2) + + +class Test_AttributeConstraint__hash__(tests.IrisTest): + def test_empty(self): + c1 = AttributeConstraint() + c2 = AttributeConstraint() + self.assertEqual(hash(c1), hash(c1)) + self.assertNotEqual(hash(c1), hash(c2)) + + +class Test_AttributeConstraint__eq__(tests.IrisTest): + def test_empty_same(self): + c1 = AttributeConstraint() + c2 = AttributeConstraint() + self.assertEqual(c1, c2) + self.assertIsNot(c1, c2) + + def test_attribute_plain_empty_diff(self): + c1 = AttributeConstraint() + c2 = Constraint() + self.assertNotEqual(c1, c2) + + def test_names_same(self): + c1 = AttributeConstraint(a=1) + c2 = AttributeConstraint(a=1) + self.assertEqual(c1, c2) + + def test_names_diff(self): + c1 = AttributeConstraint(a=1) + c2 = AttributeConstraint(a=1, b=1) + self.assertNotEqual(c1, c2) + + def test_values_diff(self): + c1 = AttributeConstraint(a=1, b=1) + c2 = AttributeConstraint(a=1, b=2) + self.assertNotEqual(c1, c2) + + def test_func_same(self): + def func(attrs): + return False + + c1 = AttributeConstraint(a=func) + c2 = AttributeConstraint(a=func) + self.assertEqual(c1, c2) + + def test_func_diff(self): + c1 = AttributeConstraint(a=lambda a: False) + c2 = AttributeConstraint(a=lambda a: False) + self.assertNotEqual(c1, c2) + + +class Test_NameConstraint__hash__(tests.IrisTest): + def test_empty(self): + c1 = NameConstraint() + c2 = NameConstraint() + self.assertEqual(hash(c1), hash(c1)) + self.assertNotEqual(hash(c1), hash(c2)) + + +class Test_NameConstraint__eq__(tests.IrisTest): + def test_empty_same(self): + c1 = NameConstraint() + c2 = NameConstraint() + self.assertEqual(c1, c2) + self.assertIsNot(c1, c2) + + def test_attribute_plain_empty_diff(self): + c1 = NameConstraint() + c2 = Constraint() + self.assertNotEqual(c1, c2) + + def test_names_same(self): + c1 = NameConstraint(standard_name="air_temperature") + c2 = NameConstraint(standard_name="air_temperature") + self.assertEqual(c1, c2) + + def test_full_same(self): + c1 = NameConstraint( + standard_name="air_temperature", + long_name="temp", + var_name="tair", + STASH="m01s02i003", + ) + c2 = NameConstraint( + standard_name="air_temperature", + long_name="temp", + var_name="tair", + STASH="m01s02i003", + ) + self.assertEqual(c1, c2) + + def test_missing_diff(self): + c1 = NameConstraint(standard_name="air_temperature", var_name="tair") + c2 = NameConstraint(standard_name="air_temperature") + self.assertNotEqual(c1, c2) + + def test_standard_name_diff(self): + c1 = NameConstraint(standard_name="air_temperature") + c2 = NameConstraint(standard_name="height") + self.assertNotEqual(c1, c2) + + def test_long_name_diff(self): + c1 = NameConstraint(long_name="temp") + c2 = NameConstraint(long_name="t3") + self.assertNotEqual(c1, c2) + + def test_var_name_diff(self): + c1 = NameConstraint(var_name="tair") + c2 = NameConstraint(var_name="xxx") + self.assertNotEqual(c1, c2) + + def test_stash_diff(self): + c1 = NameConstraint(STASH="m01s02i003") + c2 = NameConstraint(STASH="m01s02i777") + self.assertNotEqual(c1, c2) + + def test_func_same(self): + def func(name): + return True + + c1 = NameConstraint(STASH="m01s02i003", long_name=func) + c2 = NameConstraint(STASH="m01s02i003", long_name=func) + self.assertEqual(c1, c2) + + def test_func_diff(self): + c1 = NameConstraint(STASH="m01s02i003", long_name=lambda n: True) + c2 = NameConstraint(STASH="m01s02i003", long_name=lambda n: True) + self.assertNotEqual(c1, c2) + + +class Test_ConstraintCombination__hash__(tests.IrisTest): + def test_empty(self): + c1 = Constraint() & Constraint() + c2 = Constraint() & Constraint() + self.assertEqual(hash(c1), hash(c1)) + self.assertNotEqual(hash(c1), hash(c2)) + + def test_identical_construction(self): + c1, c2 = Constraint(a=1), Constraint(b=1) + cc1 = c1 & c2 + cc2 = c1 & c2 + self.assertNotEqual(hash(cc1), hash(cc2)) + + +class Test_ConstraintCombination__eq__(tests.IrisTest): + def test_empty_same(self): + c1 = Constraint() & Constraint() + c2 = Constraint() & Constraint() + self.assertEqual(c1, c2) + self.assertIsNot(c1, c2) + + def test_multi_components_same(self): + c1 = Constraint("a") & Constraint(b=1) + c2 = Constraint("a") & Constraint(b=1) + self.assertEqual(c1, c2) + + def test_multi_components_diff(self): + c1 = Constraint("a") & Constraint(b=1, c=2) + c2 = Constraint("a") & Constraint(b=1) + self.assertNotEqual(c1, c2) + + def test_different_component_order(self): + c1, c2 = Constraint("a"), Constraint(b=1) + cc1 = c1 & c2 + cc2 = c2 & c1 + self.assertNotEqual(cc1, cc2) + + +if __name__ == "__main__": + tests.main() From 8bb85a89fc7087320500ba7bc030a87a4492605f Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:37:46 +0000 Subject: [PATCH 280/319] More accurate netcdf4 pin `<1.6.1`. (#5075) * More accurate netcdf4 pin <1.6.1. * What's New entry. * What's New entry. --- docs/src/whatsnew/latest.rst | 6 +++--- requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.cfg | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 57540b9ffe..657b196a94 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -121,7 +121,7 @@ This document explains the changes made to Iris for this release #. `@bjlittle`_ and `@trexfeathers`_ (reviewer) fixed an issue which prevented uncompressed PP fields with additional trailing padded words in the field data to be loaded and saved. (:pull:`5058`) - + #. `@lbdreyer`_ and `@trexfeathers`_ (reviewer) fixed the handling of data when regridding with :class:`~iris.analysis.UnstructuredNearest` or calling :func:`~iris.analysis.trajectory.interpolate` such that the data type and mask is @@ -184,8 +184,8 @@ This document explains the changes made to Iris for this release details. (:pull:`4968`) -#. `@trexfeathers`_ introduced the ``netcdf4!=1.6.1`` pin to avoid a problem - with segfaults. (:pull:`4968`) +#. `@trexfeathers`_ introduced the ``netcdf4<1.6.1`` pin to avoid a problem + with segfaults. (:pull:`4968`, :pull:`5075`, :issue:`5016`) #. `@trexfeathers`_ updated the Matplotlib colormap registration in :mod:`iris.palette` in response to a deprecation warning. Using the new diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 87a16ba18a..6815c7fe6d 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -16,7 +16,7 @@ dependencies: - cftime >=1.5 - dask-core >=2.26 - matplotlib >=3.5 - - netcdf4 !=1.6.1 + - netcdf4 <1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index e2d696d30c..316e0868ac 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -16,7 +16,7 @@ dependencies: - cftime >=1.5 - dask-core >=2.26 - matplotlib >=3.5 - - netcdf4 !=1.6.1 + - netcdf4 <1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 500ec2f80f..66e22c230f 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -16,7 +16,7 @@ dependencies: - cftime >=1.5 - dask-core >=2.26 - matplotlib >=3.5 - - netcdf4 !=1.6.1 + - netcdf4 <1.6.1 - numpy >=1.19 - python-xxhash - pyproj diff --git a/setup.cfg b/setup.cfg index ca35a8eb4e..f6276cb173 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,7 +52,7 @@ install_requires = cftime>=1.5.0 dask[array]>=2.26 matplotlib>=3.5 - netcdf4!=1.6.1 + netcdf4<1.6.1 numpy>=1.19 scipy shapely!=1.8.3 From bd5fa5fb96769ec1a559878ea0eab858df6dff2a Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 17 Nov 2022 12:15:56 +0000 Subject: [PATCH 281/319] What's New fixes. (#5077) --- docs/src/whatsnew/latest.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 241156367e..fe30828d83 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -25,7 +25,7 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. Welcome to `@ESadek-MO`_, `@TTV-Intrepid`_ and @`hsteptoe`_, who made their +#. Welcome to `@ESadek-MO`_, `@TTV-Intrepid`_ and `@hsteptoe`_, who made their first contributions to Iris 🎉 .. _try_experimental_stratify: @@ -87,7 +87,7 @@ This document explains the changes made to Iris for this release :class:`~iris.cube.Cube` dimensions, auxiliary coordinates and attribute information. **Note:** the improvements are opt-in, by setting the :obj:`iris.FUTURE.pandas_ndim` flag (see :class:`iris.Future` for more). - (:issue:`4526`, :pull:`4669`) + (:issue:`4526`, :pull:`4909`, :pull:`4669`, :pull:`5059`, :pull:`5074`) 🐛 Bugs Fixed From 4176d1566357a96b5b5c957b24a232f78bf9ca01 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 17 Nov 2022 15:35:54 +0000 Subject: [PATCH 282/319] Documentation updates for `v3.4.0rc0` release. (#5078) * Finalise v3.4 What's New. * Update What's New index. * 3.4 what's new small search engine change. * Hard-coded release information in What's New. --- docs/src/whatsnew/{latest.rst => 3.4.rst} | 28 ++++-- docs/src/whatsnew/index.rst | 4 +- docs/src/whatsnew/latest.rst.template | 112 ---------------------- 3 files changed, 23 insertions(+), 121 deletions(-) rename docs/src/whatsnew/{latest.rst => 3.4.rst} (90%) delete mode 100644 docs/src/whatsnew/latest.rst.template diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/3.4.rst similarity index 90% rename from docs/src/whatsnew/latest.rst rename to docs/src/whatsnew/3.4.rst index fe30828d83..f34fd25904 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/3.4.rst @@ -1,7 +1,7 @@ .. include:: ../common_links.inc -|iris_version| |build_date| [unreleased] -**************************************** +v3.4 (17 Nov 2022) [release candidate] +************************************** This document explains the changes made to Iris for this release (:doc:`View all changes `.) @@ -14,9 +14,22 @@ This document explains the changes made to Iris for this release :animate: fade-in :open: - The highlights for this major/minor release of Iris include: - - * N/A + The highlights for this minor release of Iris include: + + * We have **archived older Iris documentation** - everything before + ``v3.0.0`` - so older versions will soon no longer appear in search + engines. If you need this older documentation: please + see :ref:`iris_support`. + * We have added a :ref:`glossary` to the Iris documentation. + * We have completed work to make **Pandas interoperability** handle + n-dimensional :class:`~iris.cube.Cube`\s. + * We have **begun refactoring Iris' regridding**, which has already improved + performance and functionality, with more potential in future! + * We have made several other significant `🚀 Performance Enhancements`_. + * Please note that **Iris cannot currently work with the latest NetCDF4 + releases**. The pin is set to ``` if you have any issues or feature requests for improving Iris. Enjoy! @@ -226,8 +239,9 @@ This document explains the changes made to Iris for this release #. `@tkknight`_ updated the links for the Iris documentation to v2.4 and earlier to point to the archive of zip files instead. (:pull:`5064`) -#. `@Esadek-MO`_ added notes at the bottom of functions to - to clarify if the function preserves laziness or not. (:pull:`5066`) +#. `@Esadek-MO`_ began adding notes at the bottom of functions to + to clarify if the function preserves laziness or not. See :issue:`3292` for + the ongoing checklist. (:pull:`5066`) 💼 Internal =========== diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index 8cff21f32f..e5da025691 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -5,13 +5,13 @@ What's New in Iris ------------------ -.. include:: latest.rst +.. include:: 3.4.rst .. toctree:: :maxdepth: 1 :hidden: - latest.rst + 3.4.rst 3.3.rst 3.2.rst 3.1.rst diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template deleted file mode 100644 index 661ee47f50..0000000000 --- a/docs/src/whatsnew/latest.rst.template +++ /dev/null @@ -1,112 +0,0 @@ -.. include:: ../common_links.inc - -|iris_version| |build_date| [unreleased] -**************************************** - -This document explains the changes made to Iris for this release -(:doc:`View all changes `.) - - -.. dropdown:: :opticon:`report` |iris_version| Release Highlights - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - :open: - - The highlights for this major/minor release of Iris include: - - * N/A - - And finally, get in touch with us on :issue:`GitHub` if you have - any issues or feature requests for improving Iris. Enjoy! - - -NOTE: section below is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'latest.rst') - -v3.X.X (DD MMM YYYY) -==================== - -.. dropdown:: :opticon:`alert` v3.X.X Patches - :container: + shadow - :title: text-primary text-center font-weight-bold - :body: bg-light - :animate: fade-in - - The patches in this release of Iris include: - - #. N/A - -NOTE: section above is a template for bugfix patches -==================================================== - (Please remove this section when creating an initial 'latest.rst') - - - -📢 Announcements -================ - -#. N/A - - -✨ Features -=========== - -#. N/A - - -🐛 Bugs Fixed -============= - -#. N/A - - -💣 Incompatible Changes -======================= - -#. N/A - - -🚀 Performance Enhancements -=========================== - -#. N/A - - -🔥 Deprecations -=============== - -#. N/A - - -🔗 Dependencies -=============== - -#. N/A - - -📚 Documentation -================ - -#. N/A - - -💼 Internal -=========== - -#. N/A - - -.. comment - Whatsnew author names (@github name) in alphabetical order. Note that, - core dev names are automatically included by the common_links.inc: - - - - -.. comment - Whatsnew resources in alphabetical order: - - From d6ee976f16b304ea4e66ecb22739662c0b0aa9f0 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Thu, 17 Nov 2022 16:19:36 +0000 Subject: [PATCH 283/319] Restore latest What's New files. --- docs/src/whatsnew/index.rst | 3 +- docs/src/whatsnew/latest.rst | 88 ++++++++++++++++++++ docs/src/whatsnew/latest.rst.template | 111 ++++++++++++++++++++++++++ 3 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 docs/src/whatsnew/latest.rst create mode 100644 docs/src/whatsnew/latest.rst.template diff --git a/docs/src/whatsnew/index.rst b/docs/src/whatsnew/index.rst index e5da025691..005fac70c4 100644 --- a/docs/src/whatsnew/index.rst +++ b/docs/src/whatsnew/index.rst @@ -5,12 +5,13 @@ What's New in Iris ------------------ -.. include:: 3.4.rst +.. include:: latest.rst .. toctree:: :maxdepth: 1 :hidden: + latest.rst 3.4.rst 3.3.rst 3.2.rst diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst new file mode 100644 index 0000000000..6a9228a8be --- /dev/null +++ b/docs/src/whatsnew/latest.rst @@ -0,0 +1,88 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`report` |iris_version| Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this major/minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. N/A + + +🐛 Bugs Fixed +============= + +#. N/A + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. N/A + + +📚 Documentation +================ + +#. N/A + + +💼 Internal +=========== + +#. N/A + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: + diff --git a/docs/src/whatsnew/latest.rst.template b/docs/src/whatsnew/latest.rst.template new file mode 100644 index 0000000000..a0ce415a65 --- /dev/null +++ b/docs/src/whatsnew/latest.rst.template @@ -0,0 +1,111 @@ +.. include:: ../common_links.inc + +|iris_version| |build_date| [unreleased] +**************************************** + +This document explains the changes made to Iris for this release +(:doc:`View all changes `.) + + +.. dropdown:: :opticon:`report` |iris_version| Release Highlights + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + :open: + + The highlights for this major/minor release of Iris include: + + * N/A + + And finally, get in touch with us on :issue:`GitHub` if you have + any issues or feature requests for improving Iris. Enjoy! + + +NOTE: section below is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + +v3.X.X (DD MMM YYYY) +==================== + +.. dropdown:: :opticon:`alert` v3.X.X Patches + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + + The patches in this release of Iris include: + + #. N/A + +NOTE: section above is a template for bugfix patches +==================================================== + (Please remove this section when creating an initial 'latest.rst') + + + +📢 Announcements +================ + +#. N/A + + +✨ Features +=========== + +#. N/A + + +🐛 Bugs Fixed +============= + +#. N/A + + +💣 Incompatible Changes +======================= + +#. N/A + + +🚀 Performance Enhancements +=========================== + +#. N/A + + +🔥 Deprecations +=============== + +#. N/A + + +🔗 Dependencies +=============== + +#. N/A + + +📚 Documentation +================ + +#. N/A + + +💼 Internal +=========== + +#. N/A + + +.. comment + Whatsnew author names (@github name) in alphabetical order. Note that, + core dev names are automatically included by the common_links.inc: + + + + +.. comment + Whatsnew resources in alphabetical order: + From 57647d2c413dc3b85ecdde566a6340c9e43ad83b Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 10:06:36 +0000 Subject: [PATCH 284/319] Updated environment lockfiles (#5080) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 16 ++++++++-------- requirements/ci/nox.lock/py38-linux-64.lock | 16 ++++++++-------- requirements/ci/nox.lock/py39-linux-64.lock | 16 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 6cea5880aa..254eea5b26 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: e755261bf8cb4508d0b291e9efd406c55fdcebf113bdeb4ea14bfccf3f623e56 +# input_hash: 65e8c3d4ababc804f8d3715a14ce94c3f564a37860525f35ea1aed69efd67be8 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 @@ -77,7 +77,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 @@ -98,13 +98,13 @@ https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.4-h63197d8_1.tar.bz2#d1b93417274d24b751a831c21169669a +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00 -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -137,7 +137,7 @@ https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.4-default_h3a83d3e_0.tar.bz2#d60d2d91b5991e364fd95cba5ec8ab96 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 @@ -185,7 +185,7 @@ https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.4-default_h2e3cab8_0.tar.bz2#248a013d3e7248c5c5f4ff69ffc307e8 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c @@ -211,7 +211,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_100 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py310h597c629_0.tar.bz2#aa5aad596f9d5ef091364c4dad789094 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.0-pyhd8ed1ab_0.tar.bz2#1ab924fb3c44ed49796cc85eb1315cdc +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e @@ -231,7 +231,7 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.ta https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py310h8d5ebf3_0.tar.bz2#da51ddb20c0f99d672eb756c3abf27e7 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 1cb20c2cce..6c5bba3778 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 0adff1c1aa6a49a3d784fd9be06fb43f1a450cf0f1871db2d38d071ffa6efbcc +# input_hash: dc794a12a2155d2a605b34fc34ece8039a0f0d43fbf7d304366cf8c33cf94cd1 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 @@ -76,7 +76,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 @@ -97,12 +97,12 @@ https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.4-h63197d8_1.tar.bz2#d1b93417274d24b751a831c21169669a +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -117,7 +117,7 @@ https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.4-default_h3a83d3e_0.tar.bz2#d60d2d91b5991e364fd95cba5ec8ab96 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 @@ -150,7 +150,7 @@ https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#3427 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.4-default_h2e3cab8_0.tar.bz2#248a013d3e7248c5c5f4ff69ffc307e8 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 @@ -214,7 +214,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py38h2b5fc30_0.tar.bz2#218274e4a04630a977b4da2b45eff593 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.0-pyhd8ed1ab_0.tar.bz2#1ab924fb3c44ed49796cc85eb1315cdc +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -234,7 +234,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py38hb021067_0.tar.bz2#72422499195d8aded0dfd461c6e3e86f diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index bec758d380..35ea65c3e0 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 8a7c28a9309987aef78235650068290b0ecd5ac9e6ab522f4fad962cc9b1fc6c +# input_hash: 8720b47771aff1b233330a6562a535e5ad3a153a023d02d4dc71b383a25796a3 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 @@ -77,7 +77,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.b https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c -https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.39.4-h753d276_0.tar.bz2#978924c298fc2215f129e8171bbea688 +https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 @@ -98,12 +98,12 @@ https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.4-h63197d8_1.tar.bz2#d1b93417274d24b751a831c21169669a +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f -https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.39.4-h4ff8645_0.tar.bz2#643c271de2dd23ecbd107284426cebc2 +https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 @@ -118,7 +118,7 @@ https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.4-default_h3a83d3e_0.tar.bz2#d60d2d91b5991e364fd95cba5ec8ab96 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 @@ -151,7 +151,7 @@ https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#3427 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.4-default_h2e3cab8_0.tar.bz2#248a013d3e7248c5c5f4ff69ffc307e8 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 @@ -215,7 +215,7 @@ https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py39hd97740a_0.tar.bz2#be40f2e5698bd0497ddee8a63f8fb4a6 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.0-pyhd8ed1ab_0.tar.bz2#1ab924fb3c44ed49796cc85eb1315cdc +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 @@ -235,7 +235,7 @@ https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.8-pyhd8ed1ab_0.tar.bz2#8001c46448f385fa43bc4221893704d2 +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py39hf9fd14e_0.tar.bz2#78ce32061e0be12deb8e0f11ffb76906 From 60e293cba03635d5281e4de337bee0a64ff96a28 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 10:44:12 +0000 Subject: [PATCH 285/319] Updated environment lockfiles (#5085) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 83 +++++----- requirements/ci/nox.lock/py38-linux-64.lock | 153 ++++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 153 ++++++++++--------- 3 files changed, 202 insertions(+), 187 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 254eea5b26..12353aaca8 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -8,10 +8,11 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hc81fddc_0.tar.bz2#c2719e2faa7bd7076d3a4b52271e5622 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hcc3a1bd_1.conda#737be0d34c22d24432049ab7a3214de4 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-3_cp310.conda#4eb33d14d794b0f4be116443ffed3853 https://conda.anaconda.org/conda-forge/noarch/tzdata-2022f-h191b570_0.tar.bz2#e366350e2343a798e29833286abe2560 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d @@ -30,6 +31,7 @@ https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 @@ -51,6 +53,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 @@ -75,8 +78,9 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 +https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -92,10 +96,11 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.t https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 +https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 @@ -103,7 +108,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f -https://conda.anaconda.org/conda-forge/linux-64/python-3.10.6-h582c2e5_0_cpython.tar.bz2#6f009f92084e84884d1dff862b85eb00 +https://conda.anaconda.org/conda-forge/linux-64/python-3.10.8-h257c98d_0_cpython.conda#fa742265350d7f6d664bc13436caf4ad https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -111,6 +116,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bd https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 @@ -123,6 +129,7 @@ https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3.tar.bz2#785160da087cf1d70e989afbb761f01c https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 @@ -136,25 +143,32 @@ https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 +https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py310h53a5b5f_0.conda#3b114b1559def8bad228fec544ac1812 https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 -https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-2_cp310.tar.bz2#9e7160cd0d865e98f6803f1fe15c8b61 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_2.tar.bz2#cce72b32ccc346ed166fc85071854a86 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_5.tar.bz2#9e68d2ff6d98737c855b65f48dd3c597 https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e @@ -168,86 +182,77 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_1.tar.bz2#be4a201ac582c11d89ed7d15b3157cc3 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py310h5764c6d_0.tar.bz2#e972c5a1f472561cf4a91962cb01f4b4 https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c -https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_2.tar.bz2#6bb8063dd08f9724c18744b0e040cfe2 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3.tar.bz2#785160da087cf1d70e989afbb761f01c +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e -https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py310h53a5b5f_1.tar.bz2#0b7d4c8253f7191030adf34e2768c412 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310h454ad03_3.tar.bz2#eb354ff791f505b1d6f13f776359d88e https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0d2025b_11.tar.bz2#316026c5f80d8b5b9666e87b8614ac6c +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h4a94279_0.tar.bz2#7a499b94463000c83e349fffb6ce2631 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_2.tar.bz2#cce72b32ccc346ed166fc85071854a86 -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_5.tar.bz2#9e68d2ff6d98737c855b65f48dd3c597 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py310h5764c6d_1.tar.bz2#be4a201ac582c11d89ed7d15b3157cc3 +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2.tar.bz2#61e2f2f7befaf45f47d1da449a9a0aca +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_2.tar.bz2#0582a434d03f6b06d5defbb142c96f4f +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5b266fc_2.tar.bz2#c4a3707d6a630facb6cf7ed8e0d37326 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py310h5764c6d_0.tar.bz2#e972c5a1f472561cf4a91962cb01f4b4 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py310hff52083_0.tar.bz2#02600c102a32274e20fc0604ef35af3c https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18 https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py310h597c629_0.tar.bz2#aa5aad596f9d5ef091364c4dad789094 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e -https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.1-py310h769672d_1.tar.bz2#4dd589c55d445e52ef0a7102158254df +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py310h8d5ebf3_0.tar.bz2#da51ddb20c0f99d672eb756c3abf27e7 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py310h769672d_0.conda#bc363997d22f3b058fb17f1e89d4c96f https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_2.tar.bz2#e1648c222911ad7559d62831e4bc447c https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2.tar.bz2#61e2f2f7befaf45f47d1da449a9a0aca -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_2.tar.bz2#0582a434d03f6b06d5defbb142c96f4f https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5b266fc_2.tar.bz2#c4a3707d6a630facb6cf7ed8e0d37326 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.4-py310hd8f1fbe_0.tar.bz2#5c54faf5327d5d7ef993dd0f6f25e123 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py310hd8f1fbe_0.conda#765b39936044b542a69ec2d863f5b891 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310h83f2385_3.tar.bz2#4ec35f7eebe4221c1c00fdd6540db4dc +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee_0.conda#52cbed7e92713cf01b76445530396695 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py310h8d5ebf3_0.tar.bz2#da51ddb20c0f99d672eb756c3abf27e7 +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_2.tar.bz2#0d815f1b2258d3d4c17cc80fd01e0f36 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310h83f2385_3.tar.bz2#4ec35f7eebe4221c1c00fdd6540db4dc https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_1.tar.bz2#8c151d720f9fe3b9962efe71fc10b07b -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-h7acdfc8_2.conda#7ec7d259b6d725ca952d40e2355e192c +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_2.tar.bz2#1e2c49215b17e6cf06edf100c9869ebe diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 6c5bba3778..c3a7f19b05 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -8,10 +8,11 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hc81fddc_0.tar.bz2#c2719e2faa7bd7076d3a4b52271e5622 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hcc3a1bd_1.conda#737be0d34c22d24432049ab7a3214de4 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-3_cp38.conda#2f3f7af062b42d664117662612022204 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373 @@ -29,6 +30,7 @@ https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 @@ -50,6 +52,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 @@ -74,8 +77,9 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 +https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -91,10 +95,11 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.t https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 +https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 @@ -102,69 +107,67 @@ https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f +https://conda.anaconda.org/conda-forge/linux-64/python-3.8.15-h257c98d_0_cpython.conda#485151f9b0c1cfb2375b6c4995ac52ba https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 -https://conda.anaconda.org/conda-forge/linux-64/python-3.8.13-h582c2e5_0_cpython.tar.bz2#8ec74710472994e2411a8020fa8589ce -https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 +https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_2.tar.bz2#c342a370480791db83d5dd20f2d8899f +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar.bz2#0c469687a517052c0d581fc6e1a4189d https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py38h7042d01_0.conda#d5a3620cd8c1af4115120f21d678507a +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0d2025b_11.tar.bz2#316026c5f80d8b5b9666e87b8614ac6c +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py38h0a891b7_0.tar.bz2#fe2ef279417faa1af0adf178de2032f7 https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 -https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.8-2_cp38.tar.bz2#bfbb29d517281e78ac53e48d21e6e860 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_2.tar.bz2#9b13816a39904084556126a6ce7fd0d0 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_5.tar.bz2#0856c59f9ddb710c640dc0428d66b1b7 https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e @@ -178,80 +181,82 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_1.tar.bz2#358beb228a53b5e1031862de3525d1d3 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py38h0a891b7_0.tar.bz2#44421904760e9f5ae2035193e04360f0 https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c -https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_2.tar.bz2#2276b1f4d1ede3f5f14cc7e4ae6f9a33 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e -https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_2.tar.bz2#c342a370480791db83d5dd20f2d8899f -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar.bz2#0c469687a517052c0d581fc6e1a4189d +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py38h7042d01_1.tar.bz2#7fa0e9ed4e8a096e2f00b2426ebba0de https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h9eb91d8_3.tar.bz2#61dc7b3140b7b79b1985b53d52726d74 https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py38h0a891b7_0.tar.bz2#fe2ef279417faa1af0adf178de2032f7 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h4a94279_0.tar.bz2#7a499b94463000c83e349fffb6ce2631 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hce0a2d1_2.tar.bz2#be61a535f279bffdf7f449a654eaa19d https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_2.tar.bz2#9b13816a39904084556126a6ce7fd0d0 -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_5.tar.bz2#0856c59f9ddb710c640dc0428d66b1b7 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py38h0a891b7_1.tar.bz2#358beb228a53b5e1031862de3525d1d3 +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2.tar.bz2#d30399a3c636c75cfd3460c92effa960 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_2.tar.bz2#dfd81898f0c6e9ee0c22305da6aa443e +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hafd38ec_2.tar.bz2#8df75c6a8c1deac4e99583ec624ff327 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py38h0a891b7_0.tar.bz2#44421904760e9f5ae2035193e04360f0 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py38h578d9bd_0.tar.bz2#fc6d74114bb0006224f252393bdacee6 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py38h2b5fc30_0.tar.bz2#218274e4a04630a977b4da2b45eff593 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 -https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.1-py38h8f669ce_1.tar.bz2#75f37fc81d6513ba5db1e05301671a2e -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py38hb021067_0.tar.bz2#72422499195d8aded0dfd461c6e3e86f +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py38h8f669ce_0.conda#dbc17622f9d159be987bd21959d5494e +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hce0a2d1_2.tar.bz2#be61a535f279bffdf7f449a654eaa19d https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2.tar.bz2#d30399a3c636c75cfd3460c92effa960 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_2.tar.bz2#dfd81898f0c6e9ee0c22305da6aa443e https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hafd38ec_2.tar.bz2#8df75c6a8c1deac4e99583ec624ff327 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.4-py38hfa26641_0.tar.bz2#a39a1d696fbe108d705c0ac584b937e3 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py38hfa26641_0.conda#7be81814bae276dc7b4c707cf1e8186b https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar.bz2#82b3797d08a43a101b645becbb938e65 -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38hf6c3373_3.tar.bz2#1dc477fef9b0b1080af3e7c7ecb4aff7 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee_0.conda#52cbed7e92713cf01b76445530396695 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py38hb021067_0.tar.bz2#72422499195d8aded0dfd461c6e3e86f +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_2.tar.bz2#ad6437509a14f1e8e5b8a354f93f340c https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38hf6c3373_3.tar.bz2#1dc477fef9b0b1080af3e7c7ecb4aff7 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_1.tar.bz2#38d9029214399e4bfc378b62b0171bf0 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-h7acdfc8_2.conda#7ec7d259b6d725ca952d40e2355e192c +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_1.tar.bz2#38d9029214399e4bfc378b62b0171bf0 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_2.tar.bz2#cfa725eff634872f90dcd5ebf8e8dc1a -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py38h578d9bd_0.tar.bz2#e1a19f0d4686a701d4a4acce2b625acb https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py38h578d9bd_0.tar.bz2#e1a19f0d4686a701d4a4acce2b625acb https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 35ea65c3e0..de49c39a84 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -8,10 +8,11 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hc81fddc_0.tar.bz2#c2719e2faa7bd7076d3a4b52271e5622 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hcc3a1bd_1.conda#737be0d34c22d24432049ab7a3214de4 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf +https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-3_cp39.conda#0dd193187d54e585cac7eab942a8847e https://conda.anaconda.org/conda-forge/noarch/tzdata-2022f-h191b570_0.tar.bz2#e366350e2343a798e29833286abe2560 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d @@ -30,6 +31,7 @@ https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#9 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2#626e68ae9cc5912d6adb79d318cf962d https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 @@ -51,6 +53,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 @@ -75,8 +78,9 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 +https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c -https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.38-h753d276_0.tar.bz2#575078de1d3a3114b3ce131bd1508d0c +https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 @@ -92,10 +96,11 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.t https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 -https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_0.tar.bz2#4e54cbfc47b8c74c2ecc1e7730d8edce +https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 +https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 @@ -103,69 +108,67 @@ https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f +https://conda.anaconda.org/conda-forge/linux-64/python-3.9.15-h47a2c10_0_cpython.conda#4c15ad54369ad2fa36a0d56c6675e241 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b -https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 -https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 -https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 -https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 -https://conda.anaconda.org/conda-forge/linux-64/python-3.9.13-h9a8a25e_0_cpython.tar.bz2#69bc307cc4d7396c5fccb26bbcc9c379 -https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 -https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb +https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f -https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 +https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb +https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 -https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 +https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 +https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 +https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 +https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_2.tar.bz2#c678e07e7862b3157fb9f6d908233ffa +https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar.bz2#b035b507f55bb6a967d86d4b7e059437 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py39h3d75532_0.conda#ea5d332e361eb72c2593cf79559bc0ec +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-14.0-h0d2025b_11.tar.bz2#316026c5f80d8b5b9666e87b8614ac6c +https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py39hb9d737c_0.tar.bz2#12184951da572828fb986b06ffb63eed https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz2#076becd9e05608f8dc72757d5f3a91ff https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 -https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-2_cp39.tar.bz2#39adde4247484de2bb4000122fdcf665 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_2.tar.bz2#b643f1e19306b75a6013d77228156076 https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2#ef9db3c38ae7275f6b14491cfe61a248 https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e @@ -179,80 +182,82 @@ https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1. https://conda.anaconda.org/conda-forge/noarch/toml-0.10.2-pyhd8ed1ab_0.tar.bz2#f832c45a477c78bebd107098db465095 https://conda.anaconda.org/conda-forge/noarch/tomli-2.0.1-pyhd8ed1ab_0.tar.bz2#5844808ffab9ebdb694585b50ba02a96 https://conda.anaconda.org/conda-forge/noarch/toolz-0.12.0-pyhd8ed1ab_0.tar.bz2#92facfec94bc02d6ccf42e7173831a36 +https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_1.tar.bz2#8a7d309b08cff6386fe384aa10dd3748 https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.4.0-pyha770c72_0.tar.bz2#2d93b130d148d7fc77e583677792fc6a +https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py39hb9d737c_0.tar.bz2#230d65004135bf312504a1bbcb0c7a08 https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2#c829cfb8cb826acb9de0ac1a2df0a940 +https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 +https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c -https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_2.tar.bz2#fc70a133e8162f51e363cff3b6dc741c -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.1-hd4edc92_1.tar.bz2#e604f83df3497fcdb6991ae58b73238f -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e -https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.0.0-pyha770c72_1.tar.bz2#ec069c4db6a0ad84107bac5da62819d2 -https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411 -https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_2.tar.bz2#c678e07e7862b3157fb9f6d908233ffa -https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar.bz2#b035b507f55bb6a967d86d4b7e059437 +https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 +https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 +https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef +https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.4-py39h3d75532_1.tar.bz2#ba4f1c0466d4ba7f53090c150fc88434 https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hf3a2cdf_3.tar.bz2#2bd111c38da69056e5fe25a51b832eba https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py39hb9d737c_0.tar.bz2#12184951da572828fb986b06ffb63eed +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h4a94279_0.tar.bz2#7a499b94463000c83e349fffb6ce2631 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h14a8356_2.tar.bz2#5d93c781338ff274a0b3dc3d901e19a6 https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_2.tar.bz2#b643f1e19306b75a6013d77228156076 -https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2#ef9db3c38ae7275f6b14491cfe61a248 -https://conda.anaconda.org/conda-forge/linux-64/tornado-6.2-py39hb9d737c_1.tar.bz2#8a7d309b08cff6386fe384aa10dd3748 +https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2.tar.bz2#234ad9828eca1caf0f2fdcb4a24ad816 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_2.tar.bz2#0615ac8191c6ccf7d40860aff645f774 +https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h76a96b7_2.tar.bz2#10bea68a9dd064b703743d210e679408 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-15.0.0-py39hb9d737c_0.tar.bz2#230d65004135bf312504a1bbcb0c7a08 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py39hf3d152e_0.tar.bz2#242b09f574d87f15a1d1479970037594 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045 -https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 +https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4 https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py39hd97740a_0.tar.bz2#be40f2e5698bd0497ddee8a63f8fb4a6 https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab -https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.1-h3e40eee_1.tar.bz2#c03f4fca88373cf6c26d932c26e4ee20 -https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.1-py39h4661b88_1.tar.bz2#d541bbe75ce0f2679344ead981b2f858 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.11-h382ae3d_0.tar.bz2#509e3f89508398070d3bf7769d9e8b03 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a +https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py39hf9fd14e_0.tar.bz2#78ce32061e0be12deb8e0f11ffb76906 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py39h4661b88_0.conda#e17e50269c268d79478956a262a9fe13 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h14a8356_2.tar.bz2#5d93c781338ff274a0b3dc3d901e19a6 https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c -https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2.tar.bz2#234ad9828eca1caf0f2fdcb4a24ad816 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_2.tar.bz2#0615ac8191c6ccf7d40860aff645f774 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h76a96b7_2.tar.bz2#10bea68a9dd064b703743d210e679408 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.4-py39h5a03fae_0.tar.bz2#1d06fe7a83e1ac8340c7b483f6ee00e5 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py39h5a03fae_0.conda#c3eb463691a8b93f1c381a9e56ecad9a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar.bz2#0f11bcdf9669a5ae0f39efd8c830209a -https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 -https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39h91bfd65_3.tar.bz2#7d10a2e14c08f383baae00e77bf890e5 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee_0.conda#52cbed7e92713cf01b76445530396695 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a -https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py39hf9fd14e_0.tar.bz2#78ce32061e0be12deb8e0f11ffb76906 +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_2.tar.bz2#306f1a018668f06a0bd89350a3f62c07 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hd477bba_1.tar.bz2#738d009d60cd682df336b6d52c766190 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39h91bfd65_3.tar.bz2#7d10a2e14c08f383baae00e77bf890e5 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 +https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_1.tar.bz2#921f8a7c2a16d18d7168fdac88b2adfe +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-h7acdfc8_2.conda#7ec7d259b6d725ca952d40e2355e192c +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_1.tar.bz2#921f8a7c2a16d18d7168fdac88b2adfe https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_2.tar.bz2#384809c51fb2adc04773f6fa097cd051 -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.11-pyhd8ed1ab_0.tar.bz2#0738978569b10669bdef41c671252dd1 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py39hf3d152e_0.tar.bz2#03225b4745d1dee7bb19d81e41c773a0 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py39hf3d152e_0.tar.bz2#03225b4745d1dee7bb19d81e41c773a0 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c From aa2bfa9f59740027fb4ce4846e419df959f9bc72 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Nov 2022 12:09:59 +0000 Subject: [PATCH 286/319] [pre-commit.ci] pre-commit autoupdate (#5086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0) - [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0) * Fix plot_atlantic_profiles Flake8 E741 ambiguous variable name. (#5087) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- docs/gallery_code/oceanography/plot_atlantic_profiles.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b7746edb43..13f6abe2e8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ minimum_pre_commit_version: 1.21.0 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: # Prevent giant files from being committed. - id: check-added-large-files @@ -36,7 +36,7 @@ repos: args: [--config=./pyproject.toml, .] - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 types: [file, python] diff --git a/docs/gallery_code/oceanography/plot_atlantic_profiles.py b/docs/gallery_code/oceanography/plot_atlantic_profiles.py index dc038ecffe..6604b61ec3 100644 --- a/docs/gallery_code/oceanography/plot_atlantic_profiles.py +++ b/docs/gallery_code/oceanography/plot_atlantic_profiles.py @@ -34,7 +34,7 @@ def main(): # the southern portion of the domain, and limit the depth of the profile # to 1000m. lon_cons = iris.Constraint(longitude=330.5) - lat_cons = iris.Constraint(latitude=lambda l: -10 < l < -9) + lat_cons = iris.Constraint(latitude=lambda lat: -10 < lat < -9) depth_cons = iris.Constraint(depth=lambda d: d <= 1000) theta_1000m = theta.extract(depth_cons & lon_cons & lat_cons) salinity_1000m = salinity.extract(depth_cons & lon_cons & lat_cons) From d9c0743952594a2eaac65981ac9f55aebcba4713 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 1 Dec 2022 11:49:59 +0000 Subject: [PATCH 287/319] Update What's New for 3.4 release. (#5088) * Update What's New for 3.4 release. * Add www.ecmwf.int to linkcheck_ignore. --- docs/src/conf.py | 2 ++ docs/src/whatsnew/3.4.rst | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index 33864c4658..d1ec7bab3b 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -364,6 +364,8 @@ def _dotv(version): "http://www.esrl.noaa.gov/psd/data/gridded/conventions/cdc_netcdf_standard.shtml", "http://www.nationalarchives.gov.uk/doc/open-government-licence", "https://www.metoffice.gov.uk/", + # TODO: try removing this again in future - was raising an SSLError. + "http://www.ecmwf.int/", ] # list of sources to exclude from the build. diff --git a/docs/src/whatsnew/3.4.rst b/docs/src/whatsnew/3.4.rst index f34fd25904..0b3dd3cf94 100644 --- a/docs/src/whatsnew/3.4.rst +++ b/docs/src/whatsnew/3.4.rst @@ -1,7 +1,7 @@ .. include:: ../common_links.inc -v3.4 (17 Nov 2022) [release candidate] -************************************** +v3.4 (01 Dec 2022) +****************** This document explains the changes made to Iris for this release (:doc:`View all changes `.) From dfbc5c2aef0c64918061a4e312f66d94b0beb525 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 5 Dec 2022 14:03:29 +0000 Subject: [PATCH 288/319] Updated environment lockfiles (#5092) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 56 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 54 +++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 56 ++++++++++---------- 3 files changed, 83 insertions(+), 83 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 12353aaca8..efa5b12198 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -13,7 +13,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19. https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.10-3_cp310.conda#4eb33d14d794b0f4be116443ffed3853 -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022f-h191b570_0.tar.bz2#e366350e2343a798e29833286abe2560 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022g-h191b570_0.conda#51fc4fcfb19f5d95ffc8c339db5068e8 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373 @@ -25,7 +25,7 @@ https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_106.conda#d7407e695358f068a2a7f8295cde0567 https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 @@ -57,8 +57,8 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 -https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h166bdaf_0.tar.bz2#d1ad1824c71e67dea42f07e06cd177dc https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -71,23 +71,24 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 -https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hff17c54_1.tar.bz2#2b7dbfa6988a41f9d23ba6d4f0e1d74e https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906 https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-h26416b9_0.tar.bz2#6c531bc30d49ae75b9c7c7f65bd62e3c https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -98,17 +99,18 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#ad https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f -https://conda.anaconda.org/conda-forge/linux-64/python-3.10.8-h257c98d_0_cpython.conda#fa742265350d7f6d664bc13436caf4ad +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 +https://conda.anaconda.org/conda-forge/linux-64/python-3.10.8-h4a9ceb5_0_cpython.conda#be2a6d78752c2ab85f360ce37d2c64e2 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -142,20 +144,18 @@ https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#3427 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h2283fc2_1.tar.bz2#fdca8cd67ec2676f90a70ac73a32538b +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-h67c24c5_1.conda#e1389a8d9a907133b3e6483c2807d243 https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py310h53a5b5f_0.conda#3b114b1559def8bad228fec544ac1812 https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 @@ -189,20 +189,20 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c +https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b5b885341697137879a4f039a9e5a1 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_2.tar.bz2#6bb8063dd08f9724c18744b0e040cfe2 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h2283fc2_1.tar.bz2#9d4149760567cb232691cce2d8ccc21f https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_0.tar.bz2#6b5c2d276f306df759cfbdb0f41c4db9 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c @@ -212,7 +212,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310h454ad03_3.tar https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h4a94279_0.tar.bz2#7a499b94463000c83e349fffb6ce2631 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f @@ -220,11 +220,11 @@ https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_2.tar.bz2#0582a434d03f6b06d5defbb142c96f4f https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5b266fc_2.tar.bz2#c4a3707d6a630facb6cf7ed8e0d37326 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py310hff52083_0.tar.bz2#02600c102a32274e20fc0604ef35af3c +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py310hff52083_0.conda#c6fc5e3f0a463ddb59cfda9a1582cfa0 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py310h597c629_0.tar.bz2#aa5aad596f9d5ef091364c4dad789094 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.4-py310h600f1e7_0.conda#f999dcc21fe27ad97a8afcfa590daa14 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.0-pyhd8ed1ab_0.conda#3a0f020d07998e1ae711df071f97fc19 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a @@ -251,10 +251,10 @@ https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_10 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_1.tar.bz2#8c151d720f9fe3b9962efe71fc10b07b -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-h7acdfc8_2.conda#7ec7d259b6d725ca952d40e2355e192c +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-he99da89_3.conda#b7b364a82ad3ce9e56f0bad77efa9ab1 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a -https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.2-h99bc08f_0.conda#8f247587d1520a2bbc6f79a821b74c07 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_2.tar.bz2#1e2c49215b17e6cf06edf100c9869ebe https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py310hff52083_0.tar.bz2#aa78d12708912cd34135e6694a046ba0 diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index c3a7f19b05..1ea7a64c5a 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -24,7 +24,7 @@ https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_106.conda#d7407e695358f068a2a7f8295cde0567 https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 @@ -56,8 +56,8 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 -https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h166bdaf_0.tar.bz2#d1ad1824c71e67dea42f07e06cd177dc https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -70,23 +70,24 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 -https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hff17c54_1.tar.bz2#2b7dbfa6988a41f9d23ba6d4f0e1d74e https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906 https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-h26416b9_0.tar.bz2#6c531bc30d49ae75b9c7c7f65bd62e3c https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -97,17 +98,18 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#ad https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f -https://conda.anaconda.org/conda-forge/linux-64/python-3.8.15-h257c98d_0_cpython.conda#485151f9b0c1cfb2375b6c4995ac52ba +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 +https://conda.anaconda.org/conda-forge/linux-64/python-3.8.15-h4a9ceb5_0_cpython.conda#dc29a8a79d0f2c80004cc06d3190104f https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -141,20 +143,18 @@ https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#3427 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h2283fc2_1.tar.bz2#fdca8cd67ec2676f90a70ac73a32538b +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-h67c24c5_1.conda#e1389a8d9a907133b3e6483c2807d243 https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_2.tar.bz2#c342a370480791db83d5dd20f2d8899f https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar.bz2#0c469687a517052c0d581fc6e1a4189d https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py38h7042d01_0.conda#d5a3620cd8c1af4115120f21d678507a https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 @@ -188,20 +188,20 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c +https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b5b885341697137879a4f039a9e5a1 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_2.tar.bz2#2276b1f4d1ede3f5f14cc7e4ae6f9a33 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h2283fc2_1.tar.bz2#9d4149760567cb232691cce2d8ccc21f https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_0.tar.bz2#6b5c2d276f306df759cfbdb0f41c4db9 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c @@ -211,7 +211,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h9eb91d8_3.tar. https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h4a94279_0.tar.bz2#7a499b94463000c83e349fffb6ce2631 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 @@ -219,11 +219,11 @@ https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2. https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_2.tar.bz2#dfd81898f0c6e9ee0c22305da6aa443e https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hafd38ec_2.tar.bz2#8df75c6a8c1deac4e99583ec624ff327 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py38h578d9bd_0.tar.bz2#fc6d74114bb0006224f252393bdacee6 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py38h578d9bd_0.conda#d89831246b5ea571858611690c3c75a4 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py38h2b5fc30_0.tar.bz2#218274e4a04630a977b4da2b45eff593 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.4-py38h80a4ca7_0.conda#d3c4698fd7475640f4d9eff8d792deac +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.0-pyhd8ed1ab_0.conda#3a0f020d07998e1ae711df071f97fc19 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a @@ -250,10 +250,10 @@ https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_10 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_1.tar.bz2#38d9029214399e4bfc378b62b0171bf0 -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-h7acdfc8_2.conda#7ec7d259b6d725ca952d40e2355e192c +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-he99da89_3.conda#b7b364a82ad3ce9e56f0bad77efa9ab1 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.2-h99bc08f_0.conda#8f247587d1520a2bbc6f79a821b74c07 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_2.tar.bz2#cfa725eff634872f90dcd5ebf8e8dc1a https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py38h578d9bd_0.tar.bz2#e1a19f0d4686a701d4a4acce2b625acb diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index de49c39a84..565ac6be28 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -13,7 +13,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19. https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf https://conda.anaconda.org/conda-forge/linux-64/python_abi-3.9-3_cp39.conda#0dd193187d54e585cac7eab942a8847e -https://conda.anaconda.org/conda-forge/noarch/tzdata-2022f-h191b570_0.tar.bz2#e366350e2343a798e29833286abe2560 +https://conda.anaconda.org/conda-forge/noarch/tzdata-2022g-h191b570_0.conda#51fc4fcfb19f5d95ffc8c339db5068e8 https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2#f766549260d6815b0c52253f1fb1bb29 https://conda.anaconda.org/conda-forge/linux-64/libgfortran-ng-12.2.0-h69a702a_19.tar.bz2#cd7a806282c16e1f2d39a7e80d3a3e0d https://conda.anaconda.org/conda-forge/linux-64/libgomp-12.2.0-h65d4601_19.tar.bz2#cedcee7c064c01c403f962c9e8d3c373 @@ -25,7 +25,7 @@ https://conda.anaconda.org/conda-forge/linux-64/attr-2.5.1-h166bdaf_1.tar.bz2#d9 https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-h7f98852_4.tar.bz2#a1fd65c7ccbf10880423d82bca54eb54 https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.18.1-h7f98852_0.tar.bz2#f26ef8098fab1f719c91eb760d63381a https://conda.anaconda.org/conda-forge/linux-64/expat-2.5.0-h27087fc_0.tar.bz2#c4fbad8d4bddeb3c085f18cbf97fbfad -https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_105.tar.bz2#9d3e01547ba04a57372beee01158096f +https://conda.anaconda.org/conda-forge/linux-64/fftw-3.3.10-nompi_hf0379b8_106.conda#d7407e695358f068a2a7f8295cde0567 https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.10-h36c2ea0_0.tar.bz2#ac7bc6a654f8f41b352b38f4051135f8 https://conda.anaconda.org/conda-forge/linux-64/geos-3.11.1-h27087fc_0.tar.bz2#917b9a50001fffdd89b321b5dba31e55 https://conda.anaconda.org/conda-forge/linux-64/gettext-0.21.1-h27087fc_0.tar.bz2#14947d8770185e5153fdd04d4673ed37 @@ -57,8 +57,8 @@ https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#f https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 -https://conda.anaconda.org/conda-forge/linux-64/nspr-4.32-h9c3ff4c_1.tar.bz2#29ded371806431b0499aaee146abfc3e -https://conda.anaconda.org/conda-forge/linux-64/openssl-1.1.1s-h166bdaf_0.tar.bz2#e17553617ce05787d97715177be014d1 +https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h166bdaf_0.tar.bz2#d1ad1824c71e67dea42f07e06cd177dc https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -71,23 +71,24 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2d7665abd0997f1a6d4b7596bc27b657 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 -https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h9b69904_4.tar.bz2#390026683aef81db27ff1b8570ca1336 +https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hdcd2b5c_1.tar.bz2#6fe9e31c2b8d0b022626ccac13e6ca3c +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hff17c54_1.tar.bz2#2b7dbfa6988a41f9d23ba6d4f0e1d74e https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f -https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-haa6b8db_3.tar.bz2#89acee135f0809a18a1f4537390aa2dd +https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906 https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.bz2#309dec04b70a3cc0f1e84a4013683bc0 https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 -https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc869a4a_1.tar.bz2#7a268cf1386d271e576e35ae82149ef2 -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-haf5c9bc_0.tar.bz2#0249d755f8d26cb2ac796f9f01cfb823 +https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-h26416b9_0.tar.bz2#6c531bc30d49ae75b9c7c7f65bd62e3c https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 @@ -98,17 +99,18 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#ad https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h3790be6_0.tar.bz2#7d862b05445123144bec92cb1acc8ef8 +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.5-h63197d8_0.tar.bz2#339faf1a5e13c0d4abab84405847ad13 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-h28c427c_0.tar.bz2#455d44a05123f30f66af2ca2a9652b5f -https://conda.anaconda.org/conda-forge/linux-64/python-3.9.15-h47a2c10_0_cpython.conda#4c15ad54369ad2fa36a0d56c6675e241 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 +https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 +https://conda.anaconda.org/conda-forge/linux-64/python-3.9.15-hba424b6_0_cpython.conda#7b9485fce17fac2dd4aca6117a9936c2 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 @@ -142,20 +144,18 @@ https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#3427 https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-he978b8e_1.tar.bz2#5cef21ebd70a90a0d28127543a8d3739 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.5-default_h3a83d3e_0.tar.bz2#ae4ab2853ffd9165ac91e91f64e4539d +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h7bff187_1.tar.bz2#5e5f33d81f31598d87f9b849f73c30e3 -https://conda.anaconda.org/conda-forge/linux-64/libpq-14.5-hd77ab85_1.tar.bz2#f5c8135a70758d928a8126998a6558d8 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h2283fc2_1.tar.bz2#fdca8cd67ec2676f90a70ac73a32538b +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-h67c24c5_1.conda#e1389a8d9a907133b3e6483c2807d243 https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_2.tar.bz2#c678e07e7862b3157fb9f6d908233ffa https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar.bz2#b035b507f55bb6a967d86d4b7e059437 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.78-h2350873_0.tar.bz2#ab3df39f96742e6f1a9878b09274c1dc https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py39h3d75532_0.conda#ea5d332e361eb72c2593cf79559bc0ec https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 @@ -189,20 +189,20 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.10.0-pyhd8ed1ab_0.tar.bz2#cd4eb48ebde7de61f92252979aab515c +https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b5b885341697137879a4f039a9e5a1 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_2.tar.bz2#fc70a133e8162f51e363cff3b6dc741c https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h7bff187_1.tar.bz2#bc9567c50833f4b0d36b25caca7b34f8 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h2283fc2_1.tar.bz2#9d4149760567cb232691cce2d8ccc21f https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h08b82f9_0.tar.bz2#de601caacbaa828d845f758e07e3b85e +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_0.tar.bz2#6b5c2d276f306df759cfbdb0f41c4db9 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.5-default_h2e3cab8_0.tar.bz2#bb1c595d445929e240a806bff0e67d9c +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c @@ -212,7 +212,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hf3a2cdf_3.tar. https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h4a94279_0.tar.bz2#7a499b94463000c83e349fffb6ce2631 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 @@ -220,11 +220,11 @@ https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2. https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_2.tar.bz2#0615ac8191c6ccf7d40860aff645f774 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h76a96b7_2.tar.bz2#10bea68a9dd064b703743d210e679408 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.16.7-py39hf3d152e_0.tar.bz2#242b09f574d87f15a1d1479970037594 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py39hf3d152e_0.conda#a6f9ae6d84b4b233968e20a707935462 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.3-py39hd97740a_0.tar.bz2#be40f2e5698bd0497ddee8a63f8fb4a6 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.11.1-pyhd8ed1ab_0.conda#383ee12e7c9c27adab310a884bc359ab +https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.4-py39h3ccb8fc_0.conda#dee37fde01f9bbc53ec421199d7b17cf +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.0-pyhd8ed1ab_0.conda#3a0f020d07998e1ae711df071f97fc19 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a @@ -251,10 +251,10 @@ https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_10 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_1.tar.bz2#921f8a7c2a16d18d7168fdac88b2adfe -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-h7acdfc8_2.conda#7ec7d259b6d725ca952d40e2355e192c +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-he99da89_3.conda#b7b364a82ad3ce9e56f0bad77efa9ab1 https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf -https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.1-h5abf519_0.tar.bz2#123c55da3e9ea8664f73c70e13ef08c2 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.2-h99bc08f_0.conda#8f247587d1520a2bbc6f79a821b74c07 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_2.tar.bz2#384809c51fb2adc04773f6fa097cd051 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py39hf3d152e_0.tar.bz2#03225b4745d1dee7bb19d81e41c773a0 From 6eb0401011a8edce51a942a176e2feb06f4b3f09 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Fri, 9 Dec 2022 11:13:12 +0000 Subject: [PATCH 289/319] DOC: improve gallery test instructions (#5100) --- .../contributing_documentation_full.rst | 10 ++++++---- docs/src/whatsnew/latest.rst | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/src/developers_guide/contributing_documentation_full.rst b/docs/src/developers_guide/contributing_documentation_full.rst index 41314e80ac..390f2eeea7 100755 --- a/docs/src/developers_guide/contributing_documentation_full.rst +++ b/docs/src/developers_guide/contributing_documentation_full.rst @@ -87,6 +87,8 @@ pattern matching, e.g.:: pytest -v -k plot_coriolis docs/gallery_tests/test_gallery_examples.py +If a gallery test fails, follow the instructions in :ref:`testing.graphics`. + The ``make`` commands shown below can be run in the ``docs`` or ``docs/src`` directory. @@ -165,13 +167,13 @@ The code for the gallery entries are in ``docs/gallery_code``. Each sub directory in this directory is a sub section of the gallery. The respective ``README.rst`` in each folder is included in the gallery output. -For each gallery entry there must be a corresponding test script located in -``docs/gallery_tests``. - To add an entry to the gallery simple place your python code into the appropriate sub directory and name it with a prefix of ``plot_``. If your gallery entry does not fit into any existing sub directories then create a new -directory and place it in there. +directory and place it in there. A test for the gallery entry will be +automatically generated (see Testing_ for how to run it). To add a new +reference image for this test, follow the instructions in +:ref:`testing.graphics`. The reStructuredText (rst) output of the gallery is located in ``docs/src/generated/gallery``. diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 6a9228a8be..ca4f2535f8 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -67,7 +67,7 @@ This document explains the changes made to Iris for this release 📚 Documentation ================ -#. N/A +#. `@rcomer`_ clarified instructions for updating gallery tests. (:pull:`5100`) 💼 Internal From bf33b029379a9a88d1920ed53c9cfa23b44e3607 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 12 Dec 2022 11:47:54 +0000 Subject: [PATCH 290/319] Updated environment lockfiles (#5104) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 26 ++++++++++---------- requirements/ci/nox.lock/py38-linux-64.lock | 26 ++++++++++---------- requirements/ci/nox.lock/py39-linux-64.lock | 26 ++++++++++---------- 3 files changed, 39 insertions(+), 39 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index efa5b12198..81a1661c1e 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -3,7 +3,7 @@ # input_hash: 65e8c3d4ababc804f8d3715a14ce94c3f564a37860525f35ea1aed69efd67be8 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -54,11 +54,11 @@ https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.1-h27087fc_0.tar.bz2#0af513b75f78a701a152568a31303bdf https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 -https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h166bdaf_0.tar.bz2#d1ad1824c71e67dea42f07e06cd177dc +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_1.conda#7adaac6ff98219bcb99b45e408b80f4e https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -105,7 +105,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 @@ -122,7 +122,7 @@ https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310 https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf @@ -134,7 +134,7 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3.tar.bz2#785160da087cf1d70e989afbb761f01c https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.2-pyhd8ed1ab_0.conda#0f09c2bc17ddd8732be8e5b99297c7ce https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 @@ -158,7 +158,8 @@ https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py310h53a5b5f_0.conda#3b114b1559def8bad228fec544ac1812 https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 +https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.0-pyhd8ed1ab_0.conda#b1b2ab02d1ece1719f7fa002ad4bc70d https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f @@ -206,7 +207,6 @@ https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310h454ad03_3.tar.bz2#eb354ff791f505b1d6f13f776359d88e https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d @@ -214,11 +214,13 @@ https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2.tar.bz2#61e2f2f7befaf45f47d1da449a9a0aca https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_2.tar.bz2#0582a434d03f6b06d5defbb142c96f4f https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5b266fc_2.tar.bz2#c4a3707d6a630facb6cf7ed8e0d37326 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py310hd8f1fbe_0.conda#765b39936044b542a69ec2d863f5b891 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py310hff52083_0.conda#c6fc5e3f0a463ddb59cfda9a1582cfa0 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf @@ -232,9 +234,9 @@ https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py310h8d5ebf3_0.tar.bz2#da51ddb20c0f99d672eb756c3abf27e7 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py310h769672d_0.conda#bc363997d22f3b058fb17f1e89d4c96f https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_2.tar.bz2#e1648c222911ad7559d62831e4bc447c -https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_2.tar.bz2#0d815f1b2258d3d4c17cc80fd01e0f36 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py310hd8f1fbe_0.conda#765b39936044b542a69ec2d863f5b891 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310h83f2385_3.tar.bz2#4ec35f7eebe4221c1c00fdd6540db4dc @@ -242,11 +244,9 @@ https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h55e1e36_102.tar.bz2#588d5bd8f16287b766c509ef173b892d +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h0a86a1f_103.conda#7f69695b684f2595d9ba1ce26d693b7d https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_2.tar.bz2#0d815f1b2258d3d4c17cc80fd01e0f36 -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 1ea7a64c5a..0836e3d018 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -3,7 +3,7 @@ # input_hash: dc794a12a2155d2a605b34fc34ece8039a0f0d43fbf7d304366cf8c33cf94cd1 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -53,11 +53,11 @@ https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.1-h27087fc_0.tar.bz2#0af513b75f78a701a152568a31303bdf https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 -https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h166bdaf_0.tar.bz2#d1ad1824c71e67dea42f07e06cd177dc +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_1.conda#7adaac6ff98219bcb99b45e408b80f4e https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -104,7 +104,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 @@ -121,7 +121,7 @@ https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf @@ -133,7 +133,7 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.2-pyhd8ed1ab_0.conda#0f09c2bc17ddd8732be8e5b99297c7ce https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 @@ -157,7 +157,8 @@ https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar. https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py38h7042d01_0.conda#d5a3620cd8c1af4115120f21d678507a https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 +https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.0-pyhd8ed1ab_0.conda#b1b2ab02d1ece1719f7fa002ad4bc70d https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py38h0a891b7_0.tar.bz2#fe2ef279417faa1af0adf178de2032f7 @@ -205,7 +206,6 @@ https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h9eb91d8_3.tar.bz2#61dc7b3140b7b79b1985b53d52726d74 https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d @@ -213,11 +213,13 @@ https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2.tar.bz2#d30399a3c636c75cfd3460c92effa960 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_2.tar.bz2#dfd81898f0c6e9ee0c22305da6aa443e https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hafd38ec_2.tar.bz2#8df75c6a8c1deac4e99583ec624ff327 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py38hfa26641_0.conda#7be81814bae276dc7b4c707cf1e8186b https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py38h578d9bd_0.conda#d89831246b5ea571858611690c3c75a4 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759 @@ -231,9 +233,9 @@ https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py38hb021067_0.tar.bz2#72422499195d8aded0dfd461c6e3e86f https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py38h8f669ce_0.conda#dbc17622f9d159be987bd21959d5494e https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hce0a2d1_2.tar.bz2#be61a535f279bffdf7f449a654eaa19d -https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_2.tar.bz2#ad6437509a14f1e8e5b8a354f93f340c +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py38hfa26641_0.conda#7be81814bae276dc7b4c707cf1e8186b https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar.bz2#82b3797d08a43a101b645becbb938e65 https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38hf6c3373_3.tar.bz2#1dc477fef9b0b1080af3e7c7ecb4aff7 @@ -241,11 +243,9 @@ https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h2a9f00d_102.tar.bz2#533ae5db3e2367d71a7890efb0aa3cdc +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h6b4b75c_103.conda#ea3d2204fc3a7db7d831daa437a58717 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_2.tar.bz2#ad6437509a14f1e8e5b8a354f93f340c -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index 565ac6be28..f3f071da34 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -3,7 +3,7 @@ # input_hash: 8720b47771aff1b233330a6562a535e5ad3a153a023d02d4dc71b383a25796a3 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 -https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.9.24-ha878542_0.tar.bz2#41e4e87062433e283696cf384f952ef6 +https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2#0c96522c6bdaed4b1566d11387caaf45 https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb @@ -54,11 +54,11 @@ https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.30.2-h27087fc_1.tar.bz2#2fe2a839394ef3a1825a5e5e296060bc +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.1-h27087fc_0.tar.bz2#0af513b75f78a701a152568a31303bdf https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 -https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h166bdaf_0.tar.bz2#d1ad1824c71e67dea42f07e06cd177dc +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_1.conda#7adaac6ff98219bcb99b45e408b80f4e https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -105,7 +105,7 @@ https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar. https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-h27087fc_0.tar.bz2#02fa0b56a57c8421d1195bf0c021e682 +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 @@ -122,7 +122,7 @@ https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39h https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 -https://conda.anaconda.org/conda-forge/noarch/certifi-2022.9.24-pyhd8ed1ab_0.tar.bz2#f66309b099374af91369e67e84af397d +https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf @@ -134,7 +134,7 @@ https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.0-pyhd8ed1ab_0.tar.bz2#10f0218dbd493ab2e5dc6759ddea4526 +https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.2-pyhd8ed1ab_0.conda#0f09c2bc17ddd8732be8e5b99297c7ce https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 @@ -158,7 +158,8 @@ https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar. https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py39h3d75532_0.conda#ea5d332e361eb72c2593cf79559bc0ec https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.5.2-pyhd8ed1ab_1.tar.bz2#2fb3f88922e7aec26ba652fcdfe13950 +https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.0-pyhd8ed1ab_0.conda#b1b2ab02d1ece1719f7fa002ad4bc70d https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py39hb9d737c_0.tar.bz2#12184951da572828fb986b06ffb63eed @@ -206,7 +207,6 @@ https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8 https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c -https://conda.anaconda.org/conda-forge/noarch/packaging-21.3-pyhd8ed1ab_0.tar.bz2#71f1ab2de48613876becddd496371c85 https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hf3a2cdf_3.tar.bz2#2bd111c38da69056e5fe25a51b832eba https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d @@ -214,11 +214,13 @@ https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2.tar.bz2#234ad9828eca1caf0f2fdcb4a24ad816 https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_2.tar.bz2#0615ac8191c6ccf7d40860aff645f774 https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h76a96b7_2.tar.bz2#10bea68a9dd064b703743d210e679408 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py39h5a03fae_0.conda#c3eb463691a8b93f1c381a9e56ecad9a https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py39hf3d152e_0.conda#a6f9ae6d84b4b233968e20a707935462 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045 @@ -232,9 +234,9 @@ https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py39hf9fd14e_0.tar.bz2#78ce32061e0be12deb8e0f11ffb76906 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py39h4661b88_0.conda#e17e50269c268d79478956a262a9fe13 https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h14a8356_2.tar.bz2#5d93c781338ff274a0b3dc3d901e19a6 -https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_2.tar.bz2#306f1a018668f06a0bd89350a3f62c07 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py39h5a03fae_0.conda#c3eb463691a8b93f1c381a9e56ecad9a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar.bz2#0f11bcdf9669a5ae0f39efd8c830209a https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39h91bfd65_3.tar.bz2#7d10a2e14c08f383baae00e77bf890e5 @@ -242,11 +244,9 @@ https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h6ced12a_102.tar.bz2#b92600d0fef7f12f426935d87d6413e6 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h94a714e_103.conda#ee29e7176b5854fa09ec17b101945401 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_2.tar.bz2#306f1a018668f06a0bd89350a3f62c07 -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.0.2-pyhd8ed1ab_0.tar.bz2#18bdfe034d1187a34d860ed8e6fec788 https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a From efd356deeaaaa35ed0d54a3907f5a7eaa1841edf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 09:52:52 +0000 Subject: [PATCH 291/319] [pre-commit.ci] pre-commit autoupdate (#5107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 13f6abe2e8..a4cd9359ea 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: no-commit-to-branch - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black pass_filenames: false From 4a65b9eed416cc8f6a45a1eb2d35f27ed67d0da8 Mon Sep 17 00:00:00 2001 From: Francesco Nattino <49899980+fnattino@users.noreply.github.com> Date: Wed, 14 Dec 2022 12:49:36 +0100 Subject: [PATCH 292/319] Switch order of options and parameter in `ncgen` command (#5105) * switch order of options and parameter in ncgen * add info to whatsnew --- docs/src/whatsnew/latest.rst | 5 +++-- lib/iris/tests/stock/netcdf.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index ca4f2535f8..bf5e942601 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -73,14 +73,15 @@ This document explains the changes made to Iris for this release 💼 Internal =========== -#. N/A +#. `@fnattino`_ changed the order of ``ncgen`` arguments in the command to + create NetCDF files for testing (caused errors on OS X). (:pull:`5105`) .. comment Whatsnew author names (@github name) in alphabetical order. Note that, core dev names are automatically included by the common_links.inc: - +.. _@fnattino: https://github.com/fnattino .. comment diff --git a/lib/iris/tests/stock/netcdf.py b/lib/iris/tests/stock/netcdf.py index 8a448f7d34..e32f065625 100644 --- a/lib/iris/tests/stock/netcdf.py +++ b/lib/iris/tests/stock/netcdf.py @@ -51,7 +51,7 @@ def ncgen_from_cdl( f_out.write(cdl_str) if cdl_path: # Create netcdf from stored CDL file. - call_args = [NCGEN_PATHSTR, cdl_path, "-k3", "-o", nc_path] + call_args = [NCGEN_PATHSTR, "-k3", "-o", nc_path, cdl_path] call_kwargs = {} else: # No CDL file : pipe 'cdl_str' directly into the ncgen program. From 5555d455df4bfdbaec607e8a8da439ef8663dc1e Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 14 Dec 2022 16:41:19 +0000 Subject: [PATCH 293/319] Remove test timings (#5101) * remove test timings * whatsnew * missed a bit * review action --- docs/src/whatsnew/latest.rst | 3 + lib/iris/tests/__init__.py | 82 +------------------ lib/iris/tests/graphics/README.md | 2 +- lib/iris/tests/test_plot.py | 6 +- .../maths/test__arith__derived_coords.py | 3 +- .../analysis/maths/test__arith__meshcoords.py | 6 +- .../tests/unit/analysis/maths/test_add.py | 11 +-- .../tests/unit/analysis/maths/test_divide.py | 8 +- .../unit/analysis/maths/test_multiply.py | 11 +-- .../unit/analysis/maths/test_subtract.py | 11 +-- lib/iris/tests/unit/merge/test_ProtoCube.py | 42 +++------- lib/iris/tests/unit/tests/test_IrisTest.py | 8 +- 12 files changed, 35 insertions(+), 158 deletions(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index bf5e942601..f3406cc831 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -76,6 +76,9 @@ This document explains the changes made to Iris for this release #. `@fnattino`_ changed the order of ``ncgen`` arguments in the command to create NetCDF files for testing (caused errors on OS X). (:pull:`5105`) +#. `@rcomer`_ removed some old infrastructure that printed test timings. + (:pull:`5101`) + .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/tests/__init__.py b/lib/iris/tests/__init__.py index 4840de8cdb..5529b899c5 100644 --- a/lib/iris/tests/__init__.py +++ b/lib/iris/tests/__init__.py @@ -16,7 +16,6 @@ import collections from collections.abc import Mapping import contextlib -import datetime import difflib import filecmp import functools @@ -208,7 +207,7 @@ def assert_masked_array_almost_equal(a, b, decimal=6, strict=False): ) -class IrisTest_nometa(unittest.TestCase): +class IrisTest(unittest.TestCase): """A subclass of unittest.TestCase which provides Iris specific testing functionality.""" _assertion_counts = collections.defaultdict(int) @@ -937,80 +936,6 @@ def assertEqualAndKind(self, value, expected): ) -# An environment variable controls whether test timings are output. -# -# NOTE: to run tests with timing output, nosetests cannot be used. -# At present, that includes not using "python setup.py test" -# The typically best way is like this : -# $ export IRIS_TEST_TIMINGS=1 -# $ python -m unittest discover -s iris.tests -# and commonly adding ... -# | grep "TIMING TEST" >iris_test_output.txt -# -_PRINT_TEST_TIMINGS = bool(int(os.environ.get("IRIS_TEST_TIMINGS", 0))) - - -def _method_path(meth, cls): - return ".".join([cls.__module__, cls.__name__, meth.__name__]) - - -def _testfunction_timing_decorator(fn, cls): - # Function decorator for making a testcase print its execution time. - @functools.wraps(fn) - def inner(*args, **kwargs): - start_time = datetime.datetime.now() - try: - result = fn(*args, **kwargs) - finally: - end_time = datetime.datetime.now() - elapsed_time = (end_time - start_time).total_seconds() - msg = '\n TEST TIMING -- "{}" took : {:12.6f} sec.' - name = _method_path(fn, cls) - print(msg.format(name, elapsed_time)) - return result - - return inner - - -def iristest_timing_decorator(cls): - # Class decorator to make all "test_.." functions print execution timings. - if _PRINT_TEST_TIMINGS: - # NOTE: 'dir' scans *all* class properties, including inherited ones. - attr_names = dir(cls) - for attr_name in attr_names: - attr = getattr(cls, attr_name) - if callable(attr) and attr_name.startswith("test"): - attr = _testfunction_timing_decorator(attr, cls) - setattr(cls, attr_name, attr) - return cls - - -class _TestTimingsMetaclass(type): - # An alternative metaclass for IrisTest subclasses, which makes - # them print execution timings for all the testcases. - # This is equivalent to applying the @iristest_timing_decorator to - # every test class that inherits from IrisTest. - # NOTE: however, it means you *cannot* specify a different metaclass for - # your test class inheriting from IrisTest. - # See below for how to solve that where needed. - def __new__(cls, clsname, base_classes, attrs): - result = type.__new__(cls, clsname, base_classes, attrs) - if _PRINT_TEST_TIMINGS: - result = iristest_timing_decorator(result) - return result - - -class IrisTest(IrisTest_nometa, metaclass=_TestTimingsMetaclass): - # Derive the 'ordinary' IrisTest from IrisTest_nometa, but add the - # metaclass that enables test timings output. - # This means that all subclasses also get the timing behaviour. - # However, if a different metaclass is *wanted* for an IrisTest subclass, - # this would cause a metaclass conflict. - # Instead, you can inherit from IrisTest_nometa and apply the - # @iristest_timing_decorator explicitly to your new testclass. - pass - - get_data_path = IrisTest.get_data_path get_result_path = IrisTest.get_result_path @@ -1019,11 +944,6 @@ class GraphicsTest(graphics.GraphicsTestMixin, IrisTest): pass -class GraphicsTest_nometa(graphics.GraphicsTestMixin, IrisTest_nometa): - # Graphicstest without the metaclass providing test timings. - pass - - def skip_data(fn): """ Decorator to choose whether to run tests, based on the availability of diff --git a/lib/iris/tests/graphics/README.md b/lib/iris/tests/graphics/README.md index b26f1720e8..069fc01f70 100755 --- a/lib/iris/tests/graphics/README.md +++ b/lib/iris/tests/graphics/README.md @@ -24,7 +24,7 @@ perceived as it may be a simple pixel shift. ## Testing Strategy -The `iris.tests.IrisTest_nometa.check_graphic` test routine calls out to +The `iris.tests.IrisTest.check_graphic` test routine calls out to `iris.tests.graphics.check_graphic` which tests against the **acceptable** result. It does this using an image **hash** comparison technique which allows us to be robust against minor variations based on underlying library updates. diff --git a/lib/iris/tests/test_plot.py b/lib/iris/tests/test_plot.py index 77aea2b6b6..c9eba31e58 100644 --- a/lib/iris/tests/test_plot.py +++ b/lib/iris/tests/test_plot.py @@ -712,9 +712,8 @@ def override_with_decorated_methods(attr_dict, target_dict, decorator): @tests.skip_data -@tests.iristest_timing_decorator class TestPcolorNoBounds( - tests.GraphicsTest_nometa, SliceMixin, metaclass=CheckForWarningsMetaclass + tests.GraphicsTest, SliceMixin, metaclass=CheckForWarningsMetaclass ): """ Test the iris.plot.pcolor routine on a cube with coordinates @@ -729,9 +728,8 @@ def setUp(self): @tests.skip_data -@tests.iristest_timing_decorator class TestPcolormeshNoBounds( - tests.GraphicsTest_nometa, SliceMixin, metaclass=CheckForWarningsMetaclass + tests.GraphicsTest, SliceMixin, metaclass=CheckForWarningsMetaclass ): """ Test the iris.plot.pcolormesh routine on a cube with coordinates diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py b/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py index 51f71affb0..57e012e1c9 100644 --- a/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py +++ b/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py @@ -16,9 +16,8 @@ @tests.skip_data -@tests.iristest_timing_decorator class TestBroadcastingDerived( - tests.IrisTest_nometa, + tests.IrisTest, MathsAddOperationMixin, CubeArithmeticBroadcastingTestMixin, ): diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py b/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py index 1d81e7b480..e1255ef9d8 100644 --- a/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py +++ b/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py @@ -54,9 +54,8 @@ def _base_testcube(self): @tests.skip_data -@tests.iristest_timing_decorator class TestBroadcastingWithMesh( - tests.IrisTest_nometa, + tests.IrisTest, MeshLocationsMixin, MathsAddOperationMixin, CubeArithmeticBroadcastingTestMixin, @@ -71,9 +70,8 @@ class TestBroadcastingWithMesh( @tests.skip_data -@tests.iristest_timing_decorator class TestBroadcastingWithMeshAndDerived( - tests.IrisTest_nometa, + tests.IrisTest, MeshLocationsMixin, MathsAddOperationMixin, CubeArithmeticBroadcastingTestMixin, diff --git a/lib/iris/tests/unit/analysis/maths/test_add.py b/lib/iris/tests/unit/analysis/maths/test_add.py index 77dd7877bf..1ca7f7c244 100644 --- a/lib/iris/tests/unit/analysis/maths/test_add.py +++ b/lib/iris/tests/unit/analysis/maths/test_add.py @@ -21,10 +21,7 @@ @tests.skip_data -@tests.iristest_timing_decorator -class TestBroadcasting( - tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin -): +class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): @property def data_op(self): return operator.add @@ -34,8 +31,7 @@ def cube_func(self): return add -@tests.iristest_timing_decorator -class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin): +class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): @property def data_op(self): return operator.add @@ -57,9 +53,8 @@ def test_reversed_points(self): add(cube1, cube2) -@tests.iristest_timing_decorator class TestMaskedConstant( - tests.IrisTest_nometa, CubeArithmeticMaskedConstantTestMixin + tests.IrisTest, CubeArithmeticMaskedConstantTestMixin ): @property def data_op(self): diff --git a/lib/iris/tests/unit/analysis/maths/test_divide.py b/lib/iris/tests/unit/analysis/maths/test_divide.py index 1763f223b0..4bd202e037 100644 --- a/lib/iris/tests/unit/analysis/maths/test_divide.py +++ b/lib/iris/tests/unit/analysis/maths/test_divide.py @@ -23,10 +23,7 @@ @tests.skip_data -@tests.iristest_timing_decorator -class TestBroadcasting( - tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin -): +class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): @property def data_op(self): return operator.truediv @@ -36,8 +33,7 @@ def cube_func(self): return divide -@tests.iristest_timing_decorator -class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin): +class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): @property def data_op(self): return operator.truediv diff --git a/lib/iris/tests/unit/analysis/maths/test_multiply.py b/lib/iris/tests/unit/analysis/maths/test_multiply.py index 600593c64b..266342605a 100644 --- a/lib/iris/tests/unit/analysis/maths/test_multiply.py +++ b/lib/iris/tests/unit/analysis/maths/test_multiply.py @@ -21,10 +21,7 @@ @tests.skip_data -@tests.iristest_timing_decorator -class TestBroadcasting( - tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin -): +class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): @property def data_op(self): return operator.mul @@ -34,8 +31,7 @@ def cube_func(self): return multiply -@tests.iristest_timing_decorator -class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin): +class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): @property def data_op(self): return operator.mul @@ -57,9 +53,8 @@ def test_reversed_points(self): multiply(cube1, cube2) -@tests.iristest_timing_decorator class TestMaskedConstant( - tests.IrisTest_nometa, CubeArithmeticMaskedConstantTestMixin + tests.IrisTest, CubeArithmeticMaskedConstantTestMixin ): @property def data_op(self): diff --git a/lib/iris/tests/unit/analysis/maths/test_subtract.py b/lib/iris/tests/unit/analysis/maths/test_subtract.py index 964e8c04c7..f7a9df34d0 100644 --- a/lib/iris/tests/unit/analysis/maths/test_subtract.py +++ b/lib/iris/tests/unit/analysis/maths/test_subtract.py @@ -21,10 +21,7 @@ @tests.skip_data -@tests.iristest_timing_decorator -class TestBroadcasting( - tests.IrisTest_nometa, CubeArithmeticBroadcastingTestMixin -): +class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): @property def data_op(self): return operator.sub @@ -34,8 +31,7 @@ def cube_func(self): return subtract -@tests.iristest_timing_decorator -class TestMasking(tests.IrisTest_nometa, CubeArithmeticMaskingTestMixin): +class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): @property def data_op(self): return operator.sub @@ -57,9 +53,8 @@ def test_reversed_points(self): subtract(cube1, cube2) -@tests.iristest_timing_decorator class TestMaskedConstant( - tests.IrisTest_nometa, CubeArithmeticMaskedConstantTestMixin + tests.IrisTest, CubeArithmeticMaskedConstantTestMixin ): @property def data_op(self): diff --git a/lib/iris/tests/unit/merge/test_ProtoCube.py b/lib/iris/tests/unit/merge/test_ProtoCube.py index 31b1efb3fd..625290ad24 100644 --- a/lib/iris/tests/unit/merge/test_ProtoCube.py +++ b/lib/iris/tests/unit/merge/test_ProtoCube.py @@ -77,8 +77,7 @@ def test_error(self): self.assertTrue(result) -@tests.iristest_timing_decorator -class Test_register__match(Mixin_register, tests.IrisTest_nometa): +class Test_register__match(Mixin_register, tests.IrisTest): @property def fragments(self): return [] @@ -88,8 +87,7 @@ def cube2(self): return example_cube() -@tests.iristest_timing_decorator -class Test_register__standard_name(Mixin_register, tests.IrisTest_nometa): +class Test_register__standard_name(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.standard_name", "air_temperature", "air_density"] @@ -101,8 +99,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__long_name(Mixin_register, tests.IrisTest_nometa): +class Test_register__long_name(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.long_name", "screen_air_temp", "Belling"] @@ -114,8 +111,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__var_name(Mixin_register, tests.IrisTest_nometa): +class Test_register__var_name(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.var_name", "'airtemp'", "'airtemp2'"] @@ -127,8 +123,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__units(Mixin_register, tests.IrisTest_nometa): +class Test_register__units(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.units", "'K'", "'C'"] @@ -140,8 +135,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__attributes_unequal(Mixin_register, tests.IrisTest_nometa): +class Test_register__attributes_unequal(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.attributes", "'mint'"] @@ -153,10 +147,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__attributes_unequal_array( - Mixin_register, tests.IrisTest_nometa -): +class Test_register__attributes_unequal_array(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.attributes", "'mint'"] @@ -174,10 +165,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__attributes_superset( - Mixin_register, tests.IrisTest_nometa -): +class Test_register__attributes_superset(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.attributes", "'stuffed'"] @@ -189,10 +177,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__attributes_multi_diff( - Mixin_register, tests.IrisTest_nometa -): +class Test_register__attributes_multi_diff(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.attributes", "'sam'", "'mint'"] @@ -215,8 +200,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__cell_method(Mixin_register, tests.IrisTest_nometa): +class Test_register__cell_method(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.cell_methods"] @@ -228,8 +212,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__data_shape(Mixin_register, tests.IrisTest_nometa): +class Test_register__data_shape(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube.shape", "(2,)", "(3,)"] @@ -241,8 +224,7 @@ def cube2(self): return cube -@tests.iristest_timing_decorator -class Test_register__data_dtype(Mixin_register, tests.IrisTest_nometa): +class Test_register__data_dtype(Mixin_register, tests.IrisTest): @property def fragments(self): return ["cube data dtype", "int32", "int8"] diff --git a/lib/iris/tests/unit/tests/test_IrisTest.py b/lib/iris/tests/unit/tests/test_IrisTest.py index 5725b59d40..10de2a7760 100644 --- a/lib/iris/tests/unit/tests/test_IrisTest.py +++ b/lib/iris/tests/unit/tests/test_IrisTest.py @@ -66,8 +66,7 @@ def test_different_mask_nonstrict(self): self._func(self.arr1, arr2, strict=False) -@tests.iristest_timing_decorator -class Test_assertMaskedArrayEqual(_MaskedArrayEquality, tests.IrisTest_nometa): +class Test_assertMaskedArrayEqual(_MaskedArrayEquality, tests.IrisTest): @property def _func(self): return self.assertMaskedArrayEqual @@ -114,10 +113,7 @@ def test_masked_nonmasked_same_emptymask(self): self.assertMaskedArrayEqual(arr1, arr2) -@tests.iristest_timing_decorator -class Test_assertMaskedArrayAlmostEqual( - _MaskedArrayEquality, tests.IrisTest_nometa -): +class Test_assertMaskedArrayAlmostEqual(_MaskedArrayEquality, tests.IrisTest): @property def _func(self): return self.assertMaskedArrayAlmostEqual From d629dcc41c7ba5fd6bb095a8f9ddf1fb6640a5ee Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 16 Dec 2022 15:14:24 +0000 Subject: [PATCH 294/319] Announce @ESadek-MO as a core Iris developer. (#5111) --- docs/src/whatsnew/latest.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index f3406cc831..d1aae76675 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -25,7 +25,7 @@ This document explains the changes made to Iris for this release 📢 Announcements ================ -#. N/A +#. Congratulations to `@ESadek-MO`_ who has become a core developer for Iris! 🎉 ✨ Features From 8762fab92f3586072491654eb033c94e6d37f602 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Fri, 16 Dec 2022 15:48:54 +0000 Subject: [PATCH 295/319] Correct heading for v3.4 release highlights. (#5110) * Correct heading for v3.4 release highlights. * Include patch number in release highlights heading. Co-authored-by: lbdreyer Co-authored-by: lbdreyer --- docs/src/whatsnew/3.4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/whatsnew/3.4.rst b/docs/src/whatsnew/3.4.rst index 0b3dd3cf94..1ad676c049 100644 --- a/docs/src/whatsnew/3.4.rst +++ b/docs/src/whatsnew/3.4.rst @@ -7,7 +7,7 @@ This document explains the changes made to Iris for this release (:doc:`View all changes `.) -.. dropdown:: :opticon:`report` |iris_version| Release Highlights +.. dropdown:: :opticon:`report` v3.4.0 Release Highlights :container: + shadow :title: text-primary text-center font-weight-bold :body: bg-light From a0f7af7b101925d6f95f97fea0ef72f81857924f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Dec 2022 09:32:57 +0000 Subject: [PATCH 296/319] [pre-commit.ci] pre-commit autoupdate (#5114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.10.1 → v5.11.3](https://github.com/pycqa/isort/compare/5.10.1...v5.11.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4cd9359ea..dbc74dbb18 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: args: [--config=./setup.cfg] - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: v5.11.3 hooks: - id: isort types: [file, python] From 290fe70395726c33e42e158a67f500505fa79534 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Dec 2022 10:21:45 +0000 Subject: [PATCH 297/319] Bump actions/stale from 6 to 7 (#5117) Bumps [actions/stale](https://github.com/actions/stale) from 6 to 7. - [Release notes](https://github.com/actions/stale/releases) - [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/stale/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/stale dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index c65f37284f..44b77e5c7d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -14,7 +14,7 @@ jobs: if: "github.repository == 'SciTools/iris'" runs-on: ubuntu-latest steps: - - uses: actions/stale@v6 + - uses: actions/stale@v7 with: repo-token: ${{ secrets.GITHUB_TOKEN }} From f6e4b8133fbaf1044d06663607316ee535286ebd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 09:43:30 +0000 Subject: [PATCH 298/319] [pre-commit.ci] pre-commit autoupdate (#5120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: v5.11.3 → 5.11.4](https://github.com/pycqa/isort/compare/v5.11.3...5.11.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbc74dbb18..ad5a9d4626 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: args: [--config=./setup.cfg] - repo: https://github.com/pycqa/isort - rev: v5.11.3 + rev: 5.11.4 hooks: - id: isort types: [file, python] From fc302c9c08c292cb2075d2dd249bcbdfacf08da8 Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 3 Jan 2023 13:29:08 +0000 Subject: [PATCH 299/319] pip pin for sphinx<5 (#5122) --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index f6276cb173..75647e6623 100644 --- a/setup.cfg +++ b/setup.cfg @@ -69,7 +69,7 @@ where = lib [options.extras_require] docs = - sphinx + sphinx<5 sphinx-copybutton sphinx-gallery>=0.11.0 sphinx_rtd_theme From f190415d154adc4ab740efbf65470b4cf6b27683 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 4 Jan 2023 18:02:06 +0000 Subject: [PATCH 300/319] link percentile as lazy option (#5128) --- docs/src/whatsnew/latest.rst | 4 ++++ lib/iris/analysis/__init__.py | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index d1aae76675..2029e24855 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -69,6 +69,10 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ clarified instructions for updating gallery tests. (:pull:`5100`) +#. `@rcomer`_ linked the :obj:`~iris.analysis.PERCENTILE` aggregator from the + :obj:`~iris.analysis.MEDIAN` docstring, noting that the former handles lazy + data. (:pull:`5128`) + 💼 Internal =========== diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index c0f9b5b0f8..55d5d5d93e 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -1914,7 +1914,8 @@ def interp_order(length): result = cube.collapsed('longitude', iris.analysis.MEDIAN) -This aggregator handles masked data, but NOT lazy data. +This aggregator handles masked data, but NOT lazy data. For lazy aggregation, +please try :obj:`~.PERCENTILE`. """ From e1fae5ea0a163bbcfeb23f2055996b4be4cf47ac Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Thu, 5 Jan 2023 10:33:49 +0000 Subject: [PATCH 301/319] Unpin theme (#5129) * unpin theme and set default style to light (not dark) * added whatsnew and fixed typo. * minor tweaks --- docs/src/_templates/layout.html | 2 +- docs/src/conf.py | 6 ++++++ docs/src/whatsnew/latest.rst | 3 +++ requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/src/_templates/layout.html b/docs/src/_templates/layout.html index 9a6fe46d01..974bd12753 100644 --- a/docs/src/_templates/layout.html +++ b/docs/src/_templates/layout.html @@ -11,7 +11,7 @@
    You are viewing the latest unreleased documentation v{{ version }}. You can switch to a stable version - via the flyout menu in the bottom-right of the screen. + via the flyout menu in the bottom corner of the screen.

    {%- endif %} diff --git a/docs/src/conf.py b/docs/src/conf.py index d1ec7bab3b..ae5a32e8a3 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -310,6 +310,9 @@ def _dotv(version): ], "use_edit_page_button": True, "show_toc_level": 1, + # Omitted `theme-switcher` below to disable it + # Info: https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/light-dark.html#configure-default-theme-mode + "navbar_end": ["navbar-icon-links"], } rev_parse = run(["git", "rev-parse", "--short", "HEAD"], capture_output=True) @@ -321,6 +324,9 @@ def _dotv(version): "github_user": "scitools", "github_version": "main", "doc_path": "docs/src", + # default theme. Also disabled the button in the html_theme_options. + # Info: https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/light-dark.html#configure-default-theme-mode + "default_mode": "light", # custom "on_rtd": on_rtd, "rtd_version": rtd_version, diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 2029e24855..cf4b646841 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -68,6 +68,9 @@ This document explains the changes made to Iris for this release ================ #. `@rcomer`_ clarified instructions for updating gallery tests. (:pull:`5100`) +#. `@tkknight`_ unpinned ``pydata-sphinx-theme`` and set the default to use + the light version (not dark) while we make the docs dark mode friendly + (:pull:`5129`) #. `@rcomer`_ linked the :obj:`~iris.analysis.PERCENTILE` aggregator from the :obj:`~iris.analysis.MEDIAN` docstring, noting that the former handles lazy diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 6815c7fe6d..d79015c055 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -48,7 +48,7 @@ dependencies: - sphinx-copybutton - sphinx-gallery >=0.11.0 - sphinx-panels - - pydata-sphinx-theme = 0.8.1 + - pydata-sphinx-theme # Temporary minimum pins. # See https://github.com/SciTools/iris/pull/5051 diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index 316e0868ac..b68e8ccf45 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -48,7 +48,7 @@ dependencies: - sphinx-copybutton - sphinx-gallery >=0.11.0 - sphinx-panels - - pydata-sphinx-theme = 0.8.1 + - pydata-sphinx-theme # Temporary minimum pins. # See https://github.com/SciTools/iris/pull/5051 diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 66e22c230f..9fec76cfde 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -48,7 +48,7 @@ dependencies: - sphinx-copybutton - sphinx-gallery >=0.11.0 - sphinx-panels - - pydata-sphinx-theme = 0.8.1 + - pydata-sphinx-theme # Temporary minimum pins. # See https://github.com/SciTools/iris/pull/5051 From 34560a5c7352878086b2012e651472936916a19a Mon Sep 17 00:00:00 2001 From: tkknight <2108488+tkknight@users.noreply.github.com> Date: Fri, 6 Jan 2023 09:57:56 +0000 Subject: [PATCH 302/319] Spelling remove (#5130) * remove spelling config and docs, this didnt pan out. * added line break --- docs/Makefile | 5 - docs/src/Makefile | 8 +- docs/src/conf.py | 12 - .../contributing_documentation_full.rst | 12 - docs/src/spelling_allow.txt | 361 ------------------ 5 files changed, 2 insertions(+), 396 deletions(-) delete mode 100644 docs/src/spelling_allow.txt diff --git a/docs/Makefile b/docs/Makefile index 47f3e740fa..fcb0ec0116 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -20,11 +20,6 @@ html-quick: echo "make html-quick in $$i..."; \ (cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) html-quick); done -spelling: - @for i in $(SUBDIRS); do \ - echo "make spelling in $$i..."; \ - (cd $$i; $(MAKE) $(MFLAGS) $(MYMAKEFLAGS) spelling); done - all: @for i in $(SUBDIRS); do \ echo "make all in $$i..."; \ diff --git a/docs/src/Makefile b/docs/src/Makefile index 37c2e9e3e6..a75da5371b 100644 --- a/docs/src/Makefile +++ b/docs/src/Makefile @@ -62,11 +62,6 @@ html-quick: @echo @echo "Build finished. The HTML (no gallery or api docs) pages are in $(BUILDDIR)/html" -spelling: - $(SPHINXBUILD) -b spelling $(SRCDIR) $(BUILDDIR) - @echo - @echo "Build finished. The HTML (no gallery) pages are in $(BUILDDIR)/html" - dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @@ -156,4 +151,5 @@ doctest: "results in $(BUILDDIR)/doctest/output.txt." show: - @python -c "import webbrowser; webbrowser.open_new_tab('file://$(shell pwd)/$(BUILDDIR)/html/index.html')" \ No newline at end of file + @python -c "import webbrowser; webbrowser.open_new_tab('file://$(shell pwd)/$(BUILDDIR)/html/index.html')" + diff --git a/docs/src/conf.py b/docs/src/conf.py index ae5a32e8a3..b5cdb67f3a 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -158,8 +158,6 @@ def _dotv(version): "sphinx_copybutton", "sphinx.ext.napoleon", "sphinx_panels", - # TODO: Spelling extension disabled until the dependencies can be included - # "sphinxcontrib.spelling", "sphinx_gallery.gen_gallery", "matplotlib.sphinxext.mathmpl", "matplotlib.sphinxext.plot_directive", @@ -193,16 +191,6 @@ def _dotv(version): napoleon_use_keyword = True napoleon_custom_sections = None -# -- spellingextension -------------------------------------------------------- -# See https://sphinxcontrib-spelling.readthedocs.io/en/latest/customize.html -spelling_lang = "en_GB" -# The lines in this file must only use line feeds (no carriage returns). -spelling_word_list_filename = ["spelling_allow.txt"] -spelling_show_suggestions = False -spelling_show_whole_line = False -spelling_ignore_importable_modules = True -spelling_ignore_python_builtins = True - # -- copybutton extension ----------------------------------------------------- # See https://sphinx-copybutton.readthedocs.io/en/latest/ copybutton_prompt_text = r">>> |\.\.\. " diff --git a/docs/src/developers_guide/contributing_documentation_full.rst b/docs/src/developers_guide/contributing_documentation_full.rst index 390f2eeea7..a470def683 100755 --- a/docs/src/developers_guide/contributing_documentation_full.rst +++ b/docs/src/developers_guide/contributing_documentation_full.rst @@ -113,18 +113,6 @@ adding it to the ``linkcheck_ignore`` array that is defined in the If this fails check the output for the text **broken** and then correct or ignore the url. -.. comment - Finally, the spelling in the documentation can be checked automatically via the - command:: - - make spelling - - The spelling check may pull up many technical abbreviations and acronyms. This - can be managed by using an **allow** list in the form of a file. This file, - or list of files is set in the `conf.py`_ using the string list - ``spelling_word_list_filename``. - - .. note:: In addition to the automated `Iris GitHub Actions`_ build of all the documentation build options above, the https://readthedocs.org/ service is also used. The configuration diff --git a/docs/src/spelling_allow.txt b/docs/src/spelling_allow.txt deleted file mode 100644 index ed883ac3bf..0000000000 --- a/docs/src/spelling_allow.txt +++ /dev/null @@ -1,361 +0,0 @@ -Admin -Albers -Arakawa -Arg -Args -Autoscale -Biggus -CF -CI -Cartopy -Checklist -Color -Conda -Constraining -DAP -Dask -Debian -Duchon -EO -Eos -Exner -Fieldsfile -Fieldsfiles -FillValue -Gb -GeogCS -Hovmoller -Jul -Jun -Jupyter -Lanczos -Mappables -Matplotlib -Mb -Modeling -Mollweide -NetCDF -Nino -PPfield -PPfields -Perez -Proj -Quickplot -Regrids -Royer -Scitools -Scitools -Sep -Stehfest -Steroegraphic -Subsetting -TestCodeFormat -TestLicenseHeaders -Torvalds -Trans -Trenberth -Tri -URIs -URLs -Ubuntu -Ugrid -Unidata -Vol -Vuuren -Workflow -Yury -Zaytsev -Zorder -abf -abl -advection -aggregator -aggregators -alphap -ancils -antimeridian -ap -arg -args -arithmetic -arraylike -atol -auditable -aux -basemap -behaviour -betap -bhulev -biggus -blev -boolean -boundpoints -branchname -broadcastable -bugfix -bugfixes -builtin -bulev -carrée -cartesian -celsius -center -centrepoints -cf -cftime -chunksizes -ci -clabel -cmap -cmpt -codebase -color -colorbar -colorbars -complevel -conda -config -constraining -convertor -coord -coords -cs -datafiles -datatype -datetime -datetimes -ddof -deepcopy -deprecations -der -dewpoint -dict -dicts -diff -discontiguities -discontiguous -djf -docstring -docstrings -doi -dom -dropdown -dtype -dtypes -dx -dy -edgecolor -endian -endianness -equirectangular -eta -etc -fh -fieldsfile -fieldsfiles -fileformat -fileformats -filename -filenames -filepath -filespec -fullname -func -geolocations -github -gregorian -grib -gribapi -gridcell -griddata -gridlines -hPa -hashable -hindcast -hyperlink -hyperlinks -idiff -ieee -ifunc -imagehash -inc -init -inline -inplace -int -interable -interpolator -ints -io -isosurfaces -iterable -jja -jupyter -kwarg -kwargs -landsea -lat -latlon -latlons -lats -lbcode -lbegin -lbext -lbfc -lbft -lblrec -lbmon -lbmond -lbnrec -lbrsvd -lbtim -lbuser -lbvc -lbyr -lbyrd -lh -lhs -linewidth -linted -linting -lon -lons -lt -mam -markup -matplotlib -matplotlibrc -max -mdtol -meaned -mercator -metadata -min -mpl -nanmask -nc -ndarray -neighbor -ness -netCDF -netcdf -netcdftime -nimrod -np -nsigma -numpy -nx -ny -online -orog -paramId -params -parsable -pcolormesh -pdf -placeholders -plugin -png -proj -ps -pseudocolor -pseudocolour -pseudocoloured -py -pyplot -quickplot -rST -rc -rd -reST -reStructuredText -rebase -rebases -rebasing -regrid -regridded -regridder -regridders -regridding -regrids -rel -repo -repos -reprojecting -rh -rhs -rst -rtol -scipy -scitools -seekable -setup -sines -sinh -spec -specs -src -ssh -st -stashcode -stashcodes -stats -std -stdout -str -subcube -subcubes -submodule -submodules -subsetting -sys -tanh -tb -testcases -tgt -th -timepoint -timestamp -timesteps -todo -tol -tos -traceback -travis -tripolar -tuple -tuples -txt -udunits -ufunc -ugrid -ukmo -un -unhandled -unicode -unittest -unrotate -unrotated -uris -url -urls -util -var -versioning -vmax -vmin -waypoint -waypoints -whitespace -wildcard -wildcards -windspeeds -withnans -workflow -workflows -xN -xx -xxx -zeroth -zlev -zonal From 0dd12007efa6ac911464d0a60028514b58332d6c Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Mon, 9 Jan 2023 10:40:44 +0000 Subject: [PATCH 303/319] Fix link checks (#5109) * Fix link checks. * What's New entry. * ECMWF about experiment. * ECMWF apps experiment. * Remove ECMWF link. * More specific What's New entry. --- docs/src/conf.py | 2 -- docs/src/installing.rst | 2 +- docs/src/whatsnew/1.0.rst | 3 +-- docs/src/whatsnew/latest.rst | 3 +++ 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/conf.py b/docs/src/conf.py index b5cdb67f3a..3636f94d1b 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -358,8 +358,6 @@ def _dotv(version): "http://www.esrl.noaa.gov/psd/data/gridded/conventions/cdc_netcdf_standard.shtml", "http://www.nationalarchives.gov.uk/doc/open-government-licence", "https://www.metoffice.gov.uk/", - # TODO: try removing this again in future - was raising an SSLError. - "http://www.ecmwf.int/", ] # list of sources to exclude from the build. diff --git a/docs/src/installing.rst b/docs/src/installing.rst index 6a2d2f6131..b2481973c0 100644 --- a/docs/src/installing.rst +++ b/docs/src/installing.rst @@ -14,7 +14,7 @@ Subsystem for Linux). This is a great option to get started with Iris for users and developers. Be aware that we do not currently test against any WSL_ distributions. -.. _WSL: https://docs.microsoft.com/en-us/windows/wsl/install-win10 +.. _WSL: https://learn.microsoft.com/en-us/windows/wsl/install .. note:: Iris is currently supported and tested against |python_support| running on Linux. We do not currently actively test on other diff --git a/docs/src/whatsnew/1.0.rst b/docs/src/whatsnew/1.0.rst index b226dc609b..c256c33566 100644 --- a/docs/src/whatsnew/1.0.rst +++ b/docs/src/whatsnew/1.0.rst @@ -147,8 +147,7 @@ the surface pressure. In return, it provides a virtual "pressure" coordinate whose values are derived from the given components. This facility is utilised by the GRIB2 loader to automatically provide -the derived "pressure" coordinate for certain data [#f1]_ from the -`ECMWF `_. +the derived "pressure" coordinate for certain data [#f1]_ from the ECMWF. .. [#f1] Where the level type is either 105 or 119, and where the surface pressure has an ECMWF paramId of diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index cf4b646841..06a116c5d7 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -76,6 +76,9 @@ This document explains the changes made to Iris for this release :obj:`~iris.analysis.MEDIAN` docstring, noting that the former handles lazy data. (:pull:`5128`) +#. `@trexfeathers`_ updated the WSL link to Microsoft's latest documentation, + and removed an ECMWF link in the ``v1.0`` What's New that was failing the + linkcheck CI. (:pull:`5109`) 💼 Internal =========== From e8837995b9ab2a43c351237e224883331ccc62bc Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Tue, 10 Jan 2023 10:04:44 +0000 Subject: [PATCH 304/319] Update lock files and accompanying fixes. (#5132) * Updated environment lockfiles * Use set_ylim for test_2d_coord_bounds_platecarree. * Don't use np.float for maths tests. * Remove uses of np.int . Co-authored-by: Lockfile bot --- .../benchmarks/experimental/ugrid/__init__.py | 2 +- docs/src/further_topics/metadata.rst | 6 +- .../integration/plot/test_plot_2d_coords.py | 9 +- .../tests/unit/analysis/maths/__init__.py | 2 +- requirements/ci/nox.lock/py310-linux-64.lock | 107 +++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 107 +++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 107 +++++++++--------- 7 files changed, 181 insertions(+), 159 deletions(-) diff --git a/benchmarks/benchmarks/experimental/ugrid/__init__.py b/benchmarks/benchmarks/experimental/ugrid/__init__.py index 2f9bb04e35..2e40c525a6 100644 --- a/benchmarks/benchmarks/experimental/ugrid/__init__.py +++ b/benchmarks/benchmarks/experimental/ugrid/__init__.py @@ -50,7 +50,7 @@ def time_create(self, *params): class Connectivity(UGridCommon): def setup(self, n_faces): - self.array = np.zeros([n_faces, 3], dtype=np.int) + self.array = np.zeros([n_faces, 3], dtype=int) super().setup(n_faces) def create(self): diff --git a/docs/src/further_topics/metadata.rst b/docs/src/further_topics/metadata.rst index de1afb15af..4c55047d4c 100644 --- a/docs/src/further_topics/metadata.rst +++ b/docs/src/further_topics/metadata.rst @@ -389,10 +389,10 @@ instances. Normally, this would cause issues. For example, .. doctest:: richer-metadata - >>> simply = {"one": np.int(1), "two": np.array([1.0, 2.0])} + >>> simply = {"one": np.int32(1), "two": np.array([1.0, 2.0])} >>> simply {'one': 1, 'two': array([1., 2.])} - >>> fruity = {"one": np.int(1), "two": np.array([1.0, 2.0])} + >>> fruity = {"one": np.int32(1), "two": np.array([1.0, 2.0])} >>> fruity {'one': 1, 'two': array([1., 2.])} >>> simply == fruity @@ -419,7 +419,7 @@ However, metadata class equality is rich enough to handle this eventuality, >>> metadata1 CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'one': 1, 'two': array([1., 2.])}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),)) - >>> metadata2 = cube.metadata._replace(attributes={"one": np.int(1), "two": np.array([1000.0, 2000.0])}) + >>> metadata2 = cube.metadata._replace(attributes={"one": np.int32(1), "two": np.array([1000.0, 2000.0])}) >>> metadata2 CubeMetadata(standard_name='air_temperature', long_name=None, var_name='air_temperature', units=Unit('K'), attributes={'one': 1, 'two': array([1000., 2000.])}, cell_methods=(CellMethod(method='mean', coord_names=('time',), intervals=('6 hour',), comments=()),)) >>> metadata1 == metadata2 diff --git a/lib/iris/tests/integration/plot/test_plot_2d_coords.py b/lib/iris/tests/integration/plot/test_plot_2d_coords.py index b8fbc5e31a..1b95899803 100644 --- a/lib/iris/tests/integration/plot/test_plot_2d_coords.py +++ b/lib/iris/tests/integration/plot/test_plot_2d_coords.py @@ -38,10 +38,17 @@ def simple_cube_w_2d_coords(): class Test(tests.GraphicsTest): def test_2d_coord_bounds_platecarree(self): # To avoid a problem with Cartopy smearing the data where the - # longitude wraps, we set the central_longitude + # longitude wraps, we set the central_longitude. + # SciTools/cartopy#1421 cube = simple_cube_w_2d_coords()[0, 0] ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180)) qplt.pcolormesh(cube) + + # Cartopy can't reliably set y-limits with curvilinear plotting. + # SciTools/cartopy#2121 + y_lims = [m(cube.coord("latitude").points) for m in (np.min, np.max)] + ax.set_ylim(*y_lims) + ax.coastlines(resolution="110m", color="red") self.check_graphic() diff --git a/lib/iris/tests/unit/analysis/maths/__init__.py b/lib/iris/tests/unit/analysis/maths/__init__.py index 311da8a0e6..558a6fccfe 100644 --- a/lib/iris/tests/unit/analysis/maths/__init__.py +++ b/lib/iris/tests/unit/analysis/maths/__init__.py @@ -247,7 +247,7 @@ def test_partial_mask_second_lazy_not_in_place(self): def test_in_place_introduces_mask(self): # If second cube is masked, result should also be masked. - data1 = np.arange(4, dtype=np.float) + data1 = np.arange(4, dtype=float) data2 = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 1, 0, 0]) cube1 = Cube(data1) cube2 = Cube(data2) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 81a1661c1e..75ec1e5579 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 65e8c3d4ababc804f8d3715a14ce94c3f564a37860525f35ea1aed69efd67be8 +# input_hash: 234b47d943728b5abe70fba0fd74c6adc10e4f1e2a14b919344f8a693b5b3e6f @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 @@ -37,18 +37,20 @@ https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b8 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f +https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-h9c3ff4c_0.tar.bz2#c77f5e4e418fa47d699d6afa54c5d444 https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d +https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2#b4f717df2d377410b462328bf0e8fb7d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f -https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.7-h27087fc_0.conda#f204c8ba400ec475452737094fb81d52 https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 @@ -68,7 +70,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.t https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 -https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 +https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 @@ -79,8 +81,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 -https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hff17c54_1.tar.bz2#2b7dbfa6988a41f9d23ba6d4f0e1d74e +https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.46-h620e276_0.conda#27e745f6f2e4b757e95dd7225fbe6bdb +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.51.0-hff17c54_0.conda#dd682f0b6d65e75b2bc868fc8e93d87e https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906 @@ -99,14 +101,14 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#ad https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda#89a41adce7106749573d883b2f657d78 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h82bc61c_0.conda#a01611c54334d783847879ee40109657 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 @@ -119,8 +121,9 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 +https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b -https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f +https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c @@ -132,12 +135,12 @@ https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3.tar.bz2#785160da087cf1d70e989afbb761f01c -https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.2-pyhd8ed1ab_0.conda#0f09c2bc17ddd8732be8e5b99297c7ce +https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed @@ -145,21 +148,20 @@ https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97 https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h2283fc2_1.tar.bz2#fdca8cd67ec2676f90a70ac73a32538b -https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-h67c24c5_1.conda#e1389a8d9a907133b3e6483c2807d243 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#bc302fa1cf8eda15c60f669b7524a320 +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-hb675445_2.conda#509f08b3789d9e7e9a72871491ae08e2 https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py310h53a5b5f_0.conda#3b114b1559def8bad228fec544ac1812 -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py310h08bbf29_0.conda#0d1f2e988c8810be90ffe441a303090a +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.0-pyhd8ed1ab_0.conda#b1b2ab02d1ece1719f7fa002ad4bc70d https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f @@ -167,10 +169,10 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py310h5764c6d_2.tar.bz2#cce72b32ccc346ed166fc85071854a86 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py310h1fa729e_0.conda#8d155ac95b1dfe585bcb6bec6a91c73b +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_5.tar.bz2#9e68d2ff6d98737c855b65f48dd3c597 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -194,72 +196,75 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_2.tar.bz2#6bb8063dd08f9724c18744b0e040cfe2 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_3.conda#800596144bb613cd7ac58b80900ce835 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h2283fc2_1.tar.bz2#9d4149760567cb232691cce2d8ccc21f +https://conda.anaconda.org/conda-forge/linux-64/curl-7.87.0-hdc1c0ab_0.conda#b14123ca479b9473d7f7395b0fd25c97 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_0.tar.bz2#6b5c2d276f306df759cfbdb0f41c4db9 -https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0 +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py310h454ad03_3.tar.bz2#eb354ff791f505b1d6f13f776359d88e +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py310h4927cde_0.conda#66366aceea767f174f4d0408f3a62812 https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h8ffa02c_1.conda#ed901e1f5c504b144b31f015c6702634 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1 https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2.tar.bz2#61e2f2f7befaf45f47d1da449a9a0aca -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py310hdfbd76f_2.tar.bz2#0582a434d03f6b06d5defbb142c96f4f -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py310h5b266fc_2.tar.bz2#c4a3707d6a630facb6cf7ed8e0d37326 +https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.0-py310h8b84c32_0.conda#823009371d9b961c83cdb9aa80e1e6e7 https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py310hd8f1fbe_0.conda#765b39936044b542a69ec2d863f5b891 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py310hff52083_0.conda#c6fc5e3f0a463ddb59cfda9a1582cfa0 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.4-py310h600f1e7_0.conda#f999dcc21fe27ad97a8afcfa590daa14 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.0-pyhd8ed1ab_0.conda#3a0f020d07998e1ae711df071f97fc19 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a +https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py310h34c0648_0.conda#af4b0c22dc4006ce3c095e840cb2efd7 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.1-pyhd8ed1ab_0.conda#f12878f9839c72f3d51af02fb10da43d +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.3-h25f0c4b_1.conda#0c8a8f15aa319c91d9010072278feddd +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py310h8d5ebf3_0.tar.bz2#da51ddb20c0f99d672eb756c3abf27e7 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py310h769672d_0.conda#bc363997d22f3b058fb17f1e89d4c96f -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py310hb1338dc_2.tar.bz2#e1648c222911ad7559d62831e4bc447c +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py310hfc24d34_0.conda#c126f81b5cea6b2d4a64d0744249a26f https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_2.tar.bz2#0d815f1b2258d3d4c17cc80fd01e0f36 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py310h83f2385_3.tar.bz2#4ec35f7eebe4221c1c00fdd6540db4dc -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee_0.conda#52cbed7e92713cf01b76445530396695 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.3-h4243ec0_1.conda#905563d166c13ba299e39d6c9fcebd1c +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.12-pyhd8ed1ab_0.conda#a34dcea79b2bed9520682a07f80d1c0f https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h0a86a1f_103.conda#7f69695b684f2595d9ba1ce26d693b7d -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 +https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.1-py310hff52083_0.conda#d26ee3f6561669ec1f118d6d3404e42a +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_102.conda#cbae8c932a9d2ee620db7ce7ae0abaf5 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py310hff52083_1.tar.bz2#8c151d720f9fe3b9962efe71fc10b07b -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-he99da89_3.conda#b7b364a82ad3ce9e56f0bad77efa9ab1 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.21.0-py310hff52083_0.conda#41b6a707f04268b028c497d346a97693 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hf6cd601_5.conda#9c23a5205b67f2a67b19c84bf1fd7f5e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py310hd9c82d4_101.tar.bz2#0333d51ee594be40f50b157ac6f27b5a -https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.2-h99bc08f_0.conda#8f247587d1520a2bbc6f79a821b74c07 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py310h515c5ea_101.conda#8a00edb7362ef5ff0db5dd75099daac7 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.0.5-h2e5815a_0.conda#96bf06b24d74a5bf826485e9032c9312 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_2.tar.bz2#1e2c49215b17e6cf06edf100c9869ebe https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py310hff52083_0.tar.bz2#aa78d12708912cd34135e6694a046ba0 +https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 -https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba +https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py310h8deb116_0.conda#ef72eeddf5316330730b11907c6c07d8 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py310hcb7e713_0.conda#bd14eaad9bbf54b78e48ecb8b644fcf6 +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 0836e3d018..77cd6b8962 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: dc794a12a2155d2a605b34fc34ece8039a0f0d43fbf7d304366cf8c33cf94cd1 +# input_hash: 0543fd9bbb31e9f896ccf547f3b155d68bb748634268c28dde6ff3ac77aa74d3 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 @@ -36,18 +36,20 @@ https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b8 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f +https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-h9c3ff4c_0.tar.bz2#c77f5e4e418fa47d699d6afa54c5d444 https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d +https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2#b4f717df2d377410b462328bf0e8fb7d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f -https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.7-h27087fc_0.conda#f204c8ba400ec475452737094fb81d52 https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 @@ -67,7 +69,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.t https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 -https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 +https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 @@ -78,8 +80,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 -https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hff17c54_1.tar.bz2#2b7dbfa6988a41f9d23ba6d4f0e1d74e +https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.46-h620e276_0.conda#27e745f6f2e4b757e95dd7225fbe6bdb +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.51.0-hff17c54_0.conda#dd682f0b6d65e75b2bc868fc8e93d87e https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906 @@ -98,14 +100,14 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#ad https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda#89a41adce7106749573d883b2f657d78 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h82bc61c_0.conda#a01611c54334d783847879ee40109657 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 @@ -118,8 +120,9 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a +https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b -https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f +https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c @@ -131,12 +134,12 @@ https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 -https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.2-pyhd8ed1ab_0.conda#0f09c2bc17ddd8732be8e5b99297c7ce +https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed @@ -144,21 +147,20 @@ https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84 -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97 https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h2283fc2_1.tar.bz2#fdca8cd67ec2676f90a70ac73a32538b -https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-h67c24c5_1.conda#e1389a8d9a907133b3e6483c2807d243 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#bc302fa1cf8eda15c60f669b7524a320 +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-hb675445_2.conda#509f08b3789d9e7e9a72871491ae08e2 https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_2.tar.bz2#c342a370480791db83d5dd20f2d8899f https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar.bz2#0c469687a517052c0d581fc6e1a4189d https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py38h7042d01_0.conda#d5a3620cd8c1af4115120f21d678507a -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py38hab0fcb9_0.conda#2c0b3c72dad0288d9582ccbceb250cb4 +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.0-pyhd8ed1ab_0.conda#b1b2ab02d1ece1719f7fa002ad4bc70d https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py38h0a891b7_0.tar.bz2#fe2ef279417faa1af0adf178de2032f7 @@ -166,10 +168,10 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py38h0a891b7_2.tar.bz2#9b13816a39904084556126a6ce7fd0d0 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py38h1de0b5d_0.conda#7db73572d4f7e10a759bad609a228ad0 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_5.tar.bz2#0856c59f9ddb710c640dc0428d66b1b7 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -193,72 +195,75 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_2.tar.bz2#2276b1f4d1ede3f5f14cc7e4ae6f9a33 +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_3.conda#3ac112151c6b6cfe457e976de41af0c5 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h2283fc2_1.tar.bz2#9d4149760567cb232691cce2d8ccc21f +https://conda.anaconda.org/conda-forge/linux-64/curl-7.87.0-hdc1c0ab_0.conda#b14123ca479b9473d7f7395b0fd25c97 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_0.tar.bz2#6b5c2d276f306df759cfbdb0f41c4db9 -https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0 +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py38h9eb91d8_3.tar.bz2#61dc7b3140b7b79b1985b53d52726d74 +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py38hb32c036_0.conda#a288a6e69efc2f20c30ebfa590e11bed https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h8ffa02c_1.conda#ed901e1f5c504b144b31f015c6702634 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1 https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2.tar.bz2#d30399a3c636c75cfd3460c92effa960 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py38h8ce737c_2.tar.bz2#dfd81898f0c6e9ee0c22305da6aa443e -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py38hafd38ec_2.tar.bz2#8df75c6a8c1deac4e99583ec624ff327 +https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.0-py38hd07e089_0.conda#e8243980979661d5e941fcfd954ddb13 https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py38hfa26641_0.conda#7be81814bae276dc7b4c707cf1e8186b https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py38h578d9bd_0.conda#d89831246b5ea571858611690c3c75a4 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.4-py38h80a4ca7_0.conda#d3c4698fd7475640f4d9eff8d792deac -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.0-pyhd8ed1ab_0.conda#3a0f020d07998e1ae711df071f97fc19 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a +https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py38h3d167d9_0.conda#0ef859aa9dafce54bdf3d56715daed35 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.1-pyhd8ed1ab_0.conda#f12878f9839c72f3d51af02fb10da43d +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.3-h25f0c4b_1.conda#0c8a8f15aa319c91d9010072278feddd +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py38hb021067_0.tar.bz2#72422499195d8aded0dfd461c6e3e86f https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py38h8f669ce_0.conda#dbc17622f9d159be987bd21959d5494e -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py38hce0a2d1_2.tar.bz2#be61a535f279bffdf7f449a654eaa19d +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py38hf928c62_0.conda#bb6d6874f1dcafdd2dce7dfd54d2b96c https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_2.tar.bz2#ad6437509a14f1e8e5b8a354f93f340c https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar.bz2#82b3797d08a43a101b645becbb938e65 -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py38hf6c3373_3.tar.bz2#1dc477fef9b0b1080af3e7c7ecb4aff7 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee_0.conda#52cbed7e92713cf01b76445530396695 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.3-h4243ec0_1.conda#905563d166c13ba299e39d6c9fcebd1c +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.12-pyhd8ed1ab_0.conda#a34dcea79b2bed9520682a07f80d1c0f https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h6b4b75c_103.conda#ea3d2204fc3a7db7d831daa437a58717 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 +https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.1-py38h578d9bd_0.conda#4ddc66bb73c2d53d194875c2ee8f0f06 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_102.conda#cbae8c932a9d2ee620db7ce7ae0abaf5 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py38h578d9bd_1.tar.bz2#38d9029214399e4bfc378b62b0171bf0 -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-he99da89_3.conda#b7b364a82ad3ce9e56f0bad77efa9ab1 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.21.0-py38h578d9bd_0.conda#4fb68a31e2377d41d5a33e47a5436d75 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hf6cd601_5.conda#9c23a5205b67f2a67b19c84bf1fd7f5e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py38h9147699_101.tar.bz2#5a9de1dec507b6614150a77d1aabf257 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.2-h99bc08f_0.conda#8f247587d1520a2bbc6f79a821b74c07 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py38h4407c66_101.conda#1deba9421c01396e0b1381a02a29ed93 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.0.5-h2e5815a_0.conda#96bf06b24d74a5bf826485e9032c9312 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_2.tar.bz2#cfa725eff634872f90dcd5ebf8e8dc1a https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py38h578d9bd_0.tar.bz2#e1a19f0d4686a701d4a4acce2b625acb +https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 -https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba +https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py38h10c12cc_0.conda#466ea530d622838f6cdec4f771ddc249 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py38h3d2c718_0.conda#55ba6e3a49c4293302262286a49607d8 +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index f3f071da34..cc896e6989 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 8720b47771aff1b233330a6562a535e5ad3a153a023d02d4dc71b383a25796a3 +# input_hash: de178c2d53980747bafc10c4a4387eeb8c700311af7b35a2fcb49f1b441b960b @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 @@ -37,18 +37,20 @@ https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b8 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f +https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-h9c3ff4c_0.tar.bz2#c77f5e4e418fa47d699d6afa54c5d444 https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d +https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2#b4f717df2d377410b462328bf0e8fb7d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.21-pthreads_h78a6416_3.tar.bz2#8c5963a49b6035c40646a763293fbb35 https://conda.anaconda.org/conda-forge/linux-64/libopus-1.3.1-h7f98852_1.tar.bz2#15345e56d527b330e1cacbdf58676e8f -https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.6-h9c3ff4c_1008.tar.bz2#16e143a1ed4b4fd169536373957f6fee +https://conda.anaconda.org/conda-forge/linux-64/libtool-2.4.7-h27087fc_0.conda#f204c8ba400ec475452737094fb81d52 https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2#174243089ec111479298a5b7099b64b5 https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 @@ -68,7 +70,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.3-h7f98852_0.t https://conda.anaconda.org/conda-forge/linux-64/xorg-renderproto-0.11.1-h7f98852_1002.tar.bz2#06feff3d2634e3097ce2fe681474b534 https://conda.anaconda.org/conda-forge/linux-64/xorg-xextproto-7.3.0-h7f98852_1002.tar.bz2#1e15f6ad85a7d743a2ac68dae6c82b98 https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007.tar.bz2#b4a4381d54784606820704f7b5f05a15 -https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.0-h7f98852_3.tar.bz2#52402c791f35e414e704b7a113f99605 +https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 @@ -79,8 +81,8 @@ https://conda.anaconda.org/conda-forge/linux-64/libcap-2.66-ha37c62d_0.tar.bz2#2 https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20191231-he28a2e2_2.tar.bz2#4d331e44109e3f0e19b4cb8f9b82f3e1 https://conda.anaconda.org/conda-forge/linux-64/libevent-2.1.10-h28343ad_4.tar.bz2#4a049fc560e00e43151dc51368915fdd https://conda.anaconda.org/conda-forge/linux-64/libflac-1.4.2-h27087fc_0.tar.bz2#7daf72d8e2a8e848e11d63ed6d1026e0 -https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.45-hc0c96e0_0.tar.bz2#839aeb24ab885a7b902247a6d943d02f -https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.47.0-hff17c54_1.tar.bz2#2b7dbfa6988a41f9d23ba6d4f0e1d74e +https://conda.anaconda.org/conda-forge/linux-64/libgpg-error-1.46-h620e276_0.conda#27e745f6f2e4b757e95dd7225fbe6bdb +https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.51.0-hff17c54_0.conda#dd682f0b6d65e75b2bc868fc8e93d87e https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.39-h753d276_0.conda#e1c890aebdebbfbf87e2c917187b4416 https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.40.0-h753d276_0.tar.bz2#2e5f9a37d487e1019fd4d8113adb2f9f https://conda.anaconda.org/conda-forge/linux-64/libssh2-1.10.0-hf14f497_3.tar.bz2#d85acad4b47dff4e3def14a769a97906 @@ -99,14 +101,14 @@ https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#ad https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 -https://conda.anaconda.org/conda-forge/linux-64/krb5-1.19.3-h08a2579_0.tar.bz2#d25e05e7ee0e302b52d24491db4891eb +https://conda.anaconda.org/conda-forge/linux-64/krb5-1.20.1-h81ceb04_0.conda#89a41adce7106749573d883b2f657d78 https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openblas.tar.bz2#20bae26d0a1db73f758fc3754cab4719 https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.4.0-h55922b4_4.tar.bz2#901791f0ec7cddc8714e76e273013a91 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h82bc61c_0.conda#a01611c54334d783847879ee40109657 https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 @@ -119,8 +121,9 @@ https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb +https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b -https://conda.anaconda.org/conda-forge/noarch/attrs-22.1.0-pyh71513ae_1.tar.bz2#6d3ccbc56256204925bfa8378722792f +https://conda.anaconda.org/conda-forge/noarch/attrs-22.2.0-pyh71513ae_0.conda#8b76db7818a4e401ed4486c4c1635cd9 https://conda.anaconda.org/conda-forge/linux-64/brotli-1.0.9-h166bdaf_8.tar.bz2#2ff08978892a3e8b954397c461f18418 https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.conda#fb9addc3db06e56abe03e0e9f21a63e6 https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c @@ -132,12 +135,12 @@ https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2 https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c -https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.0.4-pyhd8ed1ab_0.tar.bz2#e0734d1f12de77f9daca98bda3428733 +https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 -https://conda.anaconda.org/conda-forge/noarch/filelock-3.8.2-pyhd8ed1ab_0.conda#0f09c2bc17ddd8732be8e5b99297c7ce +https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 -https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.8-hff1cb4f_1.tar.bz2#a61c6312192e7c9de71548a6706a21e6 +https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed @@ -145,21 +148,20 @@ https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411 -https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-h6ed2654_0.tar.bz2#dcc588839de1445d90995a0a2c4f3a39 +https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97 https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe -https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h3e49a29_2.tar.bz2#3b88f1d0fe2580594d58d7e44d664617 -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.86.0-h2283fc2_1.tar.bz2#fdca8cd67ec2676f90a70ac73a32538b -https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-h67c24c5_1.conda#e1389a8d9a907133b3e6483c2807d243 +https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#bc302fa1cf8eda15c60f669b7524a320 +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-hb675445_2.conda#509f08b3789d9e7e9a72871491ae08e2 https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 -https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h522a892_0.tar.bz2#802e43f480122a85ae6a34c1909f8f98 +https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_2.tar.bz2#c678e07e7862b3157fb9f6d908233ffa https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar.bz2#b035b507f55bb6a967d86d4b7e059437 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.23.5-py39h3d75532_0.conda#ea5d332e361eb72c2593cf79559bc0ec -https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-h7d73246_1.tar.bz2#a11b4df9271a8d7917686725aa04c8f2 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py39h223a676_0.conda#ce779b1c4e7ff4cc2f2690d173974daf +https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.0-pyhd8ed1ab_0.conda#b1b2ab02d1ece1719f7fa002ad4bc70d https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py39hb9d737c_0.tar.bz2#12184951da572828fb986b06ffb63eed @@ -167,10 +169,10 @@ https://conda.anaconda.org/conda-forge/noarch/pycparser-2.21-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.bz2#e8fbc1b54b25f4b08281467bc13b70cc https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 -https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.0.0-py39hb9d737c_2.tar.bz2#b643f1e19306b75a6013d77228156076 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.6-pyhd8ed1ab_0.tar.bz2#b1f26ad83328e486910ef7f6e81dc061 +https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py39h72bdee0_0.conda#18927f971926b7271600368de71de557 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2#ef9db3c38ae7275f6b14491cfe61a248 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.5.1-pyhd8ed1ab_0.tar.bz2#cfb8dc4d9d285ca5fb1177b9dd450e33 +https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae @@ -194,72 +196,75 @@ https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 -https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_2.tar.bz2#fc70a133e8162f51e363cff3b6dc741c +https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_3.conda#20080319ef73fbad74dcd6d62f2a3ffe https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.86.0-h2283fc2_1.tar.bz2#9d4149760567cb232691cce2d8ccc21f +https://conda.anaconda.org/conda-forge/linux-64/curl-7.87.0-hdc1c0ab_0.conda#b14123ca479b9473d7f7395b0fd25c97 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 -https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_0.tar.bz2#6b5c2d276f306df759cfbdb0f41c4db9 -https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-5.1.0-pyha770c72_0.conda#46a62e35b9ae515cf0e49afc7fe0e7ef +https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0 +https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 -https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h18fbbfe_3.tar.bz2#ea9758cf553476ddf75c789fdd239dc5 +https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.2.0-py39hf3a2cdf_3.tar.bz2#2bd111c38da69056e5fe25a51b832eba +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py39ha08a7e4_0.conda#d62ba9d1a981544c809813afaf0be5c0 https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h93bde94_0.tar.bz2#255c7204dda39747c3ba380d28b026d7 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h8ffa02c_1.conda#ed901e1f5c504b144b31f015c6702634 https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 -https://conda.anaconda.org/conda-forge/noarch/pygments-2.13.0-pyhd8ed1ab_0.tar.bz2#9f478e8eedd301008b5f395bad0caaed +https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1 https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2.tar.bz2#234ad9828eca1caf0f2fdcb4a24ad816 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.9.3-py39hddc5342_2.tar.bz2#0615ac8191c6ccf7d40860aff645f774 -https://conda.anaconda.org/conda-forge/linux-64/shapely-1.8.5-py39h76a96b7_2.tar.bz2#10bea68a9dd064b703743d210e679408 +https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.0-py39hc9151fd_0.conda#735b335f9250d84a5da94ffb76692db8 https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py39h5a03fae_0.conda#c3eb463691a8b93f1c381a9e56ecad9a https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.0-py39hf3d152e_0.conda#a6f9ae6d84b4b233968e20a707935462 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-38.0.4-py39h3ccb8fc_0.conda#dee37fde01f9bbc53ec421199d7b17cf -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.0-pyhd8ed1ab_0.conda#3a0f020d07998e1ae711df071f97fc19 -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.2-hd4edc92_0.conda#3ae425efddb9da5fb35edda331e4dff7 -https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-5.3.0-h418a68e_0.tar.bz2#888056bd4b12e110b10d4d1f29161c5e -https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a +https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py39h079d5ae_0.conda#70ac60b214a8df9b9ce63e05af7d0976 +https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.1-pyhd8ed1ab_0.conda#f12878f9839c72f3d51af02fb10da43d +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.3-h25f0c4b_1.conda#0c8a8f15aa319c91d9010072278feddd +https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py39hf9fd14e_0.tar.bz2#78ce32061e0be12deb8e0f11ffb76906 https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py39h4661b88_0.conda#e17e50269c268d79478956a262a9fe13 -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.0-py39h14a8356_2.tar.bz2#5d93c781338ff274a0b3dc3d901e19a6 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54 +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py39h12578bd_0.conda#7edbb99bec2bfab82f86abd71d24b505 https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_2.tar.bz2#306f1a018668f06a0bd89350a3f62c07 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 -https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.0.5-pyhd8ed1ab_1.tar.bz2#07037fe2931871ed69b2b3d2acd5fdc6 +https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar.bz2#0f11bcdf9669a5ae0f39efd8c830209a -https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.0-py39h91bfd65_3.tar.bz2#7d10a2e14c08f383baae00e77bf890e5 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.2-h3e40eee_0.conda#52cbed7e92713cf01b76445530396695 -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.9-pyhd8ed1ab_0.conda#e7ecbbb61a37daed2a13de43d35d5282 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.3-h4243ec0_1.conda#905563d166c13ba299e39d6c9fcebd1c +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.12-pyhd8ed1ab_0.conda#a34dcea79b2bed9520682a07f80d1c0f https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h94a714e_103.conda#ee29e7176b5854fa09ec17b101945401 -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-h382ae3d_0.conda#627bea5af786dbd8013ef26127d8115a -https://conda.anaconda.org/conda-forge/noarch/pyopenssl-22.1.0-pyhd8ed1ab_0.tar.bz2#fbfa0a180d48c800f922a10a114a8632 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.2.0-mpi_mpich_h5a1934d_102.tar.bz2#bb8bdfa5e3e9e3f6ec861f05cd2ad441 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 +https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 +https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 +https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.1-py39hf3d152e_0.conda#dd1be6ccb267f13bdc5c44cfb76c4080 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_102.conda#cbae8c932a9d2ee620db7ce7ae0abaf5 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.20.0-py39hf3d152e_1.tar.bz2#921f8a7c2a16d18d7168fdac88b2adfe -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-he99da89_3.conda#b7b364a82ad3ce9e56f0bad77efa9ab1 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.21.0-py39hf3d152e_0.conda#9dafac76ddd44f3b9a4a45ad601c5917 +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hf6cd601_5.conda#9c23a5205b67f2a67b19c84bf1fd7f5e https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.2.0-mpi_mpich_py39h8bb458d_101.tar.bz2#347f324dd99dfb0b1479a466213b55bf -https://conda.anaconda.org/conda-forge/linux-64/graphviz-6.0.2-h99bc08f_0.conda#8f247587d1520a2bbc6f79a821b74c07 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py39h3088dd8_101.conda#e90e56e1bd5f2a484e435fd2745cd809 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.0.5-h2e5815a_0.conda#96bf06b24d74a5bf826485e9032c9312 https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_2.tar.bz2#384809c51fb2adc04773f6fa097cd051 https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py39hf3d152e_0.tar.bz2#03225b4745d1dee7bb19d81e41c773a0 +https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 -https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.8.1-pyhd8ed1ab_0.tar.bz2#7d8390ec71225ea9841b276552fdffba +https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py39h7360e5f_0.conda#d6d4f8195ec2c846deebe71306f60298 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a +https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py39h6e7ad6e_0.conda#7cb72bd5b1e7c5a23a062db90889356b +https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a From 769d7f058a67f8a3071ee5882cdd681f5aca5a34 Mon Sep 17 00:00:00 2001 From: Jon Seddon <17068361+jonseddon@users.noreply.github.com> Date: Fri, 13 Jan 2023 13:54:01 +0000 Subject: [PATCH 305/319] Updated citation (#5116) * Updated citation * Added WhatsNew * Reference version 3.4 in DOI Co-authored-by: Bouwe Andela * Suggest updating the citation at every release Co-authored-by: Bouwe Andela --- docs/src/developers_guide/release.rst | 5 +++++ docs/src/userguide/citation.rst | 13 +++++++------ docs/src/whatsnew/latest.rst | 2 ++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/src/developers_guide/release.rst b/docs/src/developers_guide/release.rst index de7aa6c719..bae77a7d21 100644 --- a/docs/src/developers_guide/release.rst +++ b/docs/src/developers_guide/release.rst @@ -277,6 +277,11 @@ Post Release Steps #. On main, make a new ``latest.rst`` from ``latest.rst.template`` and update the include statement and the toctree in ``index.rst`` to point at the new ``latest.rst``. +#. Consider updating ``docs/src/userguide/citation.rst`` on ``main`` to include + the version number, date and `Zenodo DOI `_ + of the new release. Ideally this would be updated before the release, but + the DOI for the new version is only available once the release has been + created in GitHub. .. _SciTools/iris: https://github.com/SciTools/iris diff --git a/docs/src/userguide/citation.rst b/docs/src/userguide/citation.rst index 0a3a85fb89..1498b9dfe1 100644 --- a/docs/src/userguide/citation.rst +++ b/docs/src/userguide/citation.rst @@ -15,11 +15,12 @@ For example:: @manual{Iris, author = {{Met Office}}, - title = {Iris: A Python package for analysing and visualising meteorological and oceanographic data sets}, - edition = {v1.2}, - year = {2010 - 2013}, + title = {Iris: A powerful, format-agnostic, and community-driven Python package for analysing and visualising Earth science data }, + edition = {v3.4}, + year = {2010 - 2022}, address = {Exeter, Devon }, - url = {http://scitools.org.uk/} + url = {http://scitools.org.uk/}, + doi = {10.5281/zenodo.7386117} } @@ -33,7 +34,7 @@ Suggested format:: For example:: - Iris. v1.2. 28-Feb-2013. Met Office. UK. https://github.com/SciTools/iris/archive/v1.2.0.tar.gz 01-03-2013 + Iris. v3.4. 1-Dec-2022. Met Office. UK. https://doi.org/10.5281/zenodo.7386117 22-12-2022 ******************** @@ -46,7 +47,7 @@ Suggested format:: For example:: - Iris. Met Office. git@github.com:SciTools/iris.git 06-03-2013 + Iris. Met Office. git@github.com:SciTools/iris.git 22-12-2022 .. _How to cite and describe software: https://software.ac.uk/how-cite-software diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 06a116c5d7..2cdc9cf1f5 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -72,6 +72,8 @@ This document explains the changes made to Iris for this release the light version (not dark) while we make the docs dark mode friendly (:pull:`5129`) +#. `@jonseddon`_ updated the citation to a more recent version of Iris. (:pull:`5116`) + #. `@rcomer`_ linked the :obj:`~iris.analysis.PERCENTILE` aggregator from the :obj:`~iris.analysis.MEDIAN` docstring, noting that the former handles lazy data. (:pull:`5128`) From a3b35600bf22186479b3a67134698bb91a18044d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 19 Jan 2023 13:57:37 +0000 Subject: [PATCH 306/319] [pre-commit.ci] pre-commit autoupdate (#5136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/asottile/blacken-docs: v1.12.1 → 1.13.0](https://github.com/asottile/blacken-docs/compare/v1.12.1...1.13.0) * Remove explicit black version dependency from blacken-docs. Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Martin Yeo --- .pre-commit-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad5a9d4626..2c1b0400e1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -50,11 +50,10 @@ repos: args: [--filter-files] - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 + rev: 1.13.0 hooks: - id: blacken-docs types: [file, rst] - additional_dependencies: [black==21.6b0] - repo: https://github.com/aio-libs/sort-all rev: v1.2.0 From 4a945ec111cff4ad0dff42049dd42c56d11fc6ac Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Tue, 24 Jan 2023 12:07:20 +0000 Subject: [PATCH 307/319] =?UTF-8?q?Iris=20=E2=9D=A4=20Xarray=20docs=20page?= =?UTF-8?q?.=20(#5025)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Iris Xarray docs page. * Add links. * Xarray page styling. * What's New entry. * Minor docs fixes. * Overall experience section. * Xarray supports other plotting backends through external packages. Co-authored-by: Deepak Cherian * Section on converting between Iris and Xarray. * Clearer language around laziness and multi-processing. * To-do note about dates and fill values. * Move iris_xarray page into a new Community section. * Language fixes from @bjlittle review. Co-authored-by: Deepak Cherian --- docs/src/common_links.inc | 1 + docs/src/community/index.rst | 48 ++++++ docs/src/community/iris_xarray.rst | 154 ++++++++++++++++++ docs/src/conf.py | 1 + .../further_topics/ugrid/partner_packages.rst | 3 +- docs/src/index.rst | 9 + docs/src/whatsnew/latest.rst | 8 + lib/iris/_lazy_data.py | 20 ++- lib/iris/cube.py | 3 +- lib/iris/experimental/ugrid/mesh.py | 4 +- lib/iris/pandas.py | 4 +- lib/iris/util.py | 7 +- 12 files changed, 244 insertions(+), 18 deletions(-) create mode 100644 docs/src/community/index.rst create mode 100644 docs/src/community/iris_xarray.rst diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index 530ebc4877..bf1567e917 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -40,6 +40,7 @@ .. _CF-UGRID: https://ugrid-conventions.github.io/ugrid-conventions/ .. _issues on GitHub: https://github.com/SciTools/iris/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc .. _python-stratify: https://github.com/SciTools/python-stratify +.. _iris-esmf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid .. comment diff --git a/docs/src/community/index.rst b/docs/src/community/index.rst new file mode 100644 index 0000000000..a9e2a4d040 --- /dev/null +++ b/docs/src/community/index.rst @@ -0,0 +1,48 @@ +.. include:: ../common_links.inc + +.. todo: + consider scientific-python.org + consider scientific-python.org/specs/ + +Iris in the Community +===================== + +Iris aims to be a valuable member of the open source scientific Python +community. + +We listen out for developments in our dependencies and neighbouring projects, +and we reach out to them when we can solve problems together; please feel free +to reach out to us! + +We are aware of our place in the user's wider 'toolbox' - offering unique +functionality and interoperating smoothly with other packages. + +We welcome contributions from all; whether that's an opinion, a 1-line +clarification, or a whole new feature 🙂 + +Quick Links +----------- + +* `GitHub Discussions`_ +* :ref:`Getting involved` +* `Twitter `_ + +Interoperability +---------------- + +There's a big choice of Python tools out there! Each one has strengths and +weaknesses in different areas, so we don't want to force a single choice for your +whole workflow - we'd much rather make it easy for you to choose the right tool +for the moment, switching whenever you need. Below are our ongoing efforts at +smoother interoperability: + +.. not using toctree due to combination of child pages and cross-references. + +* The :mod:`iris.pandas` module +* :doc:`iris_xarray` + +.. toctree:: + :maxdepth: 1 + :hidden: + + iris_xarray diff --git a/docs/src/community/iris_xarray.rst b/docs/src/community/iris_xarray.rst new file mode 100644 index 0000000000..859597da78 --- /dev/null +++ b/docs/src/community/iris_xarray.rst @@ -0,0 +1,154 @@ +.. include:: ../common_links.inc + +====================== +Iris ❤️ :term:`Xarray` +====================== + +There is a lot of overlap between Iris and :term:`Xarray`, but some important +differences too. Below is a summary of the most important differences, so that +you can be prepared, and to help you choose the best package for your use case. + +Overall Experience +------------------ + +Iris is the more specialised package, focussed on making it as easy +as possible to work with meteorological and climatological data. Iris +is built to natively handle many key concepts, such as the CF conventions, +coordinate systems and bounded coordinates. Iris offers a smaller toolkit of +operations compared to Xarray, particularly around API for sophisticated +computation such as array manipulation and multi-processing. + +Xarray's more generic data model and community-driven development give it a +richer range of operations and broader possible uses. Using Xarray +specifically for meteorology/climatology may require deeper knowledge +compared to using Iris, and you may prefer to add Xarray plugins +such as :ref:`cfxarray` to get the best experience. Advanced users can likely +achieve better performance with Xarray than with Iris. + +Conversion +---------- +There are multiple ways to convert between Iris and Xarray objects. + +* Xarray includes the :meth:`~xarray.DataArray.to_iris` and + :meth:`~xarray.DataArray.from_iris` methods - detailed in the + `Xarray IO notes on Iris`_. Since Iris evolves independently of Xarray, be + vigilant for concepts that may be lost during the conversion. +* Because both packages are closely linked to the :term:`NetCDF Format`, it is + feasible to save a NetCDF file using one package then load that file using + the other package. This will be lossy in places, as both Iris and Xarray + are opinionated on how certain NetCDF concepts relate to their data models. +* The Iris development team are exploring an improved 'bridge' between the two + packages. Follow the conversation on GitHub: `iris#4994`_. This project is + expressly intended to be as lossless as possible. + +Regridding +---------- +Iris and Xarray offer a range of regridding methods - both natively and via +additional packages such as `iris-esmf-regrid`_ and `xESMF`_ - which overlap +in places +but tend to cover a different set of use cases (e.g. Iris handles unstructured +meshes but offers access to fewer ESMF methods). The behaviour of these +regridders also differs slightly (even between different regridders attached to +the same package) so the appropriate package to use depends highly on the +particulars of the use case. + +Plotting +-------- +Xarray and Iris have a large overlap of functionality when creating +:term:`Matplotlib` plots and both support the plotting of multidimensional +coordinates. This means the experience is largely similar using either package. + +Xarray supports further plotting backends through external packages (e.g. Bokeh through `hvPlot`_) +and, if a user is already familiar with `pandas`_, the interface should be +familiar. It also supports some different plot types to Iris, and therefore can +be used for a wider variety of plots. It also has benefits regarding "out of +the box", quick customisations to plots. However, if further customisation is +required, knowledge of matplotlib is still required. + +In both cases, :term:`Cartopy` is/can be used. Iris does more work +automatically for the user here, creating Cartopy +:class:`~cartopy.mpl.geoaxes.GeoAxes` for latitude and longitude coordinates, +whereas the user has to do this manually in Xarray. + +Statistics +---------- +Both libraries are quite comparable with generally similar capabilities, +performance and laziness. Iris offers more specificity in some cases, such as +some more specific unique functions and masked tolerance in most statistics. +Xarray seems more approachable however, with some less unique but more +convenient solutions (these tend to be wrappers to :term:`Dask` functions). + +Laziness and Multi-Processing with :term:`Dask` +----------------------------------------------- +Iris and Xarray both support lazy data and out-of-core processing through +utilisation of Dask. + +While both Iris and Xarray expose :term:`NumPy` conveniences at the API level +(e.g. the `ndim()` method), only Xarray exposes Dask conveniences. For example +:attr:`xarray.DataArray.chunks`, which gives the user direct control +over the underlying Dask array chunks. The Iris API instead takes control of +such concepts and user control is only possible by manipulating the underlying +Dask array directly (accessed via :meth:`iris.cube.Cube.core_data`). + +:class:`xarray.DataArray`\ s comply with `NEP-18`_, allowing NumPy arrays to be +based on them, and they also include the necessary extra members for Dask +arrays to be based on them too. Neither of these is currently possible with +Iris :class:`~iris.cube.Cube`\ s, although an ambition for the future. + +NetCDF File Control +------------------- +(More info: :term:`NetCDF Format`) + +Unlike Iris, Xarray generally provides full control of major file structures, +i.e. dimensions + variables, including their order in the file. It mostly +respects these in a file input, and can reproduce them on output. +However, attribute handling is not so complete: like Iris, it interprets and +modifies some recognised aspects, and can add some extra attributes not in the +input. + +.. todo: + More detail on dates and fill values (@pp-mo suggestion). + +Handling of dates and fill values have some special problems here. + +Ultimately, nearly everything wanted in a particular desired result file can +be achieved in Xarray, via provided override mechanisms (`loading keywords`_ +and the '`encoding`_' dictionaries). + +Missing Data +------------ +Xarray uses :data:`numpy.nan` to represent missing values and this will support +many simple use cases assuming the data are floats. Iris enables more +sophisticated missing data handling by representing missing values as masks +(:class:`numpy.ma.MaskedArray` for real data and :class:`dask.array.Array` +for lazy data) which allows data to be any data type and to include either/both +a mask and :data:`~numpy.nan`\ s. + +.. _cfxarray: + +`cf-xarray`_ +------------- +Iris has a data model entirely based on :term:`CF Conventions`. Xarray has a +data model based on :term:`NetCDF Format` with cf-xarray acting as translation +into CF. Xarray/cf-xarray methods can be +called and data accessed with CF like arguments (e.g. axis, standard name) and +there are some CF specific utilities (similar +to Iris utilities). Iris tends to cover more of and be stricter about CF. + + +.. seealso:: + + * `Xarray IO notes on Iris`_ + * `Xarray notes on other NetCDF libraries`_ + +.. _Xarray IO notes on Iris: https://docs.xarray.dev/en/stable/user-guide/io.html#iris +.. _Xarray notes on other NetCDF libraries: https://docs.xarray.dev/en/stable/getting-started-guide/faq.html#what-other-netcdf-related-python-libraries-should-i-know-about +.. _loading keywords: https://docs.xarray.dev/en/stable/generated/xarray.open_dataset.html#xarray.open_dataset +.. _encoding: https://docs.xarray.dev/en/stable/user-guide/io.html#writing-encoded-data +.. _xESMF: https://github.com/pangeo-data/xESMF/ +.. _seaborn: https://seaborn.pydata.org/ +.. _hvPlot: https://hvplot.holoviz.org/ +.. _pandas: https://pandas.pydata.org/ +.. _NEP-18: https://numpy.org/neps/nep-0018-array-function-protocol.html +.. _cf-xarray: https://github.com/xarray-contrib/cf-xarray +.. _iris#4994: https://github.com/SciTools/iris/issues/4994 diff --git a/docs/src/conf.py b/docs/src/conf.py index 3636f94d1b..576a099b90 100644 --- a/docs/src/conf.py +++ b/docs/src/conf.py @@ -223,6 +223,7 @@ def _dotv(version): "python": ("https://docs.python.org/3/", None), "scipy": ("https://docs.scipy.org/doc/scipy/", None), "pandas": ("https://pandas.pydata.org/docs/", None), + "dask": ("https://docs.dask.org/en/stable/", None), } # The name of the Pygments (syntax highlighting) style to use. diff --git a/docs/src/further_topics/ugrid/partner_packages.rst b/docs/src/further_topics/ugrid/partner_packages.rst index 8e36f4ffc2..75b54b037f 100644 --- a/docs/src/further_topics/ugrid/partner_packages.rst +++ b/docs/src/further_topics/ugrid/partner_packages.rst @@ -1,3 +1,5 @@ +.. include:: ../../common_links.inc + .. _ugrid partners: Iris' Mesh Partner Packages @@ -97,4 +99,3 @@ Applications .. _GeoVista: https://github.com/bjlittle/geovista .. _PyVista: https://docs.pyvista.org/index.html -.. _iris-esmf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid diff --git a/docs/src/index.rst b/docs/src/index.rst index c5d654ed31..531c0e0b26 100644 --- a/docs/src/index.rst +++ b/docs/src/index.rst @@ -136,6 +136,15 @@ The legacy support resources: developers_guide/contributing_getting_involved +.. toctree:: + :caption: Community + :maxdepth: 1 + :name: community_index + :hidden: + + Community + + .. toctree:: :caption: Iris API :maxdepth: 1 diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 2cdc9cf1f5..903e6880f0 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -82,6 +82,14 @@ This document explains the changes made to Iris for this release and removed an ECMWF link in the ``v1.0`` What's New that was failing the linkcheck CI. (:pull:`5109`) +#. `@trexfeathers`_ added a new top-level :doc:`/community/index` section, + as a one-stop place to find out about getting involved, and how we relate + to other projects. (:pull:`5025`) + +#. The **Iris community**, with help from the **Xarray community**, produced + the :doc:`/community/iris_xarray` page, highlighting the similarities and + differences between the two packages. (:pull:`5025`) + 💼 Internal =========== diff --git a/lib/iris/_lazy_data.py b/lib/iris/_lazy_data.py index ac7ae34511..e0566fc8f2 100644 --- a/lib/iris/_lazy_data.py +++ b/lib/iris/_lazy_data.py @@ -39,7 +39,7 @@ def is_lazy_data(data): """ Return whether the argument is an Iris 'lazy' data array. - At present, this means simply a Dask array. + At present, this means simply a :class:`dask.array.Array`. We determine this by checking for a "compute" property. """ @@ -67,7 +67,8 @@ def _optimum_chunksize_internals( * shape (tuple of int): The full array shape of the target data. * limit (int): - The 'ideal' target chunk size, in bytes. Default from dask.config. + The 'ideal' target chunk size, in bytes. Default from + :mod:`dask.config`. * dtype (np.dtype): Numpy dtype of target data. @@ -77,7 +78,7 @@ def _optimum_chunksize_internals( .. note:: The purpose of this is very similar to - `dask.array.core.normalize_chunks`, when called as + :func:`dask.array.core.normalize_chunks`, when called as `(chunks='auto', shape, dtype=dtype, previous_chunks=chunks, ...)`. Except, the operation here is optimised specifically for a 'c-like' dimension order, i.e. outer dimensions first, as for netcdf variables. @@ -174,13 +175,13 @@ def _optimum_chunksize( def as_lazy_data(data, chunks=None, asarray=False): """ - Convert the input array `data` to a dask array. + Convert the input array `data` to a :class:`dask.array.Array`. Args: * data (array-like): An indexable object with 'shape', 'dtype' and 'ndim' properties. - This will be converted to a dask array. + This will be converted to a :class:`dask.array.Array`. Kwargs: @@ -192,7 +193,7 @@ def as_lazy_data(data, chunks=None, asarray=False): Set to False (default) to pass passed chunks through unchanged. Returns: - The input array converted to a dask array. + The input array converted to a :class:`dask.array.Array`. .. note:: The result chunk size is a multiple of 'chunks', if given, up to the @@ -284,15 +285,16 @@ def multidim_lazy_stack(stack): """ Recursively build a multidimensional stacked dask array. - This is needed because dask.array.stack only accepts a 1-dimensional list. + This is needed because :meth:`dask.array.Array.stack` only accepts a + 1-dimensional list. Args: * stack: - An ndarray of dask arrays. + An ndarray of :class:`dask.array.Array`. Returns: - The input array converted to a lazy dask array. + The input array converted to a lazy :class:`dask.array.Array`. """ if stack.ndim == 0: diff --git a/lib/iris/cube.py b/lib/iris/cube.py index be01bc9e5d..c60896f718 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -884,7 +884,8 @@ def __init__( This object defines the shape of the cube and the phenomenon value in each cell. - ``data`` can be a dask array, a NumPy array, a NumPy array + ``data`` can be a :class:`dask.array.Array`, a + :class:`numpy.ndarray`, a NumPy array subclass (such as :class:`numpy.ma.MaskedArray`), or array_like (as described in :func:`numpy.asarray`). diff --git a/lib/iris/experimental/ugrid/mesh.py b/lib/iris/experimental/ugrid/mesh.py index 4fd09175af..d18e1b2026 100644 --- a/lib/iris/experimental/ugrid/mesh.py +++ b/lib/iris/experimental/ugrid/mesh.py @@ -131,7 +131,7 @@ def __init__( Args: - * indices (numpy.ndarray or numpy.ma.core.MaskedArray or dask.array.Array): + * indices (:class:`numpy.ndarray` or :class:`numpy.ma.core.MaskedArray` or :class:`dask.array.Array`): 2D array giving the topological connection relationship between :attr:`location` elements and :attr:`connected` elements. The :attr:`location_axis` dimension indexes over the @@ -501,7 +501,7 @@ def core_indices(self): NumPy array or a Dask array. Returns: - numpy.ndarray or numpy.ma.core.MaskedArray or dask.array.Array + :class:`numpy.ndarray` or :class:`numpy.ma.core.MaskedArray` or :class:`dask.array.Array` """ return super()._core_values() diff --git a/lib/iris/pandas.py b/lib/iris/pandas.py index faa250285e..417b6b11de 100644 --- a/lib/iris/pandas.py +++ b/lib/iris/pandas.py @@ -238,7 +238,7 @@ def as_cubes( A :class:`~pandas.DataFrame` using columns as a second data dimension will need to be 'melted' before conversion. See the Examples for how. - Dask ``DataFrame``\\s are not supported. + :class:`dask.dataframe.DataFrame`\\ s are not supported. Examples -------- @@ -686,7 +686,7 @@ def as_data_frame( Notes ----- - Dask ``DataFrame``\\s are not supported. + :class:`dask.dataframe.DataFrame`\\ s are not supported. A :class:`~pandas.MultiIndex` :class:`~pandas.DataFrame` is returned by default. Use the :meth:`~pandas.DataFrame.reset_index` to return a diff --git a/lib/iris/util.py b/lib/iris/util.py index 3d82ea68c5..1cf41e6fb6 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -1815,8 +1815,9 @@ def _mask_array(array, points_to_mask, in_place=False): If array is lazy then in_place is ignored: _math_op_common will use the returned value regardless of in_place, so we do not need to implement it - here. If in_place is True then array must be a np.ma.MaskedArray or dask - array (must be a dask array if points_to_mask is lazy). + here. If in_place is True then array must be a + :class:`numpy.ma.MaskedArray` or :class:`dask.array.Array` + (must be a dask array if points_to_mask is lazy). """ # Decide which array library to use. @@ -1978,7 +1979,7 @@ def is_masked(array): Parameters ---------- - array : :class:`numpy.Array` or `dask.array.Array` + array : :class:`numpy.Array` or :class:`dask.array.Array` The array to be checked for masks. Returns From 30bcc4c0d277e3b43e74c9d23196373c2a89ce34 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Jan 2023 09:51:10 +0000 Subject: [PATCH 308/319] [pre-commit.ci] pre-commit autoupdate (#5143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/isort: 5.11.4 → 5.12.0](https://github.com/pycqa/isort/compare/5.11.4...5.12.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c1b0400e1..0233955d77 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: args: [--config=./setup.cfg] - repo: https://github.com/pycqa/isort - rev: 5.11.4 + rev: 5.12.0 hooks: - id: isort types: [file, python] From 7181bbc75d1aa9dc2209a5f7ed45b291aa2c82bd Mon Sep 17 00:00:00 2001 From: Bill Little Date: Tue, 31 Jan 2023 11:34:44 +0000 Subject: [PATCH 309/319] add readme #showyourstripes (#5141) * add readme #showyourstripes * use defacto url src * update whatsnew * licensed to distributed --- README.md | 21 +++++++++++++++++++++ docs/src/whatsnew/latest.rst | 10 ++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac2781f469..cdf4b2b043 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,24 @@ For documentation see the developer version or the most recent released stable version.

    + +## [#ShowYourStripes](https://showyourstripes.info/s/globe) + +

    + + #showyourstripes Global 1850-2021 +

    + +**Graphics and Lead Scientist**: [Ed Hawkins](http://www.met.reading.ac.uk/~ed/home/index.php), National Centre for Atmospheric Science, University of Reading. + +**Data**: Berkeley Earth, NOAA, UK Met Office, MeteoSwiss, DWD, SMHI, UoR, Meteo France & ZAMG. + +

    +#ShowYourStripes is distributed under a +Creative Commons Attribution 4.0 International License + + creative-commons-by +

    + diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 903e6880f0..8dac1c7614 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -16,7 +16,7 @@ This document explains the changes made to Iris for this release The highlights for this major/minor release of Iris include: - * N/A + * We're so proud to fully support `@ed-hawkins`_ and `#ShowYourStripes`_ ❤️ And finally, get in touch with us on :issue:`GitHub` if you have any issues or feature requests for improving Iris. Enjoy! @@ -90,6 +90,10 @@ This document explains the changes made to Iris for this release the :doc:`/community/iris_xarray` page, highlighting the similarities and differences between the two packages. (:pull:`5025`) +#. `@bjlittle`_ added a new section to the `README.md`_ to show our support + for the outstanding work of `@ed-hawkins`_ et al for `#ShowYourStripes`_. + (:pull:`5141`) + 💼 Internal =========== @@ -105,8 +109,10 @@ This document explains the changes made to Iris for this release core dev names are automatically included by the common_links.inc: .. _@fnattino: https://github.com/fnattino - +.. _@ed-hawkins: https://github.com/ed-hawkins .. comment Whatsnew resources in alphabetical order: +.. _#ShowYourStripes: https://showyourstripes.info/s/globe/ +.. _README.md: https://github.com/SciTools/iris#----- From 7da248cc8c8b30902bf2343b4945da373f9c58ab Mon Sep 17 00:00:00 2001 From: Henry Wright <84939917+HGWright@users.noreply.github.com> Date: Fri, 3 Feb 2023 16:39:36 +0000 Subject: [PATCH 310/319] Fixing typo's in Gitwash. (#5145) * Fixing typo's in Gitwash. * Updating the whatsnew to implement requested changes --- docs/src/common_links.inc | 3 ++- docs/src/developers_guide/gitwash/forking.rst | 2 +- docs/src/developers_guide/gitwash/set_up_fork.rst | 2 +- docs/src/whatsnew/latest.rst | 3 +++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index bf1567e917..c8a7fef74b 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -9,7 +9,7 @@ .. _conda: https://docs.conda.io/en/latest/ .. _contributor: https://github.com/SciTools/scitools.org.uk/blob/master/contributors.json .. _core developers: https://github.com/SciTools/scitools.org.uk/blob/master/contributors.json -.. _generating sss keys for GitHub: https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account +.. _generating ssh keys for GitHub: https://docs.github.com/en/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account .. _GitHub Actions: https://docs.github.com/en/actions .. _GitHub Help Documentation: https://docs.github.com/en/github .. _GitHub Discussions: https://github.com/SciTools/iris/discussions @@ -57,6 +57,7 @@ .. _@DPeterK: https://github.com/DPeterK .. _@ESadek-MO: https://github.com/ESadek-MO .. _@esc24: https://github.com/esc24 +.. _@HGWright: https://github.com/HGWright .. _@jamesp: https://github.com/jamesp .. _@jonseddon: https://github.com/jonseddon .. _@jvegasbsc: https://github.com/jvegasbsc diff --git a/docs/src/developers_guide/gitwash/forking.rst b/docs/src/developers_guide/gitwash/forking.rst index 247e3cf678..baeb243c86 100644 --- a/docs/src/developers_guide/gitwash/forking.rst +++ b/docs/src/developers_guide/gitwash/forking.rst @@ -18,7 +18,7 @@ Set up and Configure a Github Account If you don't have a github account, go to the github page, and make one. You then need to configure your account to allow write access, see -the `generating sss keys for GitHub`_ help on `github help`_. +the `generating ssh keys for GitHub`_ help on `github help`_. Create Your own Forked Copy of Iris diff --git a/docs/src/developers_guide/gitwash/set_up_fork.rst b/docs/src/developers_guide/gitwash/set_up_fork.rst index d5c5bc5c44..5318825488 100644 --- a/docs/src/developers_guide/gitwash/set_up_fork.rst +++ b/docs/src/developers_guide/gitwash/set_up_fork.rst @@ -15,7 +15,7 @@ Overview git clone git@github.com:your-user-name/iris.git cd iris - git remote add upstream git://github.com/SciTools/iris.git + git remote add upstream git@github.com/SciTools/iris.git In Detail ========= diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 8dac1c7614..9d9fa8b342 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -26,6 +26,7 @@ This document explains the changes made to Iris for this release ================ #. Congratulations to `@ESadek-MO`_ who has become a core developer for Iris! 🎉 +#. Welcome and congratulations to `@HGWright`_ for making his first contribution to Iris! 🎉 ✨ Features @@ -94,6 +95,8 @@ This document explains the changes made to Iris for this release for the outstanding work of `@ed-hawkins`_ et al for `#ShowYourStripes`_. (:pull:`5141`) +#. `@HGWright`_ fixed some typo's from Gitwash. (:pull:`5145`) + 💼 Internal =========== From b08cfa63bb435566546d9deb52985ea4561cf391 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Feb 2023 10:10:52 +0000 Subject: [PATCH 311/319] [pre-commit.ci] pre-commit autoupdate (#5150) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 22.12.0 → 23.1.0](https://github.com/psf/black/compare/22.12.0...23.1.0) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- .../general/plot_lineplot_with_legend.py | 1 - .../plot_projections_and_annotations.py | 1 - docs/gallery_code/general/plot_zonal_means.py | 1 - .../meteorology/plot_lagged_ensemble.py | 1 - .../plotting_examples/1d_with_legend.py | 1 - lib/iris/__init__.py | 1 - lib/iris/_merge.py | 1 + lib/iris/analysis/__init__.py | 1 - lib/iris/analysis/_area_weighted.py | 2 +- lib/iris/analysis/_interpolation.py | 2 +- lib/iris/analysis/_scipy_interpolate.py | 1 - lib/iris/analysis/calculus.py | 1 - lib/iris/analysis/trajectory.py | 1 - lib/iris/common/metadata.py | 2 + lib/iris/coord_categorisation.py | 1 + lib/iris/coord_systems.py | 2 - lib/iris/coords.py | 1 - lib/iris/cube.py | 1 - lib/iris/experimental/ugrid/mesh.py | 4 +- lib/iris/experimental/ugrid/metadata.py | 5 +++ lib/iris/fileformats/abf.py | 1 - lib/iris/fileformats/name_loaders.py | 2 - lib/iris/fileformats/pp.py | 4 +- lib/iris/fileformats/pp_load_rules.py | 1 - lib/iris/fileformats/rules.py | 2 +- .../um/_fast_load_structured_fields.py | 1 + lib/iris/quickplot.py | 1 - lib/iris/tests/graphics/__init__.py | 2 - lib/iris/tests/integration/test_Datums.py | 1 - lib/iris/tests/test_cdm.py | 1 - lib/iris/tests/test_cf.py | 2 - lib/iris/tests/test_io_init.py | 2 +- lib/iris/tests/test_merge.py | 2 +- lib/iris/tests/test_netcdf.py | 5 +-- lib/iris/tests/test_nimrod.py | 3 +- lib/iris/tests/test_pp_stash.py | 5 +-- lib/iris/tests/test_util.py | 1 - .../cartography/test_gridcell_angles.py | 1 - .../unit/coords/test_AncillaryVariable.py | 43 ++++++++----------- lib/iris/tests/unit/coords/test_AuxCoord.py | 5 --- lib/iris/tests/unit/coords/test_Coord.py | 3 -- lib/iris/tests/unit/coords/test_DimCoord.py | 3 -- lib/iris/tests/unit/cube/test_Cube.py | 1 + .../experimental/ugrid/mesh/test_MeshCoord.py | 4 +- .../actions/test__miscellaneous.py | 1 - .../test_build_dimension_coordinate.py | 4 +- .../fileformats/netcdf/test_Saver__ugrid.py | 1 - .../tests/unit/fileformats/pp/test_PPField.py | 1 - .../pp_load_rules/test__all_other_rules.py | 1 - .../tests/unit/lazy_data/test_as_lazy_data.py | 4 +- lib/iris/tests/unit/merge/test_ProtoCube.py | 4 -- lib/iris/util.py | 1 - 53 files changed, 49 insertions(+), 97 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0233955d77..7c95eeaca3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -29,7 +29,7 @@ repos: - id: no-commit-to-branch - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black pass_filenames: false diff --git a/docs/gallery_code/general/plot_lineplot_with_legend.py b/docs/gallery_code/general/plot_lineplot_with_legend.py index 78401817ba..aad7906acd 100644 --- a/docs/gallery_code/general/plot_lineplot_with_legend.py +++ b/docs/gallery_code/general/plot_lineplot_with_legend.py @@ -24,7 +24,6 @@ def main(): ) for cube in temperature.slices("longitude"): - # Create a string label to identify this cube (i.e. latitude: value). cube_label = "latitude: %s" % cube.coord("latitude").points[0] diff --git a/docs/gallery_code/general/plot_projections_and_annotations.py b/docs/gallery_code/general/plot_projections_and_annotations.py index 75122591b9..2cf42e66e0 100644 --- a/docs/gallery_code/general/plot_projections_and_annotations.py +++ b/docs/gallery_code/general/plot_projections_and_annotations.py @@ -26,7 +26,6 @@ def make_plot(projection_name, projection_crs): - # Create a matplotlib Figure. plt.figure() diff --git a/docs/gallery_code/general/plot_zonal_means.py b/docs/gallery_code/general/plot_zonal_means.py index 08a9578e63..195f8b4bb0 100644 --- a/docs/gallery_code/general/plot_zonal_means.py +++ b/docs/gallery_code/general/plot_zonal_means.py @@ -16,7 +16,6 @@ def main(): - # Loads air_temp.pp and "collapses" longitude into a single, average value. fname = iris.sample_data_path("air_temp.pp") temperature = iris.load_cube(fname) diff --git a/docs/gallery_code/meteorology/plot_lagged_ensemble.py b/docs/gallery_code/meteorology/plot_lagged_ensemble.py index 5cd2752f39..e15aa0e6ef 100644 --- a/docs/gallery_code/meteorology/plot_lagged_ensemble.py +++ b/docs/gallery_code/meteorology/plot_lagged_ensemble.py @@ -86,7 +86,6 @@ def main(): # Iterate over all possible latitude longitude slices. for cube in last_timestep.slices(["latitude", "longitude"]): - # Get the ensemble member number from the ensemble coordinate. ens_member = cube.coord("realization").points[0] diff --git a/docs/src/userguide/plotting_examples/1d_with_legend.py b/docs/src/userguide/plotting_examples/1d_with_legend.py index 9b9fd8a49d..626335af45 100644 --- a/docs/src/userguide/plotting_examples/1d_with_legend.py +++ b/docs/src/userguide/plotting_examples/1d_with_legend.py @@ -13,7 +13,6 @@ temperature = temperature[5:9, :] for cube in temperature.slices("longitude"): - # Create a string label to identify this cube (i.e. latitude: value) cube_label = "latitude: %s" % cube.coord("latitude").points[0] diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index 896b850541..a81d25add3 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -175,7 +175,6 @@ def __init__(self, datum_support=False, pandas_ndim=False): self.__dict__["pandas_ndim"] = pandas_ndim def __repr__(self): - # msg = ('Future(example_future_flag={})') # return msg.format(self.example_future_flag) msg = "Future(datum_support={}, pandas_ndim={})" diff --git a/lib/iris/_merge.py b/lib/iris/_merge.py index bc12080523..5ca5f31a8e 100644 --- a/lib/iris/_merge.py +++ b/lib/iris/_merge.py @@ -1418,6 +1418,7 @@ def _define_space(self, space, positions, indexes, function_matrix): participates in a functional relationship. """ + # Heuristic reordering of coordinate defintion indexes into # preferred dimension order. def axis_and_name(name): diff --git a/lib/iris/analysis/__init__.py b/lib/iris/analysis/__init__.py index 55d5d5d93e..f34cda1402 100644 --- a/lib/iris/analysis/__init__.py +++ b/lib/iris/analysis/__init__.py @@ -296,7 +296,6 @@ def _dimensional_metadata_comparison(*cubes, object_get=None): # for coordinate groups for cube, coords in zip(cubes, all_coords): for coord in coords: - # if this coordinate has already been processed, then continue on # to the next one if id(coord) in processed_coords: diff --git a/lib/iris/analysis/_area_weighted.py b/lib/iris/analysis/_area_weighted.py index edbfd41ef9..3b728e9a43 100644 --- a/lib/iris/analysis/_area_weighted.py +++ b/lib/iris/analysis/_area_weighted.py @@ -853,7 +853,7 @@ def _calculate_regrid_area_weighted_weights( cached_x_bounds = [] cached_x_indices = [] max_x_indices = 0 - for (x_0, x_1) in grid_x_bounds: + for x_0, x_1 in grid_x_bounds: if grid_x_decreasing: x_0, x_1 = x_1, x_0 x_bounds, x_indices = _cropped_bounds(src_x_bounds, x_0, x_1) diff --git a/lib/iris/analysis/_interpolation.py b/lib/iris/analysis/_interpolation.py index 2a7dfa6e62..f5e89a9e51 100644 --- a/lib/iris/analysis/_interpolation.py +++ b/lib/iris/analysis/_interpolation.py @@ -268,7 +268,7 @@ def _account_for_circular(self, points, data): """ from iris.analysis.cartography import wrap_lons - for (circular, modulus, index, dim, offset) in self._circulars: + for circular, modulus, index, dim, offset in self._circulars: if modulus: # Map all the requested values into the range of the source # data (centred over the centre of the source data to allow diff --git a/lib/iris/analysis/_scipy_interpolate.py b/lib/iris/analysis/_scipy_interpolate.py index fc64249729..bfa070c7c7 100644 --- a/lib/iris/analysis/_scipy_interpolate.py +++ b/lib/iris/analysis/_scipy_interpolate.py @@ -225,7 +225,6 @@ def compute_interp_weights(self, xi, method=None): prepared = (xi_shape, method) + self._find_indices(xi.T) if method == "linear": - xi_shape, method, indices, norm_distances, out_of_bounds = prepared # Allocate arrays for describing the sparse matrix. diff --git a/lib/iris/analysis/calculus.py b/lib/iris/analysis/calculus.py index c530dbd216..75b7d86406 100644 --- a/lib/iris/analysis/calculus.py +++ b/lib/iris/analysis/calculus.py @@ -594,7 +594,6 @@ def curl(i_cube, j_cube, k_cube=None): horiz_cs, (iris.coord_systems.GeogCS, iris.coord_systems.RotatedGeogCS) ) if not spherical_coords: - # TODO Implement some mechanism for conforming to a common grid dj_dx = _curl_differentiate(j_cube, x_coord) prototype_diff = dj_dx diff --git a/lib/iris/analysis/trajectory.py b/lib/iris/analysis/trajectory.py index c21d71d48c..24f7a9dede 100644 --- a/lib/iris/analysis/trajectory.py +++ b/lib/iris/analysis/trajectory.py @@ -85,7 +85,6 @@ def __init__(self, waypoints, sample_count=10): cur_seg = segments[cur_seg_i] len_accum = cur_seg.length for p in range(self.sample_count): - # calculate the sample position along our total length sample_at_len = p * sample_step diff --git a/lib/iris/common/metadata.py b/lib/iris/common/metadata.py index 8ec39bb4b1..cb3149fe58 100644 --- a/lib/iris/common/metadata.py +++ b/lib/iris/common/metadata.py @@ -969,6 +969,7 @@ def _combine_lenient(self, other): A list of combined metadata member values. """ + # Perform "strict" combination for "coord_system" and "climatological". def func(field): left = getattr(self, field) @@ -1024,6 +1025,7 @@ def _difference_lenient(self, other): A list of difference metadata member values. """ + # Perform "strict" difference for "coord_system" and "climatological". def func(field): left = getattr(self, field) diff --git a/lib/iris/coord_categorisation.py b/lib/iris/coord_categorisation.py index 72019b4b87..698b4828f1 100644 --- a/lib/iris/coord_categorisation.py +++ b/lib/iris/coord_categorisation.py @@ -90,6 +90,7 @@ def vectorised_fn(*args): # coordinates only # + # Private "helper" function def _pt_date(coord, time): """ diff --git a/lib/iris/coord_systems.py b/lib/iris/coord_systems.py index 802571925e..edf0c1871b 100644 --- a/lib/iris/coord_systems.py +++ b/lib/iris/coord_systems.py @@ -478,7 +478,6 @@ def datum(self, value): @classmethod def from_datum(cls, datum, longitude_of_prime_meridian=None): - crs = super().__new__(cls) crs._semi_major_axis = None @@ -949,7 +948,6 @@ def __init__( false_northing=None, ellipsoid=None, ): - """ Constructs a Geostationary coord system. diff --git a/lib/iris/coords.py b/lib/iris/coords.py index cfcdcd92a0..91bb786ae8 100644 --- a/lib/iris/coords.py +++ b/lib/iris/coords.py @@ -2846,7 +2846,6 @@ def _new_bounds_requirements(self, bounds): n_bounds = bounds.shape[-1] n_points = bounds.shape[0] if n_points > 1: - directions = set() for b_index in range(n_bounds): monotonic, direction = iris.util.monotonic( diff --git a/lib/iris/cube.py b/lib/iris/cube.py index c60896f718..abe37c35fb 100644 --- a/lib/iris/cube.py +++ b/lib/iris/cube.py @@ -2688,7 +2688,6 @@ def subset(self, coord): coord_to_extract in self.aux_coords and len(coord_to_extract.points) == 1 ): - # Default to returning None result = None diff --git a/lib/iris/experimental/ugrid/mesh.py b/lib/iris/experimental/ugrid/mesh.py index d18e1b2026..0d566da73f 100644 --- a/lib/iris/experimental/ugrid/mesh.py +++ b/lib/iris/experimental/ugrid/mesh.py @@ -3127,9 +3127,7 @@ def _construct_access_arrays(self): flat_inds_safe = al.where(missing_inds, 0, flat_inds_nomask) # Here's the core indexing operation. # The comma applies all inds-array values to the *first* dimension. - bounds = node_points[ - flat_inds_safe, - ] + bounds = node_points[flat_inds_safe,] # Fix 'missing' locations, and restore the proper shape. bounds = al.ma.masked_array(bounds, missing_inds) bounds = bounds.reshape(indices.shape) diff --git a/lib/iris/experimental/ugrid/metadata.py b/lib/iris/experimental/ugrid/metadata.py index ae0b787908..44bbe04fe9 100644 --- a/lib/iris/experimental/ugrid/metadata.py +++ b/lib/iris/experimental/ugrid/metadata.py @@ -53,6 +53,7 @@ def _combine_lenient(self, other): A list of combined metadata member values. """ + # Perform "strict" combination for "cf_role", "start_index", "location_axis". def func(field): left = getattr(self, field) @@ -113,6 +114,7 @@ def _difference_lenient(self, other): A list of difference metadata member values. """ + # Perform "strict" difference for "cf_role", "start_index", "location_axis". def func(field): left = getattr(self, field) @@ -233,6 +235,7 @@ def _difference_lenient(self, other): A list of difference metadata member values. """ + # Perform "strict" difference for "topology_dimension", # "node_dimension", "edge_dimension" and "face_dimension". def func(field): @@ -297,6 +300,7 @@ def _combine_lenient(self, other): A list of combined metadata member values. """ + # It is actually "strict" : return None except where members are equal. def func(field): left = getattr(self, field) @@ -352,6 +356,7 @@ def _difference_lenient(self, other): A list of different metadata member values. """ + # Perform "strict" difference for location / axis. def func(field): left = getattr(self, field) diff --git a/lib/iris/fileformats/abf.py b/lib/iris/fileformats/abf.py index 5c70c5acf2..4dcd5ce6aa 100644 --- a/lib/iris/fileformats/abf.py +++ b/lib/iris/fileformats/abf.py @@ -219,7 +219,6 @@ def load_cubes(filespecs, callback=None): for filespec in filespecs: for filename in glob.glob(filespec): - field = ABFField(filename) cube = field.to_cube() diff --git a/lib/iris/fileformats/name_loaders.py b/lib/iris/fileformats/name_loaders.py index d15a3717d0..b9b64a343e 100644 --- a/lib/iris/fileformats/name_loaders.py +++ b/lib/iris/fileformats/name_loaders.py @@ -994,7 +994,6 @@ def load_NAMEIII_version2(filename): # using the next() method. This will come in handy as we wish to # progress through the file line by line. with open(filename, "r") as file_handle: - # define a dictionary to hold the header metadata about this file header = read_header(file_handle) @@ -1005,7 +1004,6 @@ def load_NAMEIII_version2(filename): column_headings = {} datacol1 = header["Number of preliminary cols"] for line in file_handle: - data = [col.strip() for col in line.split(",")][:-1] # If first column is not zero we have reached the end diff --git a/lib/iris/fileformats/pp.py b/lib/iris/fileformats/pp.py index bc35acb3b3..cff088cf89 100644 --- a/lib/iris/fileformats/pp.py +++ b/lib/iris/fileformats/pp.py @@ -625,7 +625,7 @@ def __getstate__(self): def __setstate__(self, state): # Because we have __slots__, this is needed to support Pickle.load() # (Use setattr, as there is no object dictionary.) - for (key, value) in state: + for key, value in state: setattr(self, key, value) def __eq__(self, other): @@ -2029,10 +2029,8 @@ def pp_filter(field): res = True if field.stash not in _STASH_ALLOW: if pp_constraints.get("stash"): - res = False for call_func in pp_constraints["stash"]: - if call_func(str(field.stash)): res = True break diff --git a/lib/iris/fileformats/pp_load_rules.py b/lib/iris/fileformats/pp_load_rules.py index ebccec47ee..11d03e978a 100644 --- a/lib/iris/fileformats/pp_load_rules.py +++ b/lib/iris/fileformats/pp_load_rules.py @@ -756,7 +756,6 @@ def date2year(t_in): ) ) ): - coords_and_dims.append( _new_coord_and_dims( do_vector, diff --git a/lib/iris/fileformats/rules.py b/lib/iris/fileformats/rules.py index 07ed5eb8ce..51940b7c4d 100644 --- a/lib/iris/fileformats/rules.py +++ b/lib/iris/fileformats/rules.py @@ -394,7 +394,7 @@ def _load_pairs_from_fields_and_filenames( yield (cube, field) regrid_cache = {} - for (cube, factories, field) in results_needing_reference: + for cube, factories, field in results_needing_reference: _resolve_factory_references( cube, factories, concrete_reference_targets, regrid_cache ) diff --git a/lib/iris/fileformats/um/_fast_load_structured_fields.py b/lib/iris/fileformats/um/_fast_load_structured_fields.py index d193aa30ce..64b7f8e891 100644 --- a/lib/iris/fileformats/um/_fast_load_structured_fields.py +++ b/lib/iris/fileformats/um/_fast_load_structured_fields.py @@ -133,6 +133,7 @@ def element_arrays_and_dims(self): def _field_vector_element_arrays(self): """Define the field components used in the structure analysis.""" + # Define functions to make t1 and t2 values as date-time tuples. # These depend on header version (PPField2 has no seconds values). def t1_fn(fld): diff --git a/lib/iris/quickplot.py b/lib/iris/quickplot.py index 18ed2554a3..6006314265 100644 --- a/lib/iris/quickplot.py +++ b/lib/iris/quickplot.py @@ -45,7 +45,6 @@ def _title(cube_or_coord, with_units): or units.is_no_unit() or units == cf_units.Unit("1") ): - if _use_symbol(units): units = units.symbol elif units.is_time_reference(): diff --git a/lib/iris/tests/graphics/__init__.py b/lib/iris/tests/graphics/__init__.py index a083de3934..544d989564 100755 --- a/lib/iris/tests/graphics/__init__.py +++ b/lib/iris/tests/graphics/__init__.py @@ -187,7 +187,6 @@ def check_graphic(test_id: str, results_dir: Union[str, Path]) -> None: try: def _create_missing(phash: str) -> None: - output_path = test_output_dir / (test_id + ".png") print(f"Creating image file: {output_path}") @@ -214,7 +213,6 @@ def _create_missing(phash: str) -> None: phash = get_phash(buffer) if test_id in repo: - expected = hex_to_hash(repo[test_id]) # Calculate hamming distance vector for the result hash. diff --git a/lib/iris/tests/integration/test_Datums.py b/lib/iris/tests/integration/test_Datums.py index 77b7f28249..6953534f2d 100755 --- a/lib/iris/tests/integration/test_Datums.py +++ b/lib/iris/tests/integration/test_Datums.py @@ -23,7 +23,6 @@ def setUp(self): self.start_crs = ccrs.OSGB(False) def test_transform_points_datum(self): - # Iris version wgs84 = GeogCS.from_datum("WGS84") iris_cs = LambertConformal( diff --git a/lib/iris/tests/test_cdm.py b/lib/iris/tests/test_cdm.py index 0615dc39bf..8f2a9b474d 100644 --- a/lib/iris/tests/test_cdm.py +++ b/lib/iris/tests/test_cdm.py @@ -349,7 +349,6 @@ def test_similar_coord(self): ) def test_cube_summary_cell_methods(self): - cube = self.cube_2d.copy() # Create a list of values used to create cell methods diff --git a/lib/iris/tests/test_cf.py b/lib/iris/tests/test_cf.py index 034fb1dbda..bf3cddb8b7 100644 --- a/lib/iris/tests/test_cf.py +++ b/lib/iris/tests/test_cf.py @@ -276,9 +276,7 @@ def test_destructor(self): didn't exist because opening the dataset had failed. """ with self.temp_filename(suffix=".nc") as fn: - with open(fn, "wb+") as fh: - fh.write( b"\x89HDF\r\n\x1a\nBroken file with correct signature" ) diff --git a/lib/iris/tests/test_io_init.py b/lib/iris/tests/test_io_init.py index d33b76ddeb..82da82cfa9 100644 --- a/lib/iris/tests/test_io_init.py +++ b/lib/iris/tests/test_io_init.py @@ -126,7 +126,7 @@ def test_format_picker(self): ] # test that each filespec is identified as the expected format - for (expected_format_name, file_spec) in test_specs: + for expected_format_name, file_spec in test_specs: test_path = tests.get_data_path(file_spec) with open(test_path, "rb") as test_file: a = iff.FORMAT_AGENT.get_spec(test_path, test_file) diff --git a/lib/iris/tests/test_merge.py b/lib/iris/tests/test_merge.py index c209d68da0..e53bbfb5f3 100644 --- a/lib/iris/tests/test_merge.py +++ b/lib/iris/tests/test_merge.py @@ -190,7 +190,7 @@ def setUp(self): ) def test__ndarray_ndarray(self): - for (lazy0, lazy1) in self.lazy_combos: + for lazy0, lazy1 in self.lazy_combos: cubes = iris.cube.CubeList() cubes.append(self._make_cube(0, dtype=self.dtype, lazy=lazy0)) cubes.append(self._make_cube(1, dtype=self.dtype, lazy=lazy1)) diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py index 5017698a22..92e15a414a 100644 --- a/lib/iris/tests/test_netcdf.py +++ b/lib/iris/tests/test_netcdf.py @@ -313,9 +313,7 @@ def test_deferred_loading(self): cube[((0, 8, 4, 2, 14, 12),)][((0, 2, 4, 1),)], ("netcdf", "netcdf_deferred_tuple_1.cml"), ) - subcube = cube[((0, 8, 4, 2, 14, 12),)][((0, 2, 4, 1),)][ - (1, 3), - ] + subcube = cube[((0, 8, 4, 2, 14, 12),)][((0, 2, 4, 1),)][(1, 3),] self.assertCML(subcube, ("netcdf", "netcdf_deferred_tuple_2.cml")) # Consecutive mixture on same dimension. @@ -1417,7 +1415,6 @@ def test_process_flags(self): } for bits, descriptions in multiple_map.items(): - ll_cube = stock.lat_lon_cube() ll_cube.attributes["ukmo__process_flags"] = descriptions diff --git a/lib/iris/tests/test_nimrod.py b/lib/iris/tests/test_nimrod.py index a1d7bb298f..6d62623198 100644 --- a/lib/iris/tests/test_nimrod.py +++ b/lib/iris/tests/test_nimrod.py @@ -80,7 +80,8 @@ def test_huge_field_load(self): @tests.skip_data def test_load_kwarg(self): """Tests that the handle_metadata_errors kwarg is effective by setting it to - False with a file with known incomplete meta-data (missing ellipsoid).""" + False with a file with known incomplete meta-data (missing ellipsoid). + """ datafile = "u1096_ng_ek00_pressure_2km" with self.assertRaisesRegex( TranslationError, diff --git a/lib/iris/tests/test_pp_stash.py b/lib/iris/tests/test_pp_stash.py index b153aef0d4..42390ab2b3 100644 --- a/lib/iris/tests/test_pp_stash.py +++ b/lib/iris/tests/test_pp_stash.py @@ -86,7 +86,6 @@ def test_irregular_stash_str(self): ) def test_illegal_stash_str_range(self): - self.assertEqual(iris.fileformats.pp.STASH(0, 2, 3), "m??s02i003") self.assertNotEqual(iris.fileformats.pp.STASH(0, 2, 3), "m01s02i003") @@ -124,7 +123,7 @@ def test_illegal_stash_format(self): ("m01s02003", (1, 2, 3)), ) - for (test_value, reference) in test_values: + for test_value, reference in test_values: msg = "Expected STASH code .* {!r}".format(test_value) with self.assertRaisesRegex(ValueError, msg): test_value == iris.fileformats.pp.STASH(*reference) @@ -137,7 +136,7 @@ def test_illegal_stash_type(self): (["m01s02i003"], "m01s02i003"), ) - for (test_value, reference) in test_values: + for test_value, reference in test_values: msg = "Expected STASH code .* {!r}".format(test_value) with self.assertRaisesRegex(TypeError, msg): iris.fileformats.pp.STASH.from_msi(test_value) == reference diff --git a/lib/iris/tests/test_util.py b/lib/iris/tests/test_util.py index db182ae3f3..d8d5d73e95 100644 --- a/lib/iris/tests/test_util.py +++ b/lib/iris/tests/test_util.py @@ -161,7 +161,6 @@ def test_default_values(self): ) def test_trim_string_with_no_spaces(self): - clip_length = 200 no_space_string = "a" * 500 diff --git a/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py b/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py index 6b957baec6..810851362e 100644 --- a/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py +++ b/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py @@ -94,7 +94,6 @@ def _check_multiple_orientations_and_latitudes( atol_degrees=0.005, cellsize_degrees=1.0, ): - cube = _2d_multicells_testcube(cellsize_degrees=cellsize_degrees) # Calculate gridcell angles at each point. diff --git a/lib/iris/tests/unit/coords/test_AncillaryVariable.py b/lib/iris/tests/unit/coords/test_AncillaryVariable.py index 75b6250449..e5fc8fd28a 100644 --- a/lib/iris/tests/unit/coords/test_AncillaryVariable.py +++ b/lib/iris/tests/unit/coords/test_AncillaryVariable.py @@ -68,7 +68,7 @@ def setUp(self): self.setupTestArrays(masked=True) def test_lazyness_and_dtype_combinations(self): - for (ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses( + for ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( self, ): data = ancill_var.core_data() @@ -225,10 +225,9 @@ def test_dtypes(self): # floating dtype. # Check that dtypes remain the same in all cases, taking the dtypes # directly from the core data as we have no masking). - for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses( + for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( self ): - sub_ancill_var = main_ancill_var[:2, 1] ancill_var_dtype = main_ancill_var.dtype @@ -250,10 +249,9 @@ def test_lazyness(self): # Index ancillary variables with real+lazy data, and either an int or # floating dtype. # Check that lazy data stays lazy and real stays real, in all cases. - for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses( + for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( self ): - sub_ancill_var = main_ancill_var[:2, 1] msg = ( @@ -277,10 +275,9 @@ def test_lazyness(self): def test_real_data_copies(self): # Index ancillary variables with real+lazy data. # In all cases, check that any real arrays are copied by the indexing. - for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses( + for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( self ): - sub_ancill_var = main_ancill_var[:2, 1] msg = ( @@ -308,10 +305,9 @@ def test_lazyness(self): # Copy ancillary variables with real+lazy data, and either an int or # floating dtype. # Check that lazy data stays lazy and real stays real, in all cases. - for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses( + for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( self ): - ancill_var_dtype = main_ancill_var.dtype copied_ancill_var = main_ancill_var.copy() @@ -338,10 +334,9 @@ def test_lazyness(self): def test_realdata_copies(self): # Copy ancillary variables with real+lazy data. # In all cases, check that any real arrays are copies, not views. - for (main_ancill_var, data_lazyness) in data_all_dtypes_and_lazynesses( + for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( self ): - copied_ancill_var = main_ancill_var.copy() msg = ( @@ -520,79 +515,79 @@ def _check(self, result_ancill_var, expected_data, lazyness): self.assertEqualLazyArraysAndDtypes(expected_data, data) def test_add(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = ancill_var + 10 expected_data = orig_data + 10 self._check(result, expected_data, data_lazyness) def test_add_inplace(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: ancill_var += 10 expected_data = orig_data + 10 self._check(ancill_var, expected_data, data_lazyness) def test_right_add(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = 10 + ancill_var expected_data = 10 + orig_data self._check(result, expected_data, data_lazyness) def test_subtract(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = ancill_var - 10 expected_data = orig_data - 10 self._check(result, expected_data, data_lazyness) def test_subtract_inplace(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: ancill_var -= 10 expected_data = orig_data - 10 self._check(ancill_var, expected_data, data_lazyness) def test_right_subtract(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = 10 - ancill_var expected_data = 10 - orig_data self._check(result, expected_data, data_lazyness) def test_multiply(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = ancill_var * 10 expected_data = orig_data * 10 self._check(result, expected_data, data_lazyness) def test_multiply_inplace(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: ancill_var *= 10 expected_data = orig_data * 10 self._check(ancill_var, expected_data, data_lazyness) def test_right_multiply(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = 10 * ancill_var expected_data = 10 * orig_data self._check(result, expected_data, data_lazyness) def test_divide(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = ancill_var / 10 expected_data = orig_data / 10 self._check(result, expected_data, data_lazyness) def test_divide_inplace(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: ancill_var /= 10 expected_data = orig_data / 10 self._check(ancill_var, expected_data, data_lazyness) def test_right_divide(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = 10 / ancill_var expected_data = 10 / orig_data self._check(result, expected_data, data_lazyness) def test_negative(self): - for (ancill_var, orig_data, data_lazyness) in self.test_combinations: + for ancill_var, orig_data, data_lazyness in self.test_combinations: result = -ancill_var expected_data = -orig_data self._check(result, expected_data, data_lazyness) diff --git a/lib/iris/tests/unit/coords/test_AuxCoord.py b/lib/iris/tests/unit/coords/test_AuxCoord.py index e6cd8ac821..e5147659fc 100644 --- a/lib/iris/tests/unit/coords/test_AuxCoord.py +++ b/lib/iris/tests/unit/coords/test_AuxCoord.py @@ -370,7 +370,6 @@ def test_dtypes(self): points_type_name, bounds_type_name, ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - sub_coord = main_coord[:2, 1] coord_dtype = main_coord.dtype @@ -417,7 +416,6 @@ def test_lazyness(self): points_type_name, bounds_type_name, ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - sub_coord = main_coord[:2, 1] msg = ( @@ -463,7 +461,6 @@ def test_real_data_copies(self): points_lazyness, bounds_lazyness, ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - sub_coord = main_coord[:2, 1] msg = ( @@ -511,7 +508,6 @@ def test_lazyness(self): points_lazyness, bounds_lazyness, ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - coord_dtype = main_coord.dtype copied_coord = main_coord.copy() @@ -558,7 +554,6 @@ def test_realdata_copies(self): points_lazyness, bounds_lazyness, ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - copied_coord = main_coord.copy() msg = ( diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py index dca6ed3c1b..72a48437ec 100644 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ b/lib/iris/tests/unit/coords/test_Coord.py @@ -463,7 +463,6 @@ def test_lazy_nd_bounds_last(self): ) def test_lazy_nd_points_and_bounds(self): - self.setupTestArrays((3, 4)) coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy) @@ -520,7 +519,6 @@ def test_lazy_nd_noncontiguous_bounds_warning(self): coord.collapsed() def test_numeric_3_bounds(self): - points = np.array([2.0, 6.0, 4.0]) bounds = np.array([[1.0, 0.0, 3.0], [5.0, 4.0, 7.0], [3.0, 2.0, 5.0]]) @@ -544,7 +542,6 @@ def test_numeric_3_bounds(self): ) def test_lazy_3_bounds(self): - points = da.arange(3) * 2.0 bounds = da.arange(3 * 3).reshape(3, 3) diff --git a/lib/iris/tests/unit/coords/test_DimCoord.py b/lib/iris/tests/unit/coords/test_DimCoord.py index 4298b140ea..dd0ba48f3d 100644 --- a/lib/iris/tests/unit/coords/test_DimCoord.py +++ b/lib/iris/tests/unit/coords/test_DimCoord.py @@ -304,7 +304,6 @@ def test_dtypes(self): points_type_name, bounds_type_name, ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - sub_coord = main_coord[:2] coord_dtype = main_coord.dtype @@ -404,7 +403,6 @@ def test_real_data_copies(self): points_lazyness, bounds_lazyness, ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - sub_coord = main_coord[:2] msg = ( @@ -470,7 +468,6 @@ def test_realdata_readonly(self): points_type_name, bounds_type_name, ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - copied_coord = main_coord.copy() copied_points = copied_coord.core_points() diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py index 5d120a6982..8e9e00dce8 100644 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ b/lib/iris/tests/unit/cube/test_Cube.py @@ -1962,6 +1962,7 @@ def _assert_lists_equal(self, items_a, items_b): a different order. """ + # Compare (and thus sort) by their *common* metadata. def sortkey(item): return BaseMetadata.from_metadata(item.metadata) diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py b/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py index 538cecdc7d..03e2793fd9 100644 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py +++ b/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py @@ -235,9 +235,7 @@ class Test__getitem__(tests.IrisTest): def test_slice_wholeslice_1tuple(self): # The only slicing case that we support, to enable cube slicing. meshcoord = sample_meshcoord() - meshcoord2 = meshcoord[ - :, - ] + meshcoord2 = meshcoord[:,] self.assertIsNot(meshcoord2, meshcoord) self.assertEqual(meshcoord2, meshcoord) # In this case, we should *NOT* copy the linked Mesh object. diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py index a8e44747dd..ffe00c8c19 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py @@ -127,7 +127,6 @@ def _make_testcase_cdl( include_cellmeasure=False, include_ancil=False, ): - phenom_extra_attrs_string = "" extra_vars_string = "" diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py index b485937cb1..bc13975441 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py @@ -229,7 +229,9 @@ def test_aux_coord_construction(self): warning_patch = mock.patch("warnings.warn") # Asserts must lie within context manager because of deferred loading. - with warning_patch, self.deferred_load_patch, self.get_cf_bounds_var_patch: + with ( + warning_patch + ), self.deferred_load_patch, self.get_cf_bounds_var_patch: build_dimension_coordinate(self.engine, self.cf_coord_var) # Test that expected coord is built and added to cube. diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py index 575c852ece..18e86a9f57 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py @@ -1082,7 +1082,6 @@ def test_mesh_dim_names(self): ("dim invalid-name &%!", "dim_invalid_name____"), ] for given_name, expected_name in dim_names_tests: - mesh = make_mesh(mesh_kwargs={"face_dimension": given_name}) filepath = self.check_save_mesh(mesh) diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPField.py b/lib/iris/tests/unit/fileformats/pp/test_PPField.py index 5e2bbcaa2c..316894ded1 100644 --- a/lib/iris/tests/unit/fileformats/pp/test_PPField.py +++ b/lib/iris/tests/unit/fileformats/pp/test_PPField.py @@ -44,7 +44,6 @@ class DummyPPField(PPField): - HEADER_DEFN = DUMMY_HEADER HEADER_DICT = dict(DUMMY_HEADER) diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py index 62eb7ff019..e194e240c6 100644 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py +++ b/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py @@ -211,7 +211,6 @@ def test_lbcode3x23(self): class TestLBTIMx2x_ZeroYears(TestField): - _spec = [ "lbtim", "lbcode", diff --git a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py b/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py index 5aeebd6045..5f9dece153 100644 --- a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py +++ b/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py @@ -68,7 +68,7 @@ def test_chunk_size_limiting(self): ((11, 2, 1011, 1022), (5, 2, 1011, 1022)), ] err_fmt = "Result of optimising chunks {} was {}, expected {}" - for (shape, expected) in given_shapes_and_resulting_chunks: + for shape, expected in given_shapes_and_resulting_chunks: chunks = _optimum_chunksize( shape, shape, limit=self.FIXED_CHUNKSIZE_LIMIT ) @@ -86,7 +86,7 @@ def test_chunk_size_expanding(self): ((3, 300, 200), (117, 300, 1000), (39, 300, 1000)), ] err_fmt = "Result of optimising shape={};chunks={} was {}, expected {}" - for (shape, fullshape, expected) in given_shapes_and_resulting_chunks: + for shape, fullshape, expected in given_shapes_and_resulting_chunks: chunks = _optimum_chunksize( chunks=shape, shape=fullshape, limit=self.FIXED_CHUNKSIZE_LIMIT ) diff --git a/lib/iris/tests/unit/merge/test_ProtoCube.py b/lib/iris/tests/unit/merge/test_ProtoCube.py index 625290ad24..0fca726b28 100644 --- a/lib/iris/tests/unit/merge/test_ProtoCube.py +++ b/lib/iris/tests/unit/merge/test_ProtoCube.py @@ -289,7 +289,6 @@ def test_noise(self): class Test_register__CoordSig_general(_MergeTest, tests.IrisTest): - _mergetest_type = "coord" def setUp(self): @@ -444,7 +443,6 @@ def test_coord_system(self): class Test_register__CoordSig_scalar(_MergeTest_coordprops, tests.IrisTest): - _mergetest_type = "aux_coords (scalar)" def setUp(self): @@ -486,7 +484,6 @@ def test_dims(self): class Test_register__CoordSig_dim(_MergeTest_coordprops_vect, tests.IrisTest): - _mergetest_type = "dim_coords" _coord_typename = "dim_coord" @@ -515,7 +512,6 @@ def test_circular(self): class Test_register__CoordSig_aux(_MergeTest_coordprops_vect, tests.IrisTest): - _mergetest_type = "aux_coords (non-scalar)" _coord_typename = "aux_coord" diff --git a/lib/iris/util.py b/lib/iris/util.py index 1cf41e6fb6..9e0db9e66e 100644 --- a/lib/iris/util.py +++ b/lib/iris/util.py @@ -735,7 +735,6 @@ def _build_full_slice_given_keys(keys, ndim): for i, key in enumerate(keys): if key is Ellipsis: - # replace any subsequent Ellipsis objects in keys with # slice(None, None) as per Numpy keys = keys[:i] + tuple( From de7919ac3c34eec35436f304188560d67f56fe3a Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Thu, 9 Feb 2023 17:48:32 +0000 Subject: [PATCH 312/319] Replace apparently retired UDUNITS documentation link. (#5153) --- docs/src/userguide/cube_maths.rst | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/src/userguide/cube_maths.rst b/docs/src/userguide/cube_maths.rst index 9c0898b62c..56a2041bd3 100644 --- a/docs/src/userguide/cube_maths.rst +++ b/docs/src/userguide/cube_maths.rst @@ -5,8 +5,8 @@ Cube Maths ========== -The section :doc:`navigating_a_cube` highlighted that -every cube has a data attribute; +The section :doc:`navigating_a_cube` highlighted that +every cube has a data attribute; this attribute can then be manipulated directly:: cube.data -= 273.15 @@ -37,7 +37,7 @@ Let's load some air temperature which runs from 1860 to 2100:: filename = iris.sample_data_path('E1_north_america.nc') air_temp = iris.load_cube(filename, 'air_temperature') -We can now get the first and last time slices using indexing +We can now get the first and last time slices using indexing (see :ref:`cube_indexing` for a reminder):: t_first = air_temp[0, :, :] @@ -50,8 +50,8 @@ We can now get the first and last time slices using indexing t_first = air_temp[0, :, :] t_last = air_temp[-1, :, :] -And finally we can subtract the two. -The result is a cube of the same size as the original two time slices, +And finally we can subtract the two. +The result is a cube of the same size as the original two time slices, but with the data representing their difference: >>> print(t_last - t_first) @@ -70,8 +70,8 @@ but with the data representing their difference: .. note:: - Notice that the coordinates "time" and "forecast_period" have been removed - from the resultant cube; + Notice that the coordinates "time" and "forecast_period" have been removed + from the resultant cube; this is because these coordinates differed between the two input cubes. @@ -174,15 +174,15 @@ broadcasting behaviour:: Combining Multiple Phenomena to Form a New One ---------------------------------------------- -Combining cubes of potential-temperature and pressure we can calculate +Combining cubes of potential-temperature and pressure we can calculate the associated temperature using the equation: .. math:: - + T = \theta (\frac{p}{p_0}) ^ {(287.05 / 1005)} -Where :math:`p` is pressure, :math:`\theta` is potential temperature, -:math:`p_0` is the potential temperature reference pressure +Where :math:`p` is pressure, :math:`\theta` is potential temperature, +:math:`p_0` is the potential temperature reference pressure and :math:`T` is temperature. First, let's load pressure and potential temperature cubes:: @@ -191,7 +191,7 @@ First, let's load pressure and potential temperature cubes:: phenomenon_names = ['air_potential_temperature', 'air_pressure'] pot_temperature, pressure = iris.load_cubes(filename, phenomenon_names) -In order to calculate :math:`\frac{p}{p_0}` we can define a coordinate which +In order to calculate :math:`\frac{p}{p_0}` we can define a coordinate which represents the standard reference pressure of 1000 hPa:: import iris.coords @@ -205,7 +205,7 @@ the :meth:`iris.coords.Coord.convert_units` method:: p0.convert_units(pressure.units) -Now we can combine all of this information to calculate the air temperature +Now we can combine all of this information to calculate the air temperature using the equation above:: temperature = pot_temperature * ( (pressure / p0) ** (287.05 / 1005) ) @@ -219,12 +219,12 @@ The result could now be plotted using the guidance provided in the .. only:: html - A very similar example to this can be found in + A very similar example to this can be found in :ref:`sphx_glr_generated_gallery_meteorology_plot_deriving_phenomena.py`. .. only:: latex - A very similar example to this can be found in the examples section, + A very similar example to this can be found in the examples section, with the title "Deriving Exner Pressure and Air Temperature". .. _cube_maths_combining_units: @@ -249,7 +249,7 @@ unit (if ``a`` had units ``'m2'`` then ``a ** 0.5`` would result in a cube with units ``'m'``). Iris inherits units from `cf_units `_ -which in turn inherits from `UDUNITS `_. +which in turn inherits from `UDUNITS `_. As well as the units UDUNITS provides, cf units also provides the units ``'no-unit'`` and ``'unknown'``. A unit of ``'no-unit'`` means that the associated data is not suitable for describing with a unit, cf units From 58cdd788dd570fcf8a20920495d6e5402b5f3a62 Mon Sep 17 00:00:00 2001 From: Ruth Comer <10599679+rcomer@users.noreply.github.com> Date: Wed, 15 Feb 2023 14:50:50 +0000 Subject: [PATCH 313/319] Expand scope of common contributor links (#5159) --- docs/src/common_links.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index c8a7fef74b..7ae4e7f590 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -44,7 +44,7 @@ .. comment - Core developers (@github names) in alphabetical order: + Core developers and prolific contributors (@github names) in alphabetical order: .. _@abooton: https://github.com/abooton .. _@alastair-gemmell: https://github.com/alastair-gemmell From 504c188fd6cbe62129cc83f6f4905380ce37a212 Mon Sep 17 00:00:00 2001 From: Barnaby Sherratt Date: Wed, 15 Feb 2023 15:06:24 +0000 Subject: [PATCH 314/319] Plugin support (#5144) * Set up `iris.plugins` namespace package * Provide convenience function to use a plugin * Basic documentation for plugins * Add a hint about plugins when load fails * Expose `use_plugin` in `__all__` * Add example usage * Split plugin documentation to its own page * Document how to create a plugin * Link to documentation * Correct "plain" code block -> "text" * Whatsnew --- docs/src/common_links.inc | 1 + docs/src/community/index.rst | 10 +++++ docs/src/community/plugins.rst | 68 ++++++++++++++++++++++++++++++++++ docs/src/whatsnew/latest.rst | 4 +- lib/iris/__init__.py | 21 +++++++++++ lib/iris/io/format_picker.py | 5 ++- lib/iris/plugins/README.md | 10 +++++ 7 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 docs/src/community/plugins.rst create mode 100644 lib/iris/plugins/README.md diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index 7ae4e7f590..17278460dd 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -51,6 +51,7 @@ .. _@ajdawson: https://github.com/ajdawson .. _@bjlittle: https://github.com/bjlittle .. _@bouweandela: https://github.com/bouweandela +.. _@bsherratt: https://github.com/bsherratt .. _@corinnebosley: https://github.com/corinnebosley .. _@cpelley: https://github.com/cpelley .. _@djkirkham: https://github.com/djkirkham diff --git a/docs/src/community/index.rst b/docs/src/community/index.rst index a9e2a4d040..114cb96fe9 100644 --- a/docs/src/community/index.rst +++ b/docs/src/community/index.rst @@ -46,3 +46,13 @@ smoother interoperability: :hidden: iris_xarray + +Plugins +------- + +Iris can be extended with **plugins**! See below for further information: + +.. toctree:: + :maxdepth: 2 + + plugins diff --git a/docs/src/community/plugins.rst b/docs/src/community/plugins.rst new file mode 100644 index 0000000000..0d79d64623 --- /dev/null +++ b/docs/src/community/plugins.rst @@ -0,0 +1,68 @@ +.. _namespace package: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ + +.. _community_plugins: + +Plugins +======= + +Iris supports **plugins** under the ``iris.plugins`` `namespace package`_. +This allows packages that extend Iris' functionality to be developed and +maintained independently, while still being installed into ``iris.plugins`` +instead of a separate package. For example, a plugin may provide loaders or +savers for additional file formats, or alternative visualisation methods. + + +Using plugins +------------- + +Once a plugin is installed, it can be used either via the +:func:`iris.use_plugin` function, or by importing it directly: + +.. code-block:: python + + import iris + + iris.use_plugin("my_plugin") + # OR + import iris.plugins.my_plugin + + +Creating plugins +---------------- + +The choice of a `namespace package`_ makes writing a plugin relatively +straightforward: it simply needs to appear as a folder within ``iris/plugins``, +then can be distributed in the same way as any other package. An example +repository layout: + +.. code-block:: text + + + lib + + iris + + plugins + + my_plugin + - __init__.py + - (more code...) + - README.md + - pyproject.toml + - setup.cfg + - (other project files...) + +In particular, note that there must **not** be any ``__init__.py`` files at +higher levels than the plugin itself. + +The package name - how it is referred to by PyPI/conda, specified by +``metadata.name`` in ``setup.cfg`` - is recommended to include both "iris" and +the plugin name. Continuing this example, its ``setup.cfg`` should include, at +minimum: + +.. code-block:: ini + + [metadata] + name = iris-my-plugin + + [options] + packages = find_namespace: + + [options.packages.find] + where = lib diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index 9d9fa8b342..a38e426e6a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -32,7 +32,9 @@ This document explains the changes made to Iris for this release ✨ Features =========== -#. N/A +#. `@bsherratt`_ added support for plugins - see the corresponding + :ref:`documentation page` for further information. + (:pull:`5144`) 🐛 Bugs Fixed diff --git a/lib/iris/__init__.py b/lib/iris/__init__.py index a81d25add3..38465472ee 100644 --- a/lib/iris/__init__.py +++ b/lib/iris/__init__.py @@ -91,6 +91,7 @@ def callback(cube, field, filename): import contextlib import glob +import importlib import itertools import os.path import pathlib @@ -129,6 +130,7 @@ def callback(cube, field, filename): "sample_data_path", "save", "site_configuration", + "use_plugin", ] @@ -470,3 +472,22 @@ def sample_data_path(*path_to_join): "appropriate for general file access.".format(target) ) return target + + +def use_plugin(plugin_name): + """ + Convenience function to import a plugin + + For example:: + + use_plugin("my_plugin") + + is equivalent to:: + + import iris.plugins.my_plugin + + This is useful for plugins that are not used directly, but instead do all + their setup on import. In this case, style checkers would not know the + significance of the import statement and warn that it is an unused import. + """ + importlib.import_module(f"iris.plugins.{plugin_name}") diff --git a/lib/iris/io/format_picker.py b/lib/iris/io/format_picker.py index edf448e95b..a8e333c566 100644 --- a/lib/iris/io/format_picker.py +++ b/lib/iris/io/format_picker.py @@ -134,8 +134,9 @@ def get_spec(self, basename, buffer_obj): value = value[:50] + "..." printable_values[key] = value msg = ( - "No format specification could be found for the given buffer." - " File element cache:\n {}".format(printable_values) + "No format specification could be found for the given buffer. " + "Perhaps a plugin is missing or has not been loaded. " + "File element cache:\n {}".format(printable_values) ) raise ValueError(msg) diff --git a/lib/iris/plugins/README.md b/lib/iris/plugins/README.md new file mode 100644 index 0000000000..e8dee1de2c --- /dev/null +++ b/lib/iris/plugins/README.md @@ -0,0 +1,10 @@ +# Iris plugins + +`iris.plugins` is a [namespace package] allowing arbitrary plugins to be +installed alongside Iris. + +See [the Iris documentation][plugins] for more information. + + +[namespace package]: https://packaging.python.org/en/latest/guides/packaging-namespace-packages/ +[plugins]: https://scitools-iris.readthedocs.io/en/latest/community/plugins.html From ca42c30b47072676c7627b379e2743f1e859dd92 Mon Sep 17 00:00:00 2001 From: "scitools-ci[bot]" <107775138+scitools-ci[bot]@users.noreply.github.com> Date: Mon, 20 Feb 2023 10:28:02 +0000 Subject: [PATCH 315/319] Updated environment lockfiles (#5163) Co-authored-by: Lockfile bot --- requirements/ci/nox.lock/py310-linux-64.lock | 137 +++++++++--------- requirements/ci/nox.lock/py38-linux-64.lock | 143 ++++++++++--------- requirements/ci/nox.lock/py39-linux-64.lock | 143 ++++++++++--------- 3 files changed, 212 insertions(+), 211 deletions(-) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 75ec1e5579..910a390493 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -8,7 +8,7 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hcc3a1bd_1.conda#737be0d34c22d24432049ab7a3214de4 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf @@ -33,18 +33,17 @@ https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda#c7a069243e1fbe9a556ed2ec030e6407 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f -https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-h9c3ff4c_0.tar.bz2#c77f5e4e418fa47d699d6afa54c5d444 +https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-hcb278e6_1.conda#0f683578378cddb223e7fd24f785ab2a https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda#5cc781fd91968b11a8a7fdbee0982676 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d -https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2#b4f717df2d377410b462328bf0e8fb7d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 @@ -55,12 +54,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.1-h27087fc_0.tar.bz2#0af513b75f78a701a152568a31303bdf +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0 +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.2-hcb278e6_0.conda#08efb1e1813f1a151b7a945b972a049b https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 -https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_1.conda#7adaac6ff98219bcb99b45e408b80f4e +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.8-h0b41bf4_0.conda#e043403cd18faf815bf7705ab6c1e092 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -73,7 +72,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.22-h11f4161_0.conda#504fa9e712b99494a9cf4630e3ca7d78 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 @@ -90,14 +89,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-h26416b9_0.tar.bz2#6c531bc30d49ae75b9c7c7f65bd62e3c +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.32-ha901b37_0.conda#6a39818710235826181e104aada40c75 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda#6b63daed8feeca47be78f323e793d555 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 @@ -106,20 +105,20 @@ https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openbl https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h82bc61c_0.conda#a01611c54334d783847879ee40109657 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 -https://conda.anaconda.org/conda-forge/linux-64/python-3.10.8-h4a9ceb5_0_cpython.conda#be2a6d78752c2ab85f360ce37d2c64e2 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.7-hadd5161_0.conda#70cbb0c2033665f2a7339bf0ec51a67f +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.0-hb75c966_0.conda#c648d19cd9c8625898d5d370414de7c7 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda#2e648a34072eb39d7c4fc2a9981c5f0c +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.5.0-h79f4944_0.conda#3f67368c9b0e77a693acad193310baf1 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.32-hd7da12d_0.conda#b05d7ea8b76f1172d5fe4f30e03277ea +https://conda.anaconda.org/conda-forge/linux-64/nss-3.88-he45b914_0.conda#d7a81dfb99ad8fbb88872fb7ec646e6c +https://conda.anaconda.org/conda-forge/linux-64/python-3.10.9-he550d4f_0_cpython.conda#3cb3e91b3fe66baa68a12c85f39b9b40 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py310hff52083_1003.tar.bz2#8324f8fff866055d4b32eb25e091fe31 https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b @@ -129,7 +128,7 @@ https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.con https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda#b325bfc4cff7d7f8a868f1f7ecc4ed16 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d @@ -138,30 +137,30 @@ https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py310hff52083_3. https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d +https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.1.0-pyhd8ed1ab_0.conda#44f6828b8f7cc3433d68d1d1c0e9add2 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 -https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1.tar.bz2#ad5647e517ba68e2868ef2e6e6ff7723 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.7-default_h3e3d535_1.conda#a3a0f7a6f0885f5e1e0ec691566afb77 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#bc302fa1cf8eda15c60f669b7524a320 -https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-hb675445_2.conda#509f08b3789d9e7e9a72871491ae08e2 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.0-hdc1c0ab_0.conda#c44acb3847ff118c068b662aff858afd +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.2-hb675445_0.conda#4654b17eccaba55b8581d6b9c77f53cc https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py310h5764c6d_2.tar.bz2#2d7028ea2a77f909931e1a173d952261 +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py310h1fa729e_0.conda#a1f0db6709778b77b5903541eeac4032 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py310h37cc914_0.tar.bz2#98d598d9178d7f3091212c61c0be693c https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py310h08bbf29_0.conda#0d1f2e988c8810be90ffe441a303090a +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.2-py310h8deb116_0.conda#b7085457309e206174b8e234d90a7605 https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea -https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db +https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py310h5764c6d_0.tar.bz2#c3c55664e9becc48e6a652e2b641961f @@ -170,15 +169,15 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py310h1fa729e_0.conda#8d155ac95b1dfe585bcb6bec6a91c73b -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py310h5764c6d_5.tar.bz2#9e68d2ff6d98737c855b65f48dd3c597 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a +https://conda.anaconda.org/conda-forge/noarch/setuptools-67.3.2-pyhd8ed1ab_0.conda#543af74c4042aee5702a033e03a216d0 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.4-pyhd8ed1ab_0.conda#5a31a7d564f551d0e6dff52fd8cb5b16 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.1-pyhd8ed1ab_0.conda#6c8c4d6eb2325e59290ac6dbbeacd5f0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 @@ -192,77 +191,77 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b5b885341697137879a4f039a9e5a1 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba -https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_3.conda#800596144bb613cd7ac58b80900ce835 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py310hbf28c38_0.tar.bz2#c5b1699e390d30b680dd93a2b251062b -https://conda.anaconda.org/conda-forge/linux-64/curl-7.87.0-hdc1c0ab_0.conda#b14123ca479b9473d7f7395b0fd25c97 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py310hdf3cbec_0.conda#7bf9d8c765b6b04882c719509652c6d6 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.0-hdc1c0ab_0.conda#5d9ac94ee84305ada32c3d287d0ec602 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.7-default_had23c3d_1.conda#36c65ed73b7c92589bd9562ef8a6023d https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py310hde88566_1008.tar.bz2#f9dd8a7a2fcc23eb2cd95cd817c949e7 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py310h4927cde_0.conda#66366aceea767f174f4d0408f3a62812 -https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py310h023d228_1.conda#bbea829b541aa15df5c65bd40b8c1981 +https://conda.anaconda.org/conda-forge/noarch/pip-23.0.1-pyhd8ed1ab_0.conda#8025ca83b8ba5430b640b83917c2a6f7 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h8ffa02c_1.conda#ed901e1f5c504b144b31f015c6702634 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.1-h8ffa02c_2.conda#c264aea0e16bba26afa0a0940e954492 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-ha8d29e2_1.conda#dbfc2a8d63a43a11acf4c704e1ef9d0c https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1 -https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py310hde88566_3.tar.bz2#0b686f306a76fba9a61e7019f854321f -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py310hde88566_2.tar.bz2#61e2f2f7befaf45f47d1da449a9a0aca -https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.0-py310h8b84c32_0.conda#823009371d9b961c83cdb9aa80e1e6e7 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py310hd8f1fbe_0.conda#765b39936044b542a69ec2d863f5b891 +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.4.1-py310h0a54255_0.conda#b9e952fe3f7528ab603d2776175ba8d2 +https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.1-py310h8b84c32_0.conda#965113c401c7dc9b7a4cd5f9af57e185 +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.7-py310heca2aa9_0.conda#142c074701cf90c88667b461678aee81 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py310h5764c6d_1005.tar.bz2#87669c3468dff637bbd0363bc0f895cf https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py310hde88566_2.tar.bz2#7433944046deda7775c5b1f7e0b6fe18 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py310h34c0648_0.conda#af4b0c22dc4006ce3c095e840cb2efd7 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.1-pyhd8ed1ab_0.conda#f12878f9839c72f3d51af02fb10da43d -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.3-h25f0c4b_1.conda#0c8a8f15aa319c91d9010072278feddd +https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.1-py310h34c0648_0.conda#763b301155631438b09e6f2072d3ffaa +https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.2.0-pyhd8ed1ab_0.conda#156fb994a4e07091c4fad2c148589eb2 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.22.0-h25f0c4b_0.conda#d764367398de61c0d5531dd912e6cc96 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py310h8d5ebf3_0.tar.bz2#da51ddb20c0f99d672eb756c3abf27e7 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py310h769672d_0.conda#bc363997d22f3b058fb17f1e89d4c96f -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54 -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py310hfc24d34_0.conda#c126f81b5cea6b2d4a64d0744249a26f -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310hd8f1fbe_2.tar.bz2#0d815f1b2258d3d4c17cc80fd01e0f36 -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py310he60537e_0.conda#83a21bbd1c6fbeb339ba914fb5e5c02d +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py310h9b08913_0.conda#467244b0dbb7da40927ac6ee0e9491de +https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.0.0-pyhd8ed1ab_0.conda#c34694044915d7f291ef257029f2e2af +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py310h15e2413_1.conda#5be35366687def87437d210fd673100c +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310heca2aa9_3.conda#3b1946b676534472ce65181dda0b9554 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.2.0-pyhd8ed1ab_0.conda#70ab87b96126f35d1e68de2ad9fb6423 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py310hbf28c38_3.tar.bz2#703ff1ac7d1b27fb5944b8052b5d1edb -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.3-h4243ec0_1.conda#905563d166c13ba299e39d6c9fcebd1c -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.12-pyhd8ed1ab_0.conda#a34dcea79b2bed9520682a07f80d1c0f +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0_0.conda#81c20b15d2281a1ea48eac5b4eee8cfa +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h0a86a1f_103.conda#7f69695b684f2595d9ba1ce26d693b7d https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.1-py310hff52083_0.conda#d26ee3f6561669ec1f118d6d3404e42a -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_102.conda#cbae8c932a9d2ee620db7ce7ae0abaf5 +https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.19.0-pyhd8ed1ab_0.conda#afaa9bf6992f67a82d75fad47a93ec84 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_104.conda#ed3526a8b7f37a7ee04ab0de2a0ac314 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.21.0-py310hff52083_0.conda#41b6a707f04268b028c497d346a97693 -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hf6cd601_5.conda#9c23a5205b67f2a67b19c84bf1fd7f5e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py310h515c5ea_101.conda#8a00edb7362ef5ff0db5dd75099daac7 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.0.5-h2e5815a_0.conda#96bf06b24d74a5bf826485e9032c9312 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310h29803b5_2.tar.bz2#1e2c49215b17e6cf06edf100c9869ebe -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py310hff52083_0.tar.bz2#aa78d12708912cd34135e6694a046ba0 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-3.0.4-py310hff52083_0.conda#099815f9de141008e85f4ede8c55991c +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.8-h5d23da1_6.conda#59c73debd9405771690ddbbad6c57b69 +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py310h515c5ea_102.conda#bf8276009073388b7159736877eccd79 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.1.0-h2e5815a_0.conda#e7ecda996c443142a0e9c379f3b28e48 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py310hab646b1_3.conda#d049da3204bf5ecb54a852b622f2d7d2 +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.7.0-py310hff52083_0.conda#215e2a4504900bef6d68f520c12ef800 https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py310h8deb116_0.conda#ef72eeddf5316330730b11907c6c07d8 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py310h8deb116_2.conda#a12933d43fc0e55c2e5e00f56196108c https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index 77cd6b8962..e87b21a994 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -8,7 +8,7 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hcc3a1bd_1.conda#737be0d34c22d24432049ab7a3214de4 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf @@ -32,18 +32,17 @@ https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda#c7a069243e1fbe9a556ed2ec030e6407 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f -https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-h9c3ff4c_0.tar.bz2#c77f5e4e418fa47d699d6afa54c5d444 +https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-hcb278e6_1.conda#0f683578378cddb223e7fd24f785ab2a https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda#5cc781fd91968b11a8a7fdbee0982676 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d -https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2#b4f717df2d377410b462328bf0e8fb7d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 @@ -54,12 +53,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.1-h27087fc_0.tar.bz2#0af513b75f78a701a152568a31303bdf +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0 +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.2-hcb278e6_0.conda#08efb1e1813f1a151b7a945b972a049b https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 -https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_1.conda#7adaac6ff98219bcb99b45e408b80f4e +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.8-h0b41bf4_0.conda#e043403cd18faf815bf7705ab6c1e092 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -72,7 +71,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.22-h11f4161_0.conda#504fa9e712b99494a9cf4630e3ca7d78 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 @@ -89,14 +88,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-h26416b9_0.tar.bz2#6c531bc30d49ae75b9c7c7f65bd62e3c +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.32-ha901b37_0.conda#6a39818710235826181e104aada40c75 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda#6b63daed8feeca47be78f323e793d555 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 @@ -105,20 +104,20 @@ https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openbl https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h82bc61c_0.conda#a01611c54334d783847879ee40109657 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 -https://conda.anaconda.org/conda-forge/linux-64/python-3.8.15-h4a9ceb5_0_cpython.conda#dc29a8a79d0f2c80004cc06d3190104f +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.7-hadd5161_0.conda#70cbb0c2033665f2a7339bf0ec51a67f +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.0-hb75c966_0.conda#c648d19cd9c8625898d5d370414de7c7 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda#2e648a34072eb39d7c4fc2a9981c5f0c +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.5.0-h79f4944_0.conda#3f67368c9b0e77a693acad193310baf1 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.32-hd7da12d_0.conda#b05d7ea8b76f1172d5fe4f30e03277ea +https://conda.anaconda.org/conda-forge/linux-64/nss-3.88-he45b914_0.conda#d7a81dfb99ad8fbb88872fb7ec646e6c +https://conda.anaconda.org/conda-forge/linux-64/python-3.8.16-he550d4f_1_cpython.conda#9de84cccfbc5f8350a3667bb6ef6fc30 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py38h578d9bd_1003.tar.bz2#db8b471d9a764f561a129f94ea215c0a https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b @@ -128,39 +127,39 @@ https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.con https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda#b325bfc4cff7d7f8a868f1f7ecc4ed16 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d +https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.1.0-pyhd8ed1ab_0.conda#44f6828b8f7cc3433d68d1d1c0e9add2 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 -https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py38h43d8883_1.tar.bz2#41ca56d5cac7bfc7eb4fcdbee878eb84 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.7-default_h3e3d535_1.conda#a3a0f7a6f0885f5e1e0ec691566afb77 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#bc302fa1cf8eda15c60f669b7524a320 -https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-hb675445_2.conda#509f08b3789d9e7e9a72871491ae08e2 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.0-hdc1c0ab_0.conda#c44acb3847ff118c068b662aff858afd +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.2-hb675445_0.conda#4654b17eccaba55b8581d6b9c77f53cc https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py38h0a891b7_2.tar.bz2#c342a370480791db83d5dd20f2d8899f +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py38h1de0b5d_0.conda#6d97b5d6f06933ab653f1862ddf6e33e https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py38h97ac3a3_0.tar.bz2#0c469687a517052c0d581fc6e1a4189d https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py38hab0fcb9_0.conda#2c0b3c72dad0288d9582ccbceb250cb4 +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.2-py38h10c12cc_0.conda#05592c85b9f6931dc2df1e80c0d56294 https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea -https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db +https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py38h0a891b7_0.tar.bz2#fe2ef279417faa1af0adf178de2032f7 @@ -169,15 +168,15 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py38h1de0b5d_0.conda#7db73572d4f7e10a759bad609a228ad0 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py38h0a891b7_5.tar.bz2#0856c59f9ddb710c640dc0428d66b1b7 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a +https://conda.anaconda.org/conda-forge/noarch/setuptools-67.3.2-pyhd8ed1ab_0.conda#543af74c4042aee5702a033e03a216d0 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.4-pyhd8ed1ab_0.conda#5a31a7d564f551d0e6dff52fd8cb5b16 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.1-pyhd8ed1ab_0.conda#6c8c4d6eb2325e59290ac6dbbeacd5f0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 @@ -191,77 +190,79 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b5b885341697137879a4f039a9e5a1 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba -https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py38h4a40e3a_3.conda#3ac112151c6b6cfe457e976de41af0c5 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py38h26c90d9_1.tar.bz2#dcc025a7bb54374979c500c2e161fac9 -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py38h43d8883_0.tar.bz2#1107ee053d55172b26c4fc905dd0238e -https://conda.anaconda.org/conda-forge/linux-64/curl-7.87.0-hdc1c0ab_0.conda#b14123ca479b9473d7f7395b0fd25c97 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py38hfbd4bf9_0.conda#638537863b298151635c05c762a997ab +https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.0-hdc1c0ab_0.conda#5d9ac94ee84305ada32c3d287d0ec602 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py38h0a891b7_1.tar.bz2#62c89ddefed9c5835e228a32b357a28d https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62 +https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.7-default_had23c3d_1.conda#36c65ed73b7c92589bd9562ef8a6023d https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py38h26c90d9_1008.tar.bz2#6bc8cd29312f4fc77156b78124e165cd https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py38hb32c036_0.conda#a288a6e69efc2f20c30ebfa590e11bed -https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py38hde6dc18_1.conda#3de5619d3f556f966189e5251a266125 +https://conda.anaconda.org/conda-forge/noarch/pip-23.0.1-pyhd8ed1ab_0.conda#8025ca83b8ba5430b640b83917c2a6f7 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h8ffa02c_1.conda#ed901e1f5c504b144b31f015c6702634 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.1-h8ffa02c_2.conda#c264aea0e16bba26afa0a0940e954492 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-ha8d29e2_1.conda#dbfc2a8d63a43a11acf4c704e1ef9d0c https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1 -https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py38h26c90d9_3.tar.bz2#6e7902b0e96f42fa1b73daa5f65dd669 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py38h26c90d9_2.tar.bz2#d30399a3c636c75cfd3460c92effa960 -https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.0-py38hd07e089_0.conda#e8243980979661d5e941fcfd954ddb13 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py38hfa26641_0.conda#7be81814bae276dc7b4c707cf1e8186b +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.4.1-py38h7e4f40d_0.conda#17f682c947f9cabd348e7276f00c6d85 +https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.1-py38hd07e089_0.conda#84c9262ab4057ed9f80888fcfc4bf60a +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.7-py38h8dc9893_0.conda#ea242937718f3dacf253355e1d634535 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py38h0a891b7_1005.tar.bz2#e99e08812dfff30fdd17b3f8838e2759 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py38h26c90d9_2.tar.bz2#0ea017e84efe45badce6c32f274dbf8e -https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py38h3d167d9_0.conda#0ef859aa9dafce54bdf3d56715daed35 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.1-pyhd8ed1ab_0.conda#f12878f9839c72f3d51af02fb10da43d -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.3-h25f0c4b_1.conda#0c8a8f15aa319c91d9010072278feddd +https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.1-py38h3d167d9_0.conda#375c00c98c36b0e79aaaf2149e51f27d +https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.2.0-pyhd8ed1ab_0.conda#156fb994a4e07091c4fad2c148589eb2 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.22.0-h25f0c4b_0.conda#d764367398de61c0d5531dd912e6cc96 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38 +https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.10.2-pyhd8ed1ab_0.conda#ebf8b116aac3fe86270bfe5f61fe2b80 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py38hb021067_0.tar.bz2#72422499195d8aded0dfd461c6e3e86f -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py38h8f669ce_0.conda#dbc17622f9d159be987bd21959d5494e -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54 -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py38hf928c62_0.conda#bb6d6874f1dcafdd2dce7dfd54d2b96c -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38hfa26641_2.tar.bz2#ad6437509a14f1e8e5b8a354f93f340c -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py38hdc8b05c_0.conda#5073966d63a54434d2a2fc41d325b072 +https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.0.0-pyhd8ed1ab_0.conda#c34694044915d7f291ef257029f2e2af +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py38h58d5fe2_1.conda#5286eaec7e93586e4ae05e7d658cd3e2 +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py38h8dc9893_3.conda#7bb0328b4a0f857aeb432426b9a5f908 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.2.0-pyhd8ed1ab_0.conda#70ab87b96126f35d1e68de2ad9fb6423 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py38h43d8883_3.tar.bz2#82b3797d08a43a101b645becbb938e65 -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.3-h4243ec0_1.conda#905563d166c13ba299e39d6c9fcebd1c -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.12-pyhd8ed1ab_0.conda#a34dcea79b2bed9520682a07f80d1c0f -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0_0.conda#81c20b15d2281a1ea48eac5b4eee8cfa +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py38hd6c3c57_0.conda#dd63f6486ba95c036b6bfe0b5c53d875 +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h6b4b75c_103.conda#ea3d2204fc3a7db7d831daa437a58717 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.1-py38h578d9bd_0.conda#4ddc66bb73c2d53d194875c2ee8f0f06 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_102.conda#cbae8c932a9d2ee620db7ce7ae0abaf5 +https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.19.0-pyhd8ed1ab_0.conda#afaa9bf6992f67a82d75fad47a93ec84 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_104.conda#ed3526a8b7f37a7ee04ab0de2a0ac314 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.21.0-py38h578d9bd_0.conda#4fb68a31e2377d41d5a33e47a5436d75 -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hf6cd601_5.conda#9c23a5205b67f2a67b19c84bf1fd7f5e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py38h4407c66_101.conda#1deba9421c01396e0b1381a02a29ed93 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.0.5-h2e5815a_0.conda#96bf06b24d74a5bf826485e9032c9312 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38h7492b6b_2.tar.bz2#cfa725eff634872f90dcd5ebf8e8dc1a -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py38h578d9bd_0.tar.bz2#e1a19f0d4686a701d4a4acce2b625acb +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-3.0.4-py38h578d9bd_0.conda#ae802cf221c9549ce9924e1a3718342d +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.8-h5d23da1_6.conda#59c73debd9405771690ddbbad6c57b69 +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py38h4407c66_102.conda#9a5c841acef11d7e4f0bf98cbc6308b3 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.1.0-h2e5815a_0.conda#e7ecda996c443142a0e9c379f3b28e48 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py38ha0d8c90_3.conda#e965dc172d67920d058ac2b3a0e27565 +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.7.0-py38h578d9bd_0.conda#7fb6ab52eb5de5023445561d86dbd602 https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py38h10c12cc_0.conda#466ea530d622838f6cdec4f771ddc249 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py38h10c12cc_2.conda#d6a3defdc4ab4acd69c04c8ef73d9b57 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index cc896e6989..f2eb79bc0a 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -8,7 +8,7 @@ https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2#34893075a5c9e55cdafac56607368fc6 https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2#4d59c254e01d9cde7957100457e2d5fb https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-hab24e00_0.tar.bz2#19410c3df09dfb12d1206132a1d357c5 -https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.39-hcc3a1bd_1.conda#737be0d34c22d24432049ab7a3214de4 +https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.40-h41732ed_0.conda#7aca3059a1729aa76c597603f10b0dd3 https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-12.2.0-h337968e_19.tar.bz2#164b4b1acaedc47ee7e658ae6b308ca3 https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-12.2.0-h46fd767_19.tar.bz2#1030b1f38c129f2634eae026f704fe60 https://conda.anaconda.org/conda-forge/linux-64/mpi-1.0-mpich.tar.bz2#c1fcff3417b5a22bbc4cf6e8c23648cf @@ -33,18 +33,17 @@ https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.1-h36c2ea0_2.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.13-h58526e2_1001.tar.bz2#8c54672728e8ec6aa6db90cf2806d220 https://conda.anaconda.org/conda-forge/linux-64/gstreamer-orc-0.4.33-h166bdaf_0.tar.bz2#879c93426c9d0b84a9de4513fbce5f4f https://conda.anaconda.org/conda-forge/linux-64/icu-70.1-h27087fc_0.tar.bz2#87473a15119779e021c314249d4b4aed -https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h166bdaf_2.tar.bz2#ee8b844357a0946870901c7c6f418268 +https://conda.anaconda.org/conda-forge/linux-64/jpeg-9e-h0b41bf4_3.conda#c7a069243e1fbe9a556ed2ec030e6407 https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.1-h166bdaf_0.tar.bz2#30186d27e2c9fa62b45fb1476b7200e3 https://conda.anaconda.org/conda-forge/linux-64/lame-3.100-h166bdaf_1003.tar.bz2#a8832b479f93521a9e7b5b743803be51 https://conda.anaconda.org/conda-forge/linux-64/lerc-4.0.0-h27087fc_0.tar.bz2#76bbff344f0134279f225174e9064c8f -https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-h9c3ff4c_0.tar.bz2#c77f5e4e418fa47d699d6afa54c5d444 +https://conda.anaconda.org/conda-forge/linux-64/libaec-1.0.6-hcb278e6_1.conda#0f683578378cddb223e7fd24f785ab2a https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.0.9-h166bdaf_8.tar.bz2#9194c9bf9428035a05352d031462eae4 https://conda.anaconda.org/conda-forge/linux-64/libdb-6.2.32-h9c3ff4c_0.tar.bz2#3f3258d8f841fbac63b36b75bdac1afd -https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.14-h166bdaf_0.tar.bz2#fc84a0446e4e4fb882e78d786cfb9734 +https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.17-h0b41bf4_0.conda#5cc781fd91968b11a8a7fdbee0982676 https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-h516909a_1.tar.bz2#6f8720dff19e17ce5d48cfe7f3d2f0a3 https://conda.anaconda.org/conda-forge/linux-64/libffi-3.4.2-h7f98852_5.tar.bz2#d645c6d2ac96843a2bfaccd2d62b3ac3 https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.17-h166bdaf_0.tar.bz2#b62b52da46c39ee2bc3c162ac7f1804d -https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-2.1.4-h166bdaf_0.tar.bz2#b4f717df2d377410b462328bf0e8fb7d https://conda.anaconda.org/conda-forge/linux-64/libmo_unpack-3.1.2-hf484d3e_1001.tar.bz2#95f32a6a5a666d33886ca5627239f03d https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.0-h7f98852_0.tar.bz2#39b1328babf85c7c3a61636d9cd50206 https://conda.anaconda.org/conda-forge/linux-64/libogg-1.3.4-h7f98852_1.tar.bz2#6e8cc2173440d77708196c5b93771680 @@ -55,12 +54,12 @@ https://conda.anaconda.org/conda-forge/linux-64/libudev1-252-h166bdaf_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.32.1-h7f98852_1000.tar.bz2#772d69f030955d9646d3d0eaf21d859d https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.2.4-h166bdaf_0.tar.bz2#ac2ccf7323d21f2994e4d1f5da664f37 https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.2.13-h166bdaf_4.tar.bz2#f3f9de449d32ca9b9c66a22863c96f41 -https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.3-h9c3ff4c_1.tar.bz2#fbe97e8fa6f275d7c76a09e795adc3e6 -https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.1-h27087fc_0.tar.bz2#0af513b75f78a701a152568a31303bdf +https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda#318b08df404f9c9be5712aaa5a6f0bb0 +https://conda.anaconda.org/conda-forge/linux-64/mpg123-1.31.2-hcb278e6_0.conda#08efb1e1813f1a151b7a945b972a049b https://conda.anaconda.org/conda-forge/linux-64/mpich-4.0.3-h846660c_100.tar.bz2#50d66bb751cfa71ee2a48b2d3eb90ac1 https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.3-h27087fc_1.tar.bz2#4acfc691e64342b9dae57cf2adc63238 https://conda.anaconda.org/conda-forge/linux-64/nspr-4.35-h27087fc_0.conda#da0ec11a6454ae19bff5b02ed881a2b1 -https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.7-h0b41bf4_1.conda#7adaac6ff98219bcb99b45e408b80f4e +https://conda.anaconda.org/conda-forge/linux-64/openssl-3.0.8-h0b41bf4_0.conda#e043403cd18faf815bf7705ab6c1e092 https://conda.anaconda.org/conda-forge/linux-64/pixman-0.40.0-h36c2ea0_0.tar.bz2#660e72c82f2e75a6b3fe6a6e75c79f19 https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-h36c2ea0_1001.tar.bz2#22dad4df6e8630e8dff2428f6f6a7036 https://conda.anaconda.org/conda-forge/linux-64/xorg-kbproto-1.0.7-h7f98852_1002.tar.bz2#4b230e8381279d76131116660f5a241a @@ -73,7 +72,7 @@ https://conda.anaconda.org/conda-forge/linux-64/xorg-xproto-7.0.31-h7f98852_1007 https://conda.anaconda.org/conda-forge/linux-64/xxhash-0.8.1-h0b41bf4_0.conda#e9c3bcf0e0c719431abec8ca447eee27 https://conda.anaconda.org/conda-forge/linux-64/xz-5.2.6-h166bdaf_0.tar.bz2#2161070d867d1b1204ea749c8eec4ef0 https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-h7f98852_2.tar.bz2#4cb3ad778ec2d5a7acbdf254eb1c42ae -https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.21-h583fa2b_2.conda#7b36a10b58964d4444fcba44244710c5 +https://conda.anaconda.org/conda-forge/linux-64/jack-1.9.22-h11f4161_0.conda#504fa9e712b99494a9cf4630e3ca7d78 https://conda.anaconda.org/conda-forge/linux-64/libblas-3.9.0-16_linux64_openblas.tar.bz2#d9b7a8639171f6c6fa0a983edabcfe2b https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.0.9-h166bdaf_8.tar.bz2#4ae4d7795d33e02bd20f6b23d91caf82 https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.0.9-h166bdaf_8.tar.bz2#04bac51ba35ea023dc48af73c1c88c25 @@ -90,14 +89,14 @@ https://conda.anaconda.org/conda-forge/linux-64/libvorbis-1.3.7-h9c3ff4c_0.tar.b https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.13-h7f98852_1004.tar.bz2#b3653fdc58d03face9724f602218a904 https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.10.3-h7463322_0.tar.bz2#3b933ea47ef8f330c4c068af25fcd6a8 https://conda.anaconda.org/conda-forge/linux-64/libzip-1.9.2-hc929e4a_1.tar.bz2#5b122b50e738c4be5c3f2899f010d7cf -https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.31-h26416b9_0.tar.bz2#6c531bc30d49ae75b9c7c7f65bd62e3c +https://conda.anaconda.org/conda-forge/linux-64/mysql-common-8.0.32-ha901b37_0.conda#6a39818710235826181e104aada40c75 https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.40-hc3806b6_0.tar.bz2#69e2c796349cd9b273890bee0febfe1b https://conda.anaconda.org/conda-forge/linux-64/readline-8.1.2-h0f457ee_0.tar.bz2#db2ebbe2943aae81ed051a6a9af8e0fa https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.12-h27826a3_0.tar.bz2#5b8c42eb62e9fc961af70bdd6a26e168 https://conda.anaconda.org/conda-forge/linux-64/udunits2-2.2.28-hc3e0081_0.tar.bz2#d4c341e0379c31e9e781d4f204726867 https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.3-hd9c2040_1000.tar.bz2#9e856f78d5c80d5a78f61e72d1d473a3 https://conda.anaconda.org/conda-forge/linux-64/zlib-1.2.13-h166bdaf_4.tar.bz2#4b11e365c0275b808be78b30f904e295 -https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h6239696_4.tar.bz2#adcf0be7897e73e312bd24353b613f74 +https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.2-h3eb15da_6.conda#6b63daed8feeca47be78f323e793d555 https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.0.9-h166bdaf_8.tar.bz2#e5613f2bc717e9945840ff474419b8e4 https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-hca18f0e_1.conda#e1232042de76d24539a436d37597eb06 https://conda.anaconda.org/conda-forge/linux-64/hdf4-4.2.15-h9772cbc_5.tar.bz2#ee08782aff2ff9b3291c967fa6bc7336 @@ -106,20 +105,20 @@ https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.9.0-16_linux64_openbl https://conda.anaconda.org/conda-forge/linux-64/libgcrypt-1.10.1-h166bdaf_0.tar.bz2#f967fc95089cd247ceed56eda31de3a9 https://conda.anaconda.org/conda-forge/linux-64/libglib-2.74.1-h606061b_1.tar.bz2#ed5349aa96776e00b34eccecf4a948fe https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.9.0-16_linux64_openblas.tar.bz2#955d993f41f9354bf753d29864ea20ad -https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.6-h63197d8_0.conda#201168ef66095bbd565e124ee2c56a20 -https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.1.0-hcb278e6_1.conda#d7a07b1f5974bce4735112aaef0c1467 -https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h82bc61c_0.conda#a01611c54334d783847879ee40109657 -https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.0.3-he3ba5ed_0.tar.bz2#f9dbabc7e01c459ed7a1d1d64b206e9b -https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.31-hbc51c84_0.tar.bz2#da9633eee814d4e910fe42643a356315 -https://conda.anaconda.org/conda-forge/linux-64/nss-3.82-he02c5a1_0.conda#f8d7f11d19e4cb2207eab159fd4c0152 -https://conda.anaconda.org/conda-forge/linux-64/python-3.9.15-hba424b6_0_cpython.conda#7b9485fce17fac2dd4aca6117a9936c2 +https://conda.anaconda.org/conda-forge/linux-64/libllvm15-15.0.7-hadd5161_0.conda#70cbb0c2033665f2a7339bf0ec51a67f +https://conda.anaconda.org/conda-forge/linux-64/libsndfile-1.2.0-hb75c966_0.conda#c648d19cd9c8625898d5d370414de7c7 +https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.5.0-h6adf6a1_2.conda#2e648a34072eb39d7c4fc2a9981c5f0c +https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.5.0-h79f4944_0.conda#3f67368c9b0e77a693acad193310baf1 +https://conda.anaconda.org/conda-forge/linux-64/mysql-libs-8.0.32-hd7da12d_0.conda#b05d7ea8b76f1172d5fe4f30e03277ea +https://conda.anaconda.org/conda-forge/linux-64/nss-3.88-he45b914_0.conda#d7a81dfb99ad8fbb88872fb7ec646e6c +https://conda.anaconda.org/conda-forge/linux-64/python-3.9.16-h2782a2a_0_cpython.conda#95c9b7c96a7fd7342e0c9d0a917b8f78 https://conda.anaconda.org/conda-forge/linux-64/sqlite-3.40.0-h4ff8645_0.tar.bz2#bb11803129cbbb53ed56f9506ff74145 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.0-h166bdaf_0.tar.bz2#384e7fcb3cd162ba3e4aed4b687df566 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.0-h166bdaf_0.tar.bz2#637054603bb7594302e3bf83f0a99879 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.9-h166bdaf_0.tar.bz2#732e22f1741bccea861f5668cf7342a7 https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.1-h166bdaf_0.tar.bz2#0a8e20a8aef954390b9481a527421a8c https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.7.2-h7f98852_0.tar.bz2#12a61e640b8894504326aadafccbb790 -https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.12-py_0.tar.bz2#2489a97287f90176ecdc3ca982b4b0a0 +https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.13-pyhd8ed1ab_0.conda#06006184e203b61d3525f90de394471e https://conda.anaconda.org/conda-forge/linux-64/antlr-python-runtime-4.7.2-py39hf3d152e_1003.tar.bz2#5e8330e806e50bd6137ebd125f4bc1bb https://conda.anaconda.org/conda-forge/noarch/appdirs-1.4.4-pyh9f0ad1d_0.tar.bz2#5f095bc6454094e96f146491fd03633b https://conda.anaconda.org/conda-forge/linux-64/atk-1.0-2.38.0-hd4edc92_1.tar.bz2#6c72ec3e660a51736913ef6ea68c454b @@ -129,39 +128,39 @@ https://conda.anaconda.org/conda-forge/noarch/certifi-2022.12.7-pyhd8ed1ab_0.con https://conda.anaconda.org/conda-forge/noarch/cfgv-3.3.1-pyhd8ed1ab_0.tar.bz2#ebb5f5f7dc4f1a3780ef7ea7738db08c https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-2.1.1-pyhd8ed1ab_0.tar.bz2#c1d5b294fbf9a795dec349a6f4d8be8e https://conda.anaconda.org/conda-forge/noarch/click-8.1.3-unix_pyhd8ed1ab_2.tar.bz2#20e4087407c7cb04a40817114b333dbf -https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.0-pyhd8ed1ab_0.tar.bz2#a6cf47b09786423200d7982d1faa19eb +https://conda.anaconda.org/conda-forge/noarch/cloudpickle-2.2.1-pyhd8ed1ab_0.conda#b325bfc4cff7d7f8a868f1f7ecc4ed16 https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz2#3faab06a954c2a04039983f2c4a50d99 https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 -https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.1-hc2a2eb6_0.tar.bz2#78415f0180a8d9c5bcc47889e00d5fb1 -https://conda.anaconda.org/conda-forge/noarch/fsspec-2022.11.0-pyhd8ed1ab_0.tar.bz2#eb919f2119a6db5d0192f9e9c3711572 +https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.14.2-h14ed4e7_0.conda#0f69b688f52ff6da70bccb7ff7001d1d +https://conda.anaconda.org/conda-forge/noarch/fsspec-2023.1.0-pyhd8ed1ab_0.conda#44f6828b8f7cc3433d68d1d1c0e9add2 https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.42.10-h05c8ddd_0.conda#1a109126a43003d65b39c1cad656bc9b https://conda.anaconda.org/conda-forge/linux-64/glib-tools-2.74.1-h6239696_1.tar.bz2#5f442e6bc9d89ba236eb25a25c5c2815 https://conda.anaconda.org/conda-forge/linux-64/gts-0.7.6-h64030ff_2.tar.bz2#112eb9b5b93f0c02e59aea4fd1967363 https://conda.anaconda.org/conda-forge/noarch/idna-3.4-pyhd8ed1ab_0.tar.bz2#34272b248891bddccc64479f9a7fffed https://conda.anaconda.org/conda-forge/noarch/imagesize-1.4.1-pyhd8ed1ab_0.tar.bz2#7de5386c8fea29e76b303f37dde4c352 -https://conda.anaconda.org/conda-forge/noarch/iniconfig-1.1.1-pyh9f0ad1d_0.tar.bz2#39161f81cc5e5ca45b8226fbb06c6905 +https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda#f800d2da156d08e289b14e87e43c1ae5 https://conda.anaconda.org/conda-forge/noarch/iris-sample-data-2.4.0-pyhd8ed1ab_0.tar.bz2#18ee9c07cf945a33f92caf1ee3d23ad9 https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py39hf939315_1.tar.bz2#41679a052a8ce841c74df1ebc802e411 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97 -https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.6-default_h3a83d3e_0.conda#535dd0ca1dcb165b6a8ffa10d01945fe +https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.7-default_h3e3d535_1.conda#a3a0f7a6f0885f5e1e0ec691566afb77 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.87.0-hdc1c0ab_0.conda#bc302fa1cf8eda15c60f669b7524a320 -https://conda.anaconda.org/conda-forge/linux-64/libpq-15.1-hb675445_2.conda#509f08b3789d9e7e9a72871491ae08e2 +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.0-hdc1c0ab_0.conda#c44acb3847ff118c068b662aff858afd +https://conda.anaconda.org/conda-forge/linux-64/libpq-15.2-hb675445_0.conda#4654b17eccaba55b8581d6b9c77f53cc https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4 https://conda.anaconda.org/conda-forge/noarch/locket-1.0.0-pyhd8ed1ab_0.tar.bz2#91e27ef3d05cc772ce627e51cff111c4 -https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.1-py39hb9d737c_2.tar.bz2#c678e07e7862b3157fb9f6d908233ffa +https://conda.anaconda.org/conda-forge/linux-64/markupsafe-2.1.2-py39h72bdee0_0.conda#35514f5320206df9f4661c138c02e1c1 https://conda.anaconda.org/conda-forge/linux-64/mpi4py-3.1.4-py39h32b9844_0.tar.bz2#b035b507f55bb6a967d86d4b7e059437 https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyh9f0ad1d_0.tar.bz2#2ba8498c1018c1e9c61eb99b973dfe19 -https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.1-py39h223a676_0.conda#ce779b1c4e7ff4cc2f2690d173974daf +https://conda.anaconda.org/conda-forge/linux-64/numpy-1.24.2-py39h7360e5f_0.conda#757070dc7cc33003254888808cd34f1e https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.0-hfec8fc6_2.conda#5ce6a42505c6e9e6151c54c3ec8d68ea -https://conda.anaconda.org/conda-forge/noarch/packaging-22.0-pyhd8ed1ab_0.conda#0e8e1bd93998978fc3125522266d12db +https://conda.anaconda.org/conda-forge/noarch/packaging-23.0-pyhd8ed1ab_0.conda#1ff2e3ca41f0ce16afec7190db28288b https://conda.anaconda.org/conda-forge/noarch/pluggy-1.0.0-pyhd8ed1ab_5.tar.bz2#7d301a0d25f424d96175f810935f0da9 https://conda.anaconda.org/conda-forge/noarch/ply-3.11-py_1.tar.bz2#7205635cd71531943440fbfe3b6b5727 https://conda.anaconda.org/conda-forge/linux-64/psutil-5.9.4-py39hb9d737c_0.tar.bz2#12184951da572828fb986b06ffb63eed @@ -170,15 +169,15 @@ https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.0.9-pyhd8ed1ab_0.tar.b https://conda.anaconda.org/conda-forge/noarch/pyshp-2.3.1-pyhd8ed1ab_0.tar.bz2#92a889dc236a5197612bc85bee6d7174 https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2#2a7de29fb590ca14b5243c4c812c8025 https://conda.anaconda.org/conda-forge/linux-64/python-xxhash-3.2.0-py39h72bdee0_0.conda#18927f971926b7271600368de71de557 -https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7-pyhd8ed1ab_0.conda#c8d7e34ca76d6ecc03b84bedfd99d689 +https://conda.anaconda.org/conda-forge/noarch/pytz-2022.7.1-pyhd8ed1ab_0.conda#f59d49a7b464901cf714b9e7984d01a2 https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0-py39hb9d737c_5.tar.bz2#ef9db3c38ae7275f6b14491cfe61a248 -https://conda.anaconda.org/conda-forge/noarch/setuptools-65.6.3-pyhd8ed1ab_0.conda#9600fc9524d3f821e6a6d58c52f5bf5a +https://conda.anaconda.org/conda-forge/noarch/setuptools-67.3.2-pyhd8ed1ab_0.conda#543af74c4042aee5702a033e03a216d0 https://conda.anaconda.org/conda-forge/noarch/six-1.16.0-pyh6c4a22f_0.tar.bz2#e5f25f8dbc060e9a8d912e432202afc2 https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-2.2.0-pyhd8ed1ab_0.tar.bz2#4d22a9315e78c6827f806065957d566e https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.3.2.post1-pyhd8ed1ab_0.tar.bz2#146f4541d643d48fc8a75cacf69f03ae -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.2-py_0.tar.bz2#20b2eaeaeea4ef9a9a0d99770620fd09 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-1.0.4-pyhd8ed1ab_0.conda#5a31a7d564f551d0e6dff52fd8cb5b16 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-1.0.2-py_0.tar.bz2#68e01cac9d38d0e717cd5c87bc3d2cc9 -https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.0-pyhd8ed1ab_0.tar.bz2#77dad82eb9c8c1525ff7953e0756d708 +https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.0.1-pyhd8ed1ab_0.conda#6c8c4d6eb2325e59290ac6dbbeacd5f0 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-py_0.tar.bz2#67cd9d9c0382d37479b4d306c369a2d4 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-1.0.3-py_0.tar.bz2#d01180388e6d1838c3e1ad029590aa7a https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-1.1.5-pyhd8ed1ab_2.tar.bz2#9ff55a0901cf952f05c654394de76bf7 @@ -192,77 +191,79 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.11.0-pyhd8ed1ab_0.conda#09b5b885341697137879a4f039a9e5a1 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba -https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.1-pyha770c72_0.tar.bz2#eeec8814bd97b2681f708bb127478d7d +https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py39he91dace_3.conda#20080319ef73fbad74dcd6d62f2a3ffe https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py39h2ae25f5_1.tar.bz2#c943fb9a2818ecc5be1e0ecc8b7738f1 -https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.6-py39hf939315_0.tar.bz2#fb3f77fe25042c20c51974fcfe72f797 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.87.0-hdc1c0ab_0.conda#b14123ca479b9473d7f7395b0fd25c97 +https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py39h4b4f3f3_0.conda#c5387f3fb1f5b8b71e1c865fc55f4951 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.0-hdc1c0ab_0.conda#5d9ac94ee84305ada32c3d287d0ec602 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py39hb9d737c_1.tar.bz2#3f2d104f2fefdd5e8a205dd3aacbf1d7 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0 https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-6.0.0-pyha770c72_0.conda#691644becbcdca9f73243450b1c63e62 +https://conda.anaconda.org/conda-forge/noarch/importlib_resources-5.10.2-pyhd8ed1ab_0.conda#de76905f801c22fc43e624058574eab3 https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.2-pyhd8ed1ab_1.tar.bz2#c8490ed5c70966d232fdd389d0dbed37 -https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.6-default_h2e3cab8_0.conda#1b2cee49acc5b03c73ad0f68bfe04bb8 +https://conda.anaconda.org/conda-forge/linux-64/libclang-15.0.7-default_had23c3d_1.conda#36c65ed73b7c92589bd9562ef8a6023d https://conda.anaconda.org/conda-forge/linux-64/libgd-2.3.3-h5aea950_4.conda#82ef57611ace65b59db35a9687264572 https://conda.anaconda.org/conda-forge/linux-64/mo_pack-0.2.0-py39h2ae25f5_1008.tar.bz2#d90acb3804f16c63eb6726652e4e25b3 https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.7.0-pyhd8ed1ab_0.tar.bz2#fbe1182f650c04513046d6894046cd6c https://conda.anaconda.org/conda-forge/noarch/partd-1.3.0-pyhd8ed1ab_0.tar.bz2#af8c82d121e63082926062d61d9abb54 -https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py39ha08a7e4_0.conda#d62ba9d1a981544c809813afaf0be5c0 -https://conda.anaconda.org/conda-forge/noarch/pip-22.3.1-pyhd8ed1ab_0.tar.bz2#da66f2851b9836d3a7c5190082a45f7d +https://conda.anaconda.org/conda-forge/linux-64/pillow-9.4.0-py39h2320bf1_1.conda#d2f79132b9c8e416058a4cd84ef27b3d +https://conda.anaconda.org/conda-forge/noarch/pip-23.0.1-pyhd8ed1ab_0.conda#8025ca83b8ba5430b640b83917c2a6f7 https://conda.anaconda.org/conda-forge/noarch/pockets-0.9.1-py_0.tar.bz2#1b52f0c42e8077e5a33e00fe72269364 -https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.0-h8ffa02c_1.conda#ed901e1f5c504b144b31f015c6702634 -https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-h126f2b6_0.tar.bz2#e4b74b33e13dd146e7d8b5078fc9ad30 +https://conda.anaconda.org/conda-forge/linux-64/proj-9.1.1-h8ffa02c_2.conda#c264aea0e16bba26afa0a0940e954492 +https://conda.anaconda.org/conda-forge/linux-64/pulseaudio-16.1-ha8d29e2_1.conda#dbfc2a8d63a43a11acf4c704e1ef9d0c https://conda.anaconda.org/conda-forge/noarch/pygments-2.14.0-pyhd8ed1ab_0.conda#c78cd16b11cd6a295484bd6c8f24bea1 -https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.0-pyhd8ed1ab_2.tar.bz2#ac82c7aebc282e6ac0450fca012ca78c +https://conda.anaconda.org/conda-forge/noarch/pytest-7.2.1-pyhd8ed1ab_0.conda#f0be05afc9c9ab45e273c088e00c258b https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.8.2-pyhd8ed1ab_0.tar.bz2#dd999d1cc9f79e67dbb855c8924c7984 https://conda.anaconda.org/conda-forge/linux-64/python-stratify-0.2.post0-py39h2ae25f5_3.tar.bz2#bcc7de3bb458a198b598ac1f75bf37e3 -https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.3.0-py39h2ae25f5_2.tar.bz2#234ad9828eca1caf0f2fdcb4a24ad816 -https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.0-py39hc9151fd_0.conda#735b335f9250d84a5da94ffb76692db8 -https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.5-py39h5a03fae_0.conda#c3eb463691a8b93f1c381a9e56ecad9a +https://conda.anaconda.org/conda-forge/linux-64/pywavelets-1.4.1-py39h389d5f1_0.conda#9eeb2b2549f836ca196c6cbd22344122 +https://conda.anaconda.org/conda-forge/linux-64/shapely-2.0.1-py39hc9151fd_0.conda#d26cc40830285883abaa766a7f7798bf +https://conda.anaconda.org/conda-forge/linux-64/sip-6.7.7-py39h227be39_0.conda#7d9a35091552af3655151f164ddd64a3 https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.4.0-hd8ed1ab_0.tar.bz2#be969210b61b897775a0de63cd9e9026 https://conda.anaconda.org/conda-forge/linux-64/brotlipy-0.7.0-py39hb9d737c_1005.tar.bz2#a639fdd9428d8b25f8326a3838d54045 https://conda.anaconda.org/conda-forge/linux-64/cf-units-3.1.1-py39h2ae25f5_2.tar.bz2#b3b4aab96d1c4ed394d6f4b9146699d4 -https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.0-py39h079d5ae_0.conda#70ac60b214a8df9b9ce63e05af7d0976 -https://conda.anaconda.org/conda-forge/noarch/dask-core-2022.12.1-pyhd8ed1ab_0.conda#f12878f9839c72f3d51af02fb10da43d -https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.21.3-h25f0c4b_1.conda#0c8a8f15aa319c91d9010072278feddd +https://conda.anaconda.org/conda-forge/linux-64/cryptography-39.0.1-py39h079d5ae_0.conda#3245013812dfbff6a22e57533ac6f69d +https://conda.anaconda.org/conda-forge/noarch/dask-core-2023.2.0-pyhd8ed1ab_0.conda#156fb994a4e07091c4fad2c148589eb2 +https://conda.anaconda.org/conda-forge/linux-64/gstreamer-1.22.0-h25f0c4b_0.conda#d764367398de61c0d5531dd912e6cc96 https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-6.0.0-h8e241bc_0.conda#448fe40d2fed88ccf4d9ded37cbb2b38 +https://conda.anaconda.org/conda-forge/noarch/importlib-resources-5.10.2-pyhd8ed1ab_0.conda#ebf8b116aac3fe86270bfe5f61fe2b80 https://conda.anaconda.org/conda-forge/linux-64/libnetcdf-4.8.1-mpi_mpich_hcd871d9_6.tar.bz2#6cdc429ed22edb566ac4308f3da6916d -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.6.2-py39hf9fd14e_0.tar.bz2#78ce32061e0be12deb8e0f11ffb76906 -https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.2-py39h4661b88_0.conda#e17e50269c268d79478956a262a9fe13 -https://conda.anaconda.org/conda-forge/noarch/platformdirs-2.6.2-pyhd8ed1ab_0.conda#0b4cc3f8181b0d8446eb5387d7848a54 -https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py39h12578bd_0.conda#7edbb99bec2bfab82f86abd71d24b505 -https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h5a03fae_2.tar.bz2#306f1a018668f06a0bd89350a3f62c07 -https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.1.0-pyhd8ed1ab_0.conda#e82f8fb903d7c4a59c77954759c341f9 +https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py39h2ad29b5_0.conda#3ea96adbbc2a66fa45178102a9cfbecc +https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.0.0-pyhd8ed1ab_0.conda#c34694044915d7f291ef257029f2e2af +https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py39hf14cbfd_1.conda#67766c515601b3ee1514072d6fd060bb +https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py39h227be39_3.conda#9e381db00691e26bcf670c3586397be1 +https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.2.0-pyhd8ed1ab_0.conda#70ab87b96126f35d1e68de2ad9fb6423 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.0.1-py39hf939315_3.tar.bz2#0f11bcdf9669a5ae0f39efd8c830209a -https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.21.3-h4243ec0_1.conda#905563d166c13ba299e39d6c9fcebd1c -https://conda.anaconda.org/conda-forge/noarch/identify-2.5.12-pyhd8ed1ab_0.conda#a34dcea79b2bed9520682a07f80d1c0f -https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 -https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_hd09bd1e_1.tar.bz2#0b69750bb937cab0db14f6bcef6fd787 +https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0_0.conda#81c20b15d2281a1ea48eac5b4eee8cfa +https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py39he190548_0.conda#62d6ddd9e534f4d325d12470cc4961ab +https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h94a714e_103.conda#ee29e7176b5854fa09ec17b101945401 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 -https://conda.anaconda.org/conda-forge/linux-64/virtualenv-20.17.1-py39hf3d152e_0.conda#dd1be6ccb267f13bdc5c44cfb76c4080 -https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_102.conda#cbae8c932a9d2ee620db7ce7ae0abaf5 +https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.19.0-pyhd8ed1ab_0.conda#afaa9bf6992f67a82d75fad47a93ec84 +https://conda.anaconda.org/conda-forge/linux-64/esmf-8.4.0-mpi_mpich_hc592774_104.conda#ed3526a8b7f37a7ee04ab0de2a0ac314 https://conda.anaconda.org/conda-forge/linux-64/gtk2-2.24.33-h90689f9_2.tar.bz2#957a0255ab58aaf394a91725d73ab422 https://conda.anaconda.org/conda-forge/linux-64/librsvg-2.54.4-h7abd40a_0.tar.bz2#921e53675ed5ea352f022b79abab076a -https://conda.anaconda.org/conda-forge/linux-64/pre-commit-2.21.0-py39hf3d152e_0.conda#9dafac76ddd44f3b9a4a45ad601c5917 -https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.6-hf6cd601_5.conda#9c23a5205b67f2a67b19c84bf1fd7f5e -https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.13-pyhd8ed1ab_0.conda#3078ef2359efd6ecadbc7e085c5e0592 -https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py39h3088dd8_101.conda#e90e56e1bd5f2a484e435fd2745cd809 -https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.0.5-h2e5815a_0.conda#96bf06b24d74a5bf826485e9032c9312 -https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h18e9c17_2.tar.bz2#384809c51fb2adc04773f6fa097cd051 -https://conda.anaconda.org/conda-forge/noarch/requests-2.28.1-pyhd8ed1ab_1.tar.bz2#089382ee0e2dc2eae33a04cc3c2bddb0 -https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.6.2-py39hf3d152e_0.tar.bz2#03225b4745d1dee7bb19d81e41c773a0 +https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 +https://conda.anaconda.org/conda-forge/linux-64/pre-commit-3.0.4-py39hf3d152e_0.conda#8a98273ee904735747a8f6706b187f3e +https://conda.anaconda.org/conda-forge/linux-64/qt-main-5.15.8-h5d23da1_6.conda#59c73debd9405771690ddbbad6c57b69 +https://conda.anaconda.org/conda-forge/noarch/urllib3-1.26.14-pyhd8ed1ab_0.conda#01f33ad2e0aaf6b5ba4add50dad5ad29 +https://conda.anaconda.org/conda-forge/linux-64/esmpy-8.4.0-mpi_mpich_py39h3088dd8_102.conda#a022e48c8b12bc56083bcce841978519 +https://conda.anaconda.org/conda-forge/linux-64/graphviz-7.1.0-h2e5815a_0.conda#e7ecda996c443142a0e9c379f3b28e48 +https://conda.anaconda.org/conda-forge/linux-64/pyqt-5.15.7-py39h5c7b992_3.conda#19e30314fe824605750da905febb8ee6 +https://conda.anaconda.org/conda-forge/noarch/requests-2.28.2-pyhd8ed1ab_0.conda#11d178fc55199482ee48d6812ea83983 +https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.7.0-py39hf3d152e_0.conda#0967228e228ebeded6a36a6f4d5509ed https://conda.anaconda.org/conda-forge/noarch/pooch-1.6.0-pyhd8ed1ab_0.tar.bz2#6429e1d1091c51f626b5dcfdd38bf429 https://conda.anaconda.org/conda-forge/noarch/sphinx-4.5.0-pyh6c4a22f_0.tar.bz2#46b38d88c4270ff9ba78a89c83c66345 https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.12.0-pyhd8ed1ab_0.tar.bz2#fe4a16a5ffc6ff74d4a479a44f6bf6a2 -https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py39h7360e5f_0.conda#d6d4f8195ec2c846deebe71306f60298 +https://conda.anaconda.org/conda-forge/linux-64/scipy-1.10.0-py39h7360e5f_2.conda#fbee2ab3fe7729f2ff5c5699d58e40b9 https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.0-pyhd8ed1ab_0.tar.bz2#4c969cdd5191306c269490f7ff236d9c https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0.tar.bz2#729254314a5d178eefca50acbc2687b8 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a From 11da71b7cc34016d5d32b275b7284f644a87ae49 Mon Sep 17 00:00:00 2001 From: Martin Yeo <40734014+trexfeathers@users.noreply.github.com> Date: Mon, 20 Feb 2023 17:58:51 +0000 Subject: [PATCH 316/319] NetCDF thread safety take two (#5095) * Unpin netcdf4. * Temporarily enable GHA on this branch. * Temporarily enable GHA on this branch. * Temporarily enable GHA on this branch. * Experiment to disable wheel CI on forks. * Disable segfaulting routines. * More temporary changes to get CI passing. * More temporary changes to get CI passing. * Finessed segfault skipping. * Bring in changed from SciTools/iris#5061. * Re-instate test_load_laea_grid. * Adaptations to get the tests passing. * Use typing.Mapping instead. * Get doctests passing. * CF only resolve non-url filenames. * Confirm thread safety fixes. * Remove dummy assignment. * Restored plot_nemo What's New entry. * _add_aux_factories temporarily release global lock. * Remove per-file locking. * Remove remaining test workarounds. * Remove remaining comments. * Correct use of CFReader context manager. * Refactor for easier future maintenance. * Rename netcdf _thread_safe, add header. * Full use of ThreadSafeAggregators. * Full use of ThreadSafeAggregators. * Remove remaining imports of NetCDF4. * Test to ensure netCDF4 is via _thread_safe module. * More refined netcdf._thread_safe classes. * _thread_safe docstrings. * Restore original NetCDF code where possible. * Revert changes to 2.3.rst. * Update lockfiles. * Additions to _thread_safe.py * Remove temporary CI shims. * New locking stategy for NetCDFDataProxy. * NetCDFDataProxy simpler use of netCDF4 lock. * Update lock files. * Go back to using a Threading Lock. * Remove superfluous pass commands in test_cf.py. * Rename _thread_safe to _thread_safe_nc. * Rename thread safe classes to be 'Wrappers'. * Better contained getattr and setattr pattern. * Explicitly name netCDF4 module in _thread_safe_nc docstring. * Better docstring for _ThreadSafeWrapper. * Better comment about THREAD_SAFE_FLAG. * list() wrapping within _GLOBAL_NETCDF4_LOCK, to account for generators. * More accurate thread_safe docstrings in netcdf.saver. * Split netcdf integration tests into multiple modules. * Tests for non-thread-safe NetCDF behaviour. * Docstring accuracy. * Correct use of dask config set (context manager). * Update dependencies. * Review - don't need the first-class import of iris.tests. * Better name for the loading test. * Better selection of data to load. * What's New entry. * Improve tests. * Update lock files. * Increase chunking on test_save. --------- Co-authored-by: Patrick Peglar --- docs/src/common_links.inc | 1 + docs/src/userguide/glossary.rst | 8 +- docs/src/whatsnew/2.1.rst | 8 +- docs/src/whatsnew/latest.rst | 3 +- lib/iris/experimental/ugrid/load.py | 3 +- lib/iris/fileformats/cf.py | 26 +- .../fileformats/netcdf/_thread_safe_nc.py | 342 +++++++ lib/iris/fileformats/netcdf/loader.py | 157 ++- lib/iris/fileformats/netcdf/saver.py | 22 +- lib/iris/tests/integration/netcdf/__init__.py | 6 + .../integration/netcdf/test_attributes.py | 119 +++ .../integration/netcdf/test_aux_factories.py | 160 +++ .../integration/netcdf/test_coord_systems.py | 281 +++++ .../tests/integration/netcdf/test_general.py | 360 +++++++ .../netcdf/test_self_referencing.py | 126 +++ .../integration/netcdf/test_thread_safety.py | 109 ++ lib/iris/tests/integration/test_netcdf.py | 958 ------------------ .../multiple_different_saves_on_variables.cdl | 0 .../multiple_same_saves_as_global.cdl | 0 .../single_saves_as_global.cdl | 0 .../TestAtmosphereSigma/save.cdl | 0 .../TestHybridPressure/save.cdl | 0 .../hybrid_height_and_pressure.cdl | 0 .../hybrid_height_cubes.cml | 0 .../multi_packed_multi_dtype.cdl | 0 .../multi_packed_single_dtype.cdl | 0 .../TestPackedData/single_packed_manual.cdl | 0 .../TestPackedData/single_packed_signed.cdl | 0 .../TestPackedData/single_packed_unsigned.cdl | 0 lib/iris/tests/stock/netcdf.py | 4 +- lib/iris/tests/test_cf.py | 19 +- lib/iris/tests/test_coding_standards.py | 26 + lib/iris/tests/test_load.py | 8 +- lib/iris/tests/test_netcdf.py | 18 +- lib/iris/tests/test_pp_cf.py | 5 +- .../ugrid/cf/test_CFUGridReader.py | 5 +- .../unit/fileformats/cf/test_CFReader.py | 29 +- .../nc_load_rules/actions/__init__.py | 74 +- .../unit/fileformats/netcdf/test_Saver.py | 41 +- .../fileformats/netcdf/test_Saver__ugrid.py | 6 +- .../unit/fileformats/netcdf/test_save.py | 21 +- requirements/ci/nox.lock/py310-linux-64.lock | 7 +- requirements/ci/nox.lock/py38-linux-64.lock | 9 +- requirements/ci/nox.lock/py39-linux-64.lock | 9 +- requirements/ci/py310.yml | 2 +- requirements/ci/py38.yml | 2 +- requirements/ci/py39.yml | 2 +- setup.cfg | 2 +- 48 files changed, 1799 insertions(+), 1179 deletions(-) create mode 100644 lib/iris/fileformats/netcdf/_thread_safe_nc.py create mode 100644 lib/iris/tests/integration/netcdf/__init__.py create mode 100644 lib/iris/tests/integration/netcdf/test_attributes.py create mode 100644 lib/iris/tests/integration/netcdf/test_aux_factories.py create mode 100644 lib/iris/tests/integration/netcdf/test_coord_systems.py create mode 100644 lib/iris/tests/integration/netcdf/test_general.py create mode 100644 lib/iris/tests/integration/netcdf/test_self_referencing.py create mode 100644 lib/iris/tests/integration/netcdf/test_thread_safety.py delete mode 100644 lib/iris/tests/integration/test_netcdf.py rename lib/iris/tests/results/integration/netcdf/{ => attributes}/TestUmVersionAttribute/multiple_different_saves_on_variables.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => attributes}/TestUmVersionAttribute/multiple_same_saves_as_global.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => attributes}/TestUmVersionAttribute/single_saves_as_global.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => aux_factories}/TestAtmosphereSigma/save.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => aux_factories}/TestHybridPressure/save.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => aux_factories}/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => aux_factories}/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml (100%) rename lib/iris/tests/results/integration/netcdf/{ => general}/TestPackedData/multi_packed_multi_dtype.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => general}/TestPackedData/multi_packed_single_dtype.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => general}/TestPackedData/single_packed_manual.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => general}/TestPackedData/single_packed_signed.cdl (100%) rename lib/iris/tests/results/integration/netcdf/{ => general}/TestPackedData/single_packed_unsigned.cdl (100%) diff --git a/docs/src/common_links.inc b/docs/src/common_links.inc index 17278460dd..4d03a92715 100644 --- a/docs/src/common_links.inc +++ b/docs/src/common_links.inc @@ -41,6 +41,7 @@ .. _issues on GitHub: https://github.com/SciTools/iris/issues?q=is%3Aopen+is%3Aissue+sort%3Areactions-%2B1-desc .. _python-stratify: https://github.com/SciTools/python-stratify .. _iris-esmf-regrid: https://github.com/SciTools-incubator/iris-esmf-regrid +.. _netCDF4: https://github.com/Unidata/netcdf4-python .. comment diff --git a/docs/src/userguide/glossary.rst b/docs/src/userguide/glossary.rst index 818ef0c7ad..5c24f03372 100644 --- a/docs/src/userguide/glossary.rst +++ b/docs/src/userguide/glossary.rst @@ -1,3 +1,5 @@ +.. include:: ../common_links.inc + .. _glossary: Glossary @@ -125,7 +127,7 @@ Glossary of formats. | **Related:** :term:`CartoPy` **|** :term:`NumPy` - | **More information:** `Matplotlib `_ + | **More information:** `matplotlib`_ | Metadata @@ -143,9 +145,11 @@ Glossary When Iris loads this format, it also especially recognises and interprets data encoded according to the :term:`CF Conventions`. + __ `NetCDF4`_ + | **Related:** :term:`Fields File (FF) Format` **|** :term:`GRIB Format` **|** :term:`Post Processing (PP) Format` - | **More information:** `NetCDF-4 Python Git `_ + | **More information:** `NetCDF-4 Python Git`__ | NumPy diff --git a/docs/src/whatsnew/2.1.rst b/docs/src/whatsnew/2.1.rst index 18c562d3da..33f3a013b1 100644 --- a/docs/src/whatsnew/2.1.rst +++ b/docs/src/whatsnew/2.1.rst @@ -1,3 +1,5 @@ +.. include:: ../common_links.inc + v2.1 (06 Jun 2018) ****************** @@ -67,7 +69,7 @@ Incompatible Changes as an alternative. * This release of Iris contains a number of updated metadata translations. - See this + See this `changelist `_ for further information. @@ -84,7 +86,7 @@ Internal calendar. * Iris updated its time-handling functionality from the - `netcdf4-python `_ + `netcdf4-python`__ ``netcdftime`` implementation to the standalone module `cftime `_. cftime is entirely compatible with netcdftime, but some issues may @@ -92,6 +94,8 @@ Internal In this situation, simply replacing ``netcdftime.datetime`` with ``cftime.datetime`` should be sufficient. +__ `netCDF4`_ + * Iris now requires version 2 of Matplotlib, and ``>=1.14`` of NumPy. Full requirements can be seen in the `requirements `_ directory of the Iris' the source. diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index a38e426e6a..b23687af7a 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -40,7 +40,8 @@ This document explains the changes made to Iris for this release 🐛 Bugs Fixed ============= -#. N/A +#. `@trexfeathers`_ and `@pp-mo`_ made Iris' use of the `netCDF4`_ library + thread-safe. (:pull:`5095`) 💣 Incompatible Changes diff --git a/lib/iris/experimental/ugrid/load.py b/lib/iris/experimental/ugrid/load.py index a522d91313..cfa3935991 100644 --- a/lib/iris/experimental/ugrid/load.py +++ b/lib/iris/experimental/ugrid/load.py @@ -209,7 +209,8 @@ def load_meshes(uris, var_name=None): result = {} for source in valid_sources: - meshes_dict = _meshes_from_cf(CFUGridReader(source)) + with CFUGridReader(source) as cf_reader: + meshes_dict = _meshes_from_cf(cf_reader) meshes = list(meshes_dict.values()) if var_name is not None: meshes = list(filter(lambda m: m.var_name == var_name, meshes)) diff --git a/lib/iris/fileformats/cf.py b/lib/iris/fileformats/cf.py index a3a23dc323..a21e1d975f 100644 --- a/lib/iris/fileformats/cf.py +++ b/lib/iris/fileformats/cf.py @@ -20,10 +20,10 @@ import re import warnings -import netCDF4 import numpy as np import numpy.ma as ma +from iris.fileformats.netcdf import _thread_safe_nc import iris.util # @@ -1050,7 +1050,9 @@ def __init__(self, filename, warn=False, monotonic=False): #: Collection of CF-netCDF variables associated with this netCDF file self.cf_group = self.CFGroup() - self._dataset = netCDF4.Dataset(self._filename, mode="r") + self._dataset = _thread_safe_nc.DatasetWrapper( + self._filename, mode="r" + ) # Issue load optimisation warning. if warn and self._dataset.file_format in [ @@ -1068,6 +1070,19 @@ def __init__(self, filename, warn=False, monotonic=False): self._build_cf_groups() self._reset() + def __enter__(self): + # Enable use as a context manager + # N.B. this **guarantees* closure of the file, when the context is exited. + # Note: ideally, the class would not do so much work in the __init__ call, and + # would do all that here, after acquiring necessary permissions/locks. + # But for legacy reasons, we can't do that. So **effectively**, the context + # (in terms of access control) alreday started, when we created the object. + return self + + def __exit__(self, exc_type, exc_value, traceback): + # When used as a context-manager, **always** close the file on exit. + self._close() + @property def filename(self): """The file that the CFReader is reading.""" @@ -1294,10 +1309,15 @@ def _reset(self): for nc_var_name in self._dataset.variables.keys(): self.cf_group[nc_var_name].cf_attrs_reset() - def __del__(self): + def _close(self): # Explicitly close dataset to prevent file remaining open. if self._dataset is not None: self._dataset.close() + self._dataset = None + + def __del__(self): + # Be sure to close dataset when CFReader is destroyed / garbage-collected. + self._close() def _getncattr(dataset, attr, default=None): diff --git a/lib/iris/fileformats/netcdf/_thread_safe_nc.py b/lib/iris/fileformats/netcdf/_thread_safe_nc.py new file mode 100644 index 0000000000..decca1535f --- /dev/null +++ b/lib/iris/fileformats/netcdf/_thread_safe_nc.py @@ -0,0 +1,342 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Module to ensure all calls to the netCDF4 library are thread-safe. + +Intention is that no other Iris module should import the netCDF4 module. + +""" +from abc import ABC +from threading import Lock +import typing + +import netCDF4 +import numpy as np + +_GLOBAL_NETCDF4_LOCK = Lock() + +# Doesn't need thread protection, but this allows all netCDF4 refs to be +# replaced with thread_safe refs. +default_fillvals = netCDF4.default_fillvals + + +class _ThreadSafeWrapper(ABC): + """ + Contains a netCDF4 class instance, ensuring wrapping all API calls within _GLOBAL_NETCDF4_LOCK. + + Designed to 'gate keep' all the instance's API calls, but allowing the + same API as if working directly with the instance itself. + + Using a contained object instead of inheritance, as we cannot successfully + subclass or monkeypatch netCDF4 classes, because they are only wrappers for + the C-layer. + """ + + CONTAINED_CLASS = NotImplemented + + # Allows easy type checking, avoiding difficulties with isinstance and mocking. + THREAD_SAFE_FLAG = True + + @classmethod + def _from_existing(cls, instance): + """Pass an existing instance to __init__, where it is contained.""" + assert isinstance(instance, cls.CONTAINED_CLASS) + return cls(instance) + + def __init__(self, *args, **kwargs): + """Contain an existing instance, or generate a new one from arguments.""" + if isinstance(args[0], self.CONTAINED_CLASS): + instance = args[0] + else: + with _GLOBAL_NETCDF4_LOCK: + instance = self.CONTAINED_CLASS(*args, **kwargs) + + self._contained_instance = instance + + def __getattr__(self, item): + if item == "_contained_instance": + # Special behaviour when accessing the _contained_instance itself. + return object.__getattribute__(self, item) + else: + with _GLOBAL_NETCDF4_LOCK: + return getattr(self._contained_instance, item) + + def __setattr__(self, key, value): + if key == "_contained_instance": + # Special behaviour when accessing the _contained_instance itself. + object.__setattr__(self, key, value) + else: + with _GLOBAL_NETCDF4_LOCK: + return setattr(self._contained_instance, key, value) + + def __getitem__(self, item): + with _GLOBAL_NETCDF4_LOCK: + return self._contained_instance.__getitem__(item) + + def __setitem__(self, key, value): + with _GLOBAL_NETCDF4_LOCK: + return self._contained_instance.__setitem__(key, value) + + +class DimensionWrapper(_ThreadSafeWrapper): + """ + Accessor for a netCDF4.Dimension, always acquiring _GLOBAL_NETCDF4_LOCK. + + All API calls should be identical to those for netCDF4.Dimension. + """ + + CONTAINED_CLASS = netCDF4.Dimension + + +class VariableWrapper(_ThreadSafeWrapper): + """ + Accessor for a netCDF4.Variable, always acquiring _GLOBAL_NETCDF4_LOCK. + + All API calls should be identical to those for netCDF4.Variable. + """ + + CONTAINED_CLASS = netCDF4.Variable + + def setncattr(self, *args, **kwargs) -> None: + """ + Calls netCDF4.Variable.setncattr within _GLOBAL_NETCDF4_LOCK. + + Only defined explicitly in order to get some mocks to work. + """ + with _GLOBAL_NETCDF4_LOCK: + return self._contained_instance.setncattr(*args, **kwargs) + + @property + def dimensions(self) -> typing.List[str]: + """ + Calls netCDF4.Variable.dimensions within _GLOBAL_NETCDF4_LOCK. + + Only defined explicitly in order to get some mocks to work. + """ + with _GLOBAL_NETCDF4_LOCK: + # Return value is a list of strings so no need for + # DimensionWrapper, unlike self.get_dims(). + return self._contained_instance.dimensions + + # All Variable API that returns Dimension(s) is wrapped to instead return + # DimensionWrapper(s). + + def get_dims(self, *args, **kwargs) -> typing.Tuple[DimensionWrapper]: + """ + Calls netCDF4.Variable.get_dims() within _GLOBAL_NETCDF4_LOCK, returning DimensionWrappers. + + The original returned netCDF4.Dimensions are simply replaced with their + respective DimensionWrappers, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + dimensions_ = list( + self._contained_instance.get_dims(*args, **kwargs) + ) + return tuple([DimensionWrapper._from_existing(d) for d in dimensions_]) + + +class GroupWrapper(_ThreadSafeWrapper): + """ + Accessor for a netCDF4.Group, always acquiring _GLOBAL_NETCDF4_LOCK. + + All API calls should be identical to those for netCDF4.Group. + """ + + CONTAINED_CLASS = netCDF4.Group + + # All Group API that returns Dimension(s) is wrapped to instead return + # DimensionWrapper(s). + + @property + def dimensions(self) -> typing.Dict[str, DimensionWrapper]: + """ + Calls dimensions of netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning DimensionWrappers. + + The original returned netCDF4.Dimensions are simply replaced with their + respective DimensionWrappers, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + dimensions_ = self._contained_instance.dimensions + return { + k: DimensionWrapper._from_existing(v) + for k, v in dimensions_.items() + } + + def createDimension(self, *args, **kwargs) -> DimensionWrapper: + """ + Calls createDimension() from netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning DimensionWrapper. + + The original returned netCDF4.Dimension is simply replaced with its + respective DimensionWrapper, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + new_dimension = self._contained_instance.createDimension( + *args, **kwargs + ) + return DimensionWrapper._from_existing(new_dimension) + + # All Group API that returns Variable(s) is wrapped to instead return + # VariableWrapper(s). + + @property + def variables(self) -> typing.Dict[str, VariableWrapper]: + """ + Calls variables of netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning VariableWrappers. + + The original returned netCDF4.Variables are simply replaced with their + respective VariableWrappers, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + variables_ = self._contained_instance.variables + return { + k: VariableWrapper._from_existing(v) for k, v in variables_.items() + } + + def createVariable(self, *args, **kwargs) -> VariableWrapper: + """ + Calls createVariable() from netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning VariableWrapper. + + The original returned netCDF4.Variable is simply replaced with its + respective VariableWrapper, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + new_variable = self._contained_instance.createVariable( + *args, **kwargs + ) + return VariableWrapper._from_existing(new_variable) + + def get_variables_by_attributes( + self, *args, **kwargs + ) -> typing.List[VariableWrapper]: + """ + Calls get_variables_by_attributes() from netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning VariableWrappers. + + The original returned netCDF4.Variables are simply replaced with their + respective VariableWrappers, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + variables_ = list( + self._contained_instance.get_variables_by_attributes( + *args, **kwargs + ) + ) + return [VariableWrapper._from_existing(v) for v in variables_] + + # All Group API that returns Group(s) is wrapped to instead return + # GroupWrapper(s). + + @property + def groups(self): + """ + Calls groups of netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning GroupWrappers. + + The original returned netCDF4.Groups are simply replaced with their + respective GroupWrappers, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + groups_ = self._contained_instance.groups + return {k: GroupWrapper._from_existing(v) for k, v in groups_.items()} + + @property + def parent(self): + """ + Calls parent of netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning a GroupWrapper. + + The original returned netCDF4.Group is simply replaced with its + respective GroupWrapper, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + parent_ = self._contained_instance.parent + return GroupWrapper._from_existing(parent_) + + def createGroup(self, *args, **kwargs): + """ + Calls createGroup() from netCDF4.Group/Dataset within _GLOBAL_NETCDF4_LOCK, returning GroupWrapper. + + The original returned netCDF4.Group is simply replaced with its + respective GroupWrapper, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + new_group = self._contained_instance.createGroup(*args, **kwargs) + return GroupWrapper._from_existing(new_group) + + +class DatasetWrapper(GroupWrapper): + """ + Accessor for a netCDF4.Dataset, always acquiring _GLOBAL_NETCDF4_LOCK. + + All API calls should be identical to those for netCDF4.Dataset. + """ + + CONTAINED_CLASS = netCDF4.Dataset + + @classmethod + def fromcdl(cls, *args, **kwargs): + """ + Calls netCDF4.Dataset.fromcdl() within _GLOBAL_NETCDF4_LOCK, returning a DatasetWrapper. + + The original returned netCDF4.Dataset is simply replaced with its + respective DatasetWrapper, ensuring that downstream calls are + also performed within _GLOBAL_NETCDF4_LOCK. + """ + with _GLOBAL_NETCDF4_LOCK: + instance = cls.CONTAINED_CLASS.fromcdl(*args, **kwargs) + return cls._from_existing(instance) + + +class NetCDFDataProxy: + """A reference to the data payload of a single NetCDF file variable.""" + + __slots__ = ("shape", "dtype", "path", "variable_name", "fill_value") + + def __init__(self, shape, dtype, path, variable_name, fill_value): + self.shape = shape + self.dtype = dtype + self.path = path + self.variable_name = variable_name + self.fill_value = fill_value + + @property + def ndim(self): + return len(self.shape) + + def __getitem__(self, keys): + # Using a DatasetWrapper causes problems with invalid ID's and the + # netCDF4 library, presumably because __getitem__ gets called so many + # times by Dask. Use _GLOBAL_NETCDF4_LOCK directly instead. + with _GLOBAL_NETCDF4_LOCK: + dataset = netCDF4.Dataset(self.path) + try: + variable = dataset.variables[self.variable_name] + # Get the NetCDF variable data and slice. + var = variable[keys] + finally: + dataset.close() + return np.asanyarray(var) + + def __repr__(self): + fmt = ( + "<{self.__class__.__name__} shape={self.shape}" + " dtype={self.dtype!r} path={self.path!r}" + " variable_name={self.variable_name!r}>" + ) + return fmt.format(self=self) + + def __getstate__(self): + return {attr: getattr(self, attr) for attr in self.__slots__} + + def __setstate__(self, state): + for key, value in state.items(): + setattr(self, key, value) diff --git a/lib/iris/fileformats/netcdf/loader.py b/lib/iris/fileformats/netcdf/loader.py index 95f394c70d..8fcab61d17 100644 --- a/lib/iris/fileformats/netcdf/loader.py +++ b/lib/iris/fileformats/netcdf/loader.py @@ -15,7 +15,6 @@ """ import warnings -import netCDF4 import numpy as np from iris._lazy_data import as_lazy_data @@ -34,6 +33,7 @@ import iris.coords import iris.exceptions import iris.fileformats.cf +from iris.fileformats.netcdf import _thread_safe_nc from iris.fileformats.netcdf.saver import _CF_ATTRS import iris.io import iris.util @@ -44,6 +44,10 @@ # Get the logger : shared logger for all in 'iris.fileformats.netcdf'. from . import logger +# An expected part of the public loader API, but includes thread safety +# concerns so is housed in _thread_safe_nc. +NetCDFDataProxy = _thread_safe_nc.NetCDFDataProxy + def _actions_engine(): # Return an 'actions engine', which provides a pyke-rules-like interface to @@ -55,48 +59,6 @@ def _actions_engine(): return engine -class NetCDFDataProxy: - """A reference to the data payload of a single NetCDF file variable.""" - - __slots__ = ("shape", "dtype", "path", "variable_name", "fill_value") - - def __init__(self, shape, dtype, path, variable_name, fill_value): - self.shape = shape - self.dtype = dtype - self.path = path - self.variable_name = variable_name - self.fill_value = fill_value - - @property - def ndim(self): - return len(self.shape) - - def __getitem__(self, keys): - dataset = netCDF4.Dataset(self.path) - try: - variable = dataset.variables[self.variable_name] - # Get the NetCDF variable data and slice. - var = variable[keys] - finally: - dataset.close() - return np.asanyarray(var) - - def __repr__(self): - fmt = ( - "<{self.__class__.__name__} shape={self.shape}" - " dtype={self.dtype!r} path={self.path!r}" - " variable_name={self.variable_name!r}>" - ) - return fmt.format(self=self) - - def __getstate__(self): - return {attr: getattr(self, attr) for attr in self.__slots__} - - def __setstate__(self, state): - for key, value in state.items(): - setattr(self, key, value) - - def _assert_case_specific_facts(engine, cf, cf_group): # Initialise a data store for built cube elements. # This is used to patch element attributes *not* setup by the actions @@ -219,7 +181,7 @@ def _get_cf_var_data(cf_var, filename): fill_value = getattr( cf_var.cf_data, "_FillValue", - netCDF4.default_fillvals[cf_var.dtype.str[1:]], + _thread_safe_nc.default_fillvals[cf_var.dtype.str[1:]], ) proxy = NetCDFDataProxy( cf_var.shape, dtype, filename, cf_var.cf_name, fill_value @@ -536,59 +498,62 @@ def load_cubes(filenames, callback=None, constraints=None): # Ingest the netCDF file. meshes = {} if PARSE_UGRID_ON_LOAD: - cf = CFUGridReader(filename) - meshes = _meshes_from_cf(cf) + cf_reader_class = CFUGridReader else: - cf = iris.fileformats.cf.CFReader(filename) + cf_reader_class = iris.fileformats.cf.CFReader - # Process each CF data variable. - data_variables = list(cf.cf_group.data_variables.values()) + list( - cf.cf_group.promoted.values() - ) - for cf_var in data_variables: - if var_callback and not var_callback(cf_var): - # Deliver only selected results. - continue - - # cf_var-specific mesh handling, if a mesh is present. - # Build the mesh_coords *before* loading the cube - avoids - # mesh-related attributes being picked up by - # _add_unused_attributes(). - mesh_name = None - mesh = None - mesh_coords, mesh_dim = [], None + with cf_reader_class(filename) as cf: if PARSE_UGRID_ON_LOAD: - mesh_name = getattr(cf_var, "mesh", None) - if mesh_name is not None: + meshes = _meshes_from_cf(cf) + + # Process each CF data variable. + data_variables = list(cf.cf_group.data_variables.values()) + list( + cf.cf_group.promoted.values() + ) + for cf_var in data_variables: + if var_callback and not var_callback(cf_var): + # Deliver only selected results. + continue + + # cf_var-specific mesh handling, if a mesh is present. + # Build the mesh_coords *before* loading the cube - avoids + # mesh-related attributes being picked up by + # _add_unused_attributes(). + mesh_name = None + mesh = None + mesh_coords, mesh_dim = [], None + if PARSE_UGRID_ON_LOAD: + mesh_name = getattr(cf_var, "mesh", None) + if mesh_name is not None: + try: + mesh = meshes[mesh_name] + except KeyError: + message = ( + f"File does not contain mesh: '{mesh_name}' - " + f"referenced by variable: '{cf_var.cf_name}' ." + ) + logger.debug(message) + if mesh is not None: + mesh_coords, mesh_dim = _build_mesh_coords(mesh, cf_var) + + cube = _load_cube(engine, cf, cf_var, filename) + + # Attach the mesh (if present) to the cube. + for mesh_coord in mesh_coords: + cube.add_aux_coord(mesh_coord, mesh_dim) + + # Process any associated formula terms and attach + # the corresponding AuxCoordFactory. try: - mesh = meshes[mesh_name] - except KeyError: - message = ( - f"File does not contain mesh: '{mesh_name}' - " - f"referenced by variable: '{cf_var.cf_name}' ." - ) - logger.debug(message) - if mesh is not None: - mesh_coords, mesh_dim = _build_mesh_coords(mesh, cf_var) - - cube = _load_cube(engine, cf, cf_var, filename) - - # Attach the mesh (if present) to the cube. - for mesh_coord in mesh_coords: - cube.add_aux_coord(mesh_coord, mesh_dim) - - # Process any associated formula terms and attach - # the corresponding AuxCoordFactory. - try: - _load_aux_factory(engine, cube) - except ValueError as e: - warnings.warn("{}".format(e)) - - # Perform any user registered callback function. - cube = run_callback(callback, cube, cf_var, filename) - - # Callback mechanism may return None, which must not be yielded - if cube is None: - continue - - yield cube + _load_aux_factory(engine, cube) + except ValueError as e: + warnings.warn("{}".format(e)) + + # Perform any user registered callback function. + cube = run_callback(callback, cube, cf_var, filename) + + # Callback mechanism may return None, which must not be yielded + if cube is None: + continue + + yield cube diff --git a/lib/iris/fileformats/netcdf/saver.py b/lib/iris/fileformats/netcdf/saver.py index 650c5e3338..e5d3bf2cc7 100644 --- a/lib/iris/fileformats/netcdf/saver.py +++ b/lib/iris/fileformats/netcdf/saver.py @@ -24,7 +24,6 @@ import cf_units import dask.array as da -import netCDF4 import numpy as np import numpy.ma as ma @@ -45,6 +44,7 @@ from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord import iris.exceptions import iris.fileformats.cf +from iris.fileformats.netcdf import _thread_safe_nc import iris.io import iris.util @@ -459,7 +459,10 @@ def _setncattr(variable, name, attribute): Put the given attribute on the given netCDF4 Data type, casting attributes as we go to bytes rather than unicode. + NOTE: variable needs to be a _thread_safe_nc._ThreadSafeWrapper subclass. + """ + assert hasattr(variable, "THREAD_SAFE_FLAG") attribute = _bytes_if_ascii(attribute) return variable.setncattr(name, attribute) @@ -470,9 +473,12 @@ class _FillValueMaskCheckAndStoreTarget: given value and whether it was masked, before passing the chunk to the given target. + NOTE: target needs to be a _thread_safe_nc._ThreadSafeWrapper subclass. + """ def __init__(self, target, fill_value=None): + assert hasattr(target, "THREAD_SAFE_FLAG") self.target = target self.fill_value = fill_value self.contains_value = False @@ -544,7 +550,7 @@ def __init__(self, filename, netcdf_format): self._formula_terms_cache = {} #: NetCDF dataset try: - self._dataset = netCDF4.Dataset( + self._dataset = _thread_safe_nc.DatasetWrapper( filename, mode="w", format=netcdf_format ) except RuntimeError: @@ -2331,7 +2337,13 @@ def _create_cf_data_variable( dtype = data.dtype.newbyteorder("=") def set_packing_ncattrs(cfvar): - """Set netCDF packing attributes.""" + """ + Set netCDF packing attributes. + + NOTE: cfvar needs to be a _thread_safe_nc._ThreadSafeWrapper subclass. + + """ + assert hasattr(cfvar, "THREAD_SAFE_FLAG") if packing: if scale_factor: _setncattr(cfvar, "scale_factor", scale_factor) @@ -2478,7 +2490,9 @@ def store(data, cf_var, fill_value): if fill_value is not None: fill_value_to_check = fill_value else: - fill_value_to_check = netCDF4.default_fillvals[dtype.str[1:]] + fill_value_to_check = _thread_safe_nc.default_fillvals[ + dtype.str[1:] + ] else: fill_value_to_check = None diff --git a/lib/iris/tests/integration/netcdf/__init__.py b/lib/iris/tests/integration/netcdf/__init__.py new file mode 100644 index 0000000000..f500b52520 --- /dev/null +++ b/lib/iris/tests/integration/netcdf/__init__.py @@ -0,0 +1,6 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for loading and saving netcdf files.""" diff --git a/lib/iris/tests/integration/netcdf/test_attributes.py b/lib/iris/tests/integration/netcdf/test_attributes.py new file mode 100644 index 0000000000..a73d6c7d49 --- /dev/null +++ b/lib/iris/tests/integration/netcdf/test_attributes.py @@ -0,0 +1,119 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for attribute-related loading and saving netcdf files.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +from contextlib import contextmanager +from unittest import mock + +import iris +from iris.cube import Cube, CubeList +from iris.fileformats.netcdf import CF_CONVENTIONS_VERSION + + +class TestUmVersionAttribute(tests.IrisTest): + def test_single_saves_as_global(self): + cube = Cube( + [1.0], + standard_name="air_temperature", + units="K", + attributes={"um_version": "4.3"}, + ) + with self.temp_filename(".nc") as nc_path: + iris.save(cube, nc_path) + self.assertCDL(nc_path) + + def test_multiple_same_saves_as_global(self): + cube_a = Cube( + [1.0], + standard_name="air_temperature", + units="K", + attributes={"um_version": "4.3"}, + ) + cube_b = Cube( + [1.0], + standard_name="air_pressure", + units="hPa", + attributes={"um_version": "4.3"}, + ) + with self.temp_filename(".nc") as nc_path: + iris.save(CubeList([cube_a, cube_b]), nc_path) + self.assertCDL(nc_path) + + def test_multiple_different_saves_on_variables(self): + cube_a = Cube( + [1.0], + standard_name="air_temperature", + units="K", + attributes={"um_version": "4.3"}, + ) + cube_b = Cube( + [1.0], + standard_name="air_pressure", + units="hPa", + attributes={"um_version": "4.4"}, + ) + with self.temp_filename(".nc") as nc_path: + iris.save(CubeList([cube_a, cube_b]), nc_path) + self.assertCDL(nc_path) + + +@contextmanager +def _patch_site_configuration(): + def cf_patch_conventions(conventions): + return ", ".join([conventions, "convention1, convention2"]) + + def update(config): + config["cf_profile"] = mock.Mock(name="cf_profile") + config["cf_patch"] = mock.Mock(name="cf_patch") + config["cf_patch_conventions"] = cf_patch_conventions + + orig_site_config = iris.site_configuration.copy() + update(iris.site_configuration) + yield + iris.site_configuration = orig_site_config + + +class TestConventionsAttributes(tests.IrisTest): + def test_patching_conventions_attribute(self): + # Ensure that user defined conventions are wiped and those which are + # saved patched through site_config can be loaded without an exception + # being raised. + cube = Cube( + [1.0], + standard_name="air_temperature", + units="K", + attributes={"Conventions": "some user defined conventions"}, + ) + + # Patch the site configuration dictionary. + with _patch_site_configuration(), self.temp_filename(".nc") as nc_path: + iris.save(cube, nc_path) + res = iris.load_cube(nc_path) + + self.assertEqual( + res.attributes["Conventions"], + "{}, {}, {}".format( + CF_CONVENTIONS_VERSION, "convention1", "convention2" + ), + ) + + +class TestStandardName(tests.IrisTest): + def test_standard_name_roundtrip(self): + standard_name = "air_temperature detection_minimum" + cube = iris.cube.Cube(1, standard_name=standard_name) + with self.temp_filename(suffix=".nc") as fout: + iris.save(cube, fout) + detection_limit_cube = iris.load_cube(fout) + self.assertEqual(detection_limit_cube.standard_name, standard_name) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_aux_factories.py b/lib/iris/tests/integration/netcdf/test_aux_factories.py new file mode 100644 index 0000000000..d89f275336 --- /dev/null +++ b/lib/iris/tests/integration/netcdf/test_aux_factories.py @@ -0,0 +1,160 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for aux-factory-related loading and saving netcdf files.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import iris +from iris.tests import stock as stock + + +@tests.skip_data +class TestAtmosphereSigma(tests.IrisTest): + def setUp(self): + # Modify stock cube so it is suitable to have a atmosphere sigma + # factory added to it. + cube = stock.realistic_4d_no_derived() + cube.coord("surface_altitude").rename("surface_air_pressure") + cube.coord("surface_air_pressure").units = "Pa" + cube.coord("sigma").units = "1" + ptop_coord = iris.coords.AuxCoord(1000.0, var_name="ptop", units="Pa") + cube.add_aux_coord(ptop_coord, ()) + cube.remove_coord("level_height") + # Construct and add atmosphere sigma factory. + factory = iris.aux_factory.AtmosphereSigmaFactory( + cube.coord("ptop"), + cube.coord("sigma"), + cube.coord("surface_air_pressure"), + ) + cube.add_aux_factory(factory) + self.cube = cube + + def test_save(self): + with self.temp_filename(suffix=".nc") as filename: + iris.save(self.cube, filename) + self.assertCDL(filename) + + def test_save_load_loop(self): + # Ensure that the AtmosphereSigmaFactory is automatically loaded + # when loading the file. + with self.temp_filename(suffix=".nc") as filename: + iris.save(self.cube, filename) + cube = iris.load_cube(filename, "air_potential_temperature") + assert cube.coords("air_pressure") + + +@tests.skip_data +class TestHybridPressure(tests.IrisTest): + def setUp(self): + # Modify stock cube so it is suitable to have a + # hybrid pressure factory added to it. + cube = stock.realistic_4d_no_derived() + cube.coord("surface_altitude").rename("surface_air_pressure") + cube.coord("surface_air_pressure").units = "Pa" + cube.coord("level_height").rename("level_pressure") + cube.coord("level_pressure").units = "Pa" + # Construct and add hybrid pressure factory. + factory = iris.aux_factory.HybridPressureFactory( + cube.coord("level_pressure"), + cube.coord("sigma"), + cube.coord("surface_air_pressure"), + ) + cube.add_aux_factory(factory) + self.cube = cube + + def test_save(self): + with self.temp_filename(suffix=".nc") as filename: + iris.save(self.cube, filename) + self.assertCDL(filename) + + def test_save_load_loop(self): + # Tests an issue where the variable names in the formula + # terms changed to the standard_names instead of the variable names + # when loading a previously saved cube. + with self.temp_filename(suffix=".nc") as filename, self.temp_filename( + suffix=".nc" + ) as other_filename: + iris.save(self.cube, filename) + cube = iris.load_cube(filename, "air_potential_temperature") + iris.save(cube, other_filename) + other_cube = iris.load_cube( + other_filename, "air_potential_temperature" + ) + self.assertEqual(cube, other_cube) + + +@tests.skip_data +class TestSaveMultipleAuxFactories(tests.IrisTest): + def test_hybrid_height_and_pressure(self): + cube = stock.realistic_4d() + cube.add_aux_coord( + iris.coords.DimCoord( + 1200.0, long_name="level_pressure", units="hPa" + ) + ) + cube.add_aux_coord( + iris.coords.DimCoord(0.5, long_name="other sigma", units="1") + ) + cube.add_aux_coord( + iris.coords.DimCoord( + 1000.0, long_name="surface_air_pressure", units="hPa" + ) + ) + factory = iris.aux_factory.HybridPressureFactory( + cube.coord("level_pressure"), + cube.coord("other sigma"), + cube.coord("surface_air_pressure"), + ) + cube.add_aux_factory(factory) + with self.temp_filename(suffix=".nc") as filename: + iris.save(cube, filename) + self.assertCDL(filename) + + def test_shared_primary(self): + cube = stock.realistic_4d() + factory = iris.aux_factory.HybridHeightFactory( + cube.coord("level_height"), + cube.coord("sigma"), + cube.coord("surface_altitude"), + ) + factory.rename("another altitude") + cube.add_aux_factory(factory) + with self.temp_filename( + suffix=".nc" + ) as filename, self.assertRaisesRegex( + ValueError, "multiple aux factories" + ): + iris.save(cube, filename) + + def test_hybrid_height_cubes(self): + hh1 = stock.simple_4d_with_hybrid_height() + hh1.attributes["cube"] = "hh1" + hh2 = stock.simple_4d_with_hybrid_height() + hh2.attributes["cube"] = "hh2" + sa = hh2.coord("surface_altitude") + sa.points = sa.points * 10 + with self.temp_filename(".nc") as fname: + iris.save([hh1, hh2], fname) + cubes = iris.load(fname, "air_temperature") + cubes = sorted(cubes, key=lambda cube: cube.attributes["cube"]) + self.assertCML(cubes) + + def test_hybrid_height_cubes_on_dimension_coordinate(self): + hh1 = stock.hybrid_height() + hh2 = stock.hybrid_height() + sa = hh2.coord("surface_altitude") + sa.points = sa.points * 10 + emsg = "Unable to create dimensonless vertical coordinate." + with self.temp_filename(".nc") as fname, self.assertRaisesRegex( + ValueError, emsg + ): + iris.save([hh1, hh2], fname) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_coord_systems.py b/lib/iris/tests/integration/netcdf/test_coord_systems.py new file mode 100644 index 0000000000..8576f5ffe8 --- /dev/null +++ b/lib/iris/tests/integration/netcdf/test_coord_systems.py @@ -0,0 +1,281 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for coord-system-related loading and saving netcdf files.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +from os.path import join as path_join +import shutil +import tempfile + +import iris +from iris.coords import DimCoord +from iris.cube import Cube +from iris.tests import stock as stock +from iris.tests.stock.netcdf import ncgen_from_cdl +from iris.tests.unit.fileformats.netcdf import test_load_cubes as tlc + + +@tests.skip_data +class TestCoordSystem(tests.IrisTest): + def setUp(self): + tlc.setUpModule() + + def tearDown(self): + tlc.tearDownModule() + + def test_load_laea_grid(self): + cube = iris.load_cube( + tests.get_data_path( + ("NetCDF", "lambert_azimuthal_equal_area", "euro_air_temp.nc") + ) + ) + self.assertCML(cube, ("netcdf", "netcdf_laea.cml")) + + datum_cf_var_cdl = """ + netcdf output { + dimensions: + y = 4 ; + x = 3 ; + variables: + float data(y, x) ; + data :standard_name = "toa_brightness_temperature" ; + data :units = "K" ; + data :grid_mapping = "mercator" ; + int mercator ; + mercator:grid_mapping_name = "mercator" ; + mercator:longitude_of_prime_meridian = 0. ; + mercator:earth_radius = 6378169. ; + mercator:horizontal_datum_name = "OSGB36" ; + float y(y) ; + y:axis = "Y" ; + y:units = "m" ; + y:standard_name = "projection_y_coordinate" ; + float x(x) ; + x:axis = "X" ; + x:units = "m" ; + x:standard_name = "projection_x_coordinate" ; + + // global attributes: + :Conventions = "CF-1.7" ; + :standard_name_vocabulary = "CF Standard Name Table v27" ; + + data: + + data = + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9, 10, 11 ; + + mercator = _ ; + + y = 1, 2, 3, 5 ; + + x = -6, -4, -2 ; + + } + """ + + datum_wkt_cdl = """ +netcdf output5 { +dimensions: + y = 4 ; + x = 3 ; +variables: + float data(y, x) ; + data :standard_name = "toa_brightness_temperature" ; + data :units = "K" ; + data :grid_mapping = "mercator" ; + int mercator ; + mercator:grid_mapping_name = "mercator" ; + mercator:longitude_of_prime_meridian = 0. ; + mercator:earth_radius = 6378169. ; + mercator:longitude_of_projection_origin = 0. ; + mercator:false_easting = 0. ; + mercator:false_northing = 0. ; + mercator:scale_factor_at_projection_origin = 1. ; + mercator:crs_wkt = "PROJCRS[\\"unknown\\",BASEGEOGCRS[\\"unknown\\",DATUM[\\"OSGB36\\",ELLIPSOID[\\"unknown\\",6378169,0,LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]],PRIMEM[\\"Greenwich\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8901]]],CONVERSION[\\"unknown\\",METHOD[\\"Mercator (variant B)\\",ID[\\"EPSG\\",9805]],PARAMETER[\\"Latitude of 1st standard parallel\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8823]],PARAMETER[\\"Longitude of natural origin\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8802]],PARAMETER[\\"False easting\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8806]],PARAMETER[\\"False northing\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8807]]],CS[Cartesian,2],AXIS[\\"(E)\\",east,ORDER[1],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]],AXIS[\\"(N)\\",north,ORDER[2],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]]" ; + float y(y) ; + y:axis = "Y" ; + y:units = "m" ; + y:standard_name = "projection_y_coordinate" ; + float x(x) ; + x:axis = "X" ; + x:units = "m" ; + x:standard_name = "projection_x_coordinate" ; + +// global attributes: + :standard_name_vocabulary = "CF Standard Name Table v27" ; + :Conventions = "CF-1.7" ; +data: + + data = + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9, 10, 11 ; + + mercator = _ ; + + y = 1, 2, 3, 5 ; + + x = -6, -4, -2 ; +} + """ + + def test_load_datum_wkt(self): + expected = "OSGB 1936" + nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) + with iris.FUTURE.context(datum_support=True): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertMultiLineEqual(expected, actual) + + def test_no_load_datum_wkt(self): + nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) + with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertMultiLineEqual(actual, "unknown") + + def test_load_datum_cf_var(self): + expected = "OSGB 1936" + nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) + with iris.FUTURE.context(datum_support=True): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertMultiLineEqual(expected, actual) + + def test_no_load_datum_cf_var(self): + nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) + with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): + cube = iris.load_cube(nc_path) + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertMultiLineEqual(actual, "unknown") + + def test_save_datum(self): + expected = "OSGB 1936" + saved_crs = iris.coord_systems.Mercator( + ellipsoid=iris.coord_systems.GeogCS.from_datum("OSGB36") + ) + + base_cube = stock.realistic_3d() + base_lat_coord = base_cube.coord("grid_latitude") + test_lat_coord = DimCoord( + base_lat_coord.points, + standard_name="projection_y_coordinate", + coord_system=saved_crs, + ) + base_lon_coord = base_cube.coord("grid_longitude") + test_lon_coord = DimCoord( + base_lon_coord.points, + standard_name="projection_x_coordinate", + coord_system=saved_crs, + ) + test_cube = Cube( + base_cube.data, + standard_name=base_cube.standard_name, + units=base_cube.units, + dim_coords_and_dims=( + (base_cube.coord("time"), 0), + (test_lat_coord, 1), + (test_lon_coord, 2), + ), + ) + + with self.temp_filename(suffix=".nc") as filename: + iris.save(test_cube, filename) + with iris.FUTURE.context(datum_support=True): + cube = iris.load_cube(filename) + + test_crs = cube.coord("projection_y_coordinate").coord_system + actual = str(test_crs.as_cartopy_crs().datum) + self.assertMultiLineEqual(expected, actual) + + +class TestLoadMinimalGeostationary(tests.IrisTest): + """ + Check we can load data with a geostationary grid-mapping, even when the + 'false-easting' and 'false_northing' properties are missing. + + """ + + _geostationary_problem_cdl = """ +netcdf geostationary_problem_case { +dimensions: + y = 2 ; + x = 3 ; +variables: + short radiance(y, x) ; + radiance:standard_name = "toa_outgoing_radiance_per_unit_wavelength" ; + radiance:units = "W m-2 sr-1 um-1" ; + radiance:coordinates = "y x" ; + radiance:grid_mapping = "imager_grid_mapping" ; + short y(y) ; + y:units = "rad" ; + y:axis = "Y" ; + y:long_name = "fixed grid projection y-coordinate" ; + y:standard_name = "projection_y_coordinate" ; + short x(x) ; + x:units = "rad" ; + x:axis = "X" ; + x:long_name = "fixed grid projection x-coordinate" ; + x:standard_name = "projection_x_coordinate" ; + int imager_grid_mapping ; + imager_grid_mapping:grid_mapping_name = "geostationary" ; + imager_grid_mapping:perspective_point_height = 35786023. ; + imager_grid_mapping:semi_major_axis = 6378137. ; + imager_grid_mapping:semi_minor_axis = 6356752.31414 ; + imager_grid_mapping:latitude_of_projection_origin = 0. ; + imager_grid_mapping:longitude_of_projection_origin = -75. ; + imager_grid_mapping:sweep_angle_axis = "x" ; + +data: + + // coord values, just so these can be dim-coords + y = 0, 1 ; + x = 0, 1, 2 ; + +} +""" + + @classmethod + def setUpClass(cls): + # Create a temp directory for transient test files. + cls.temp_dir = tempfile.mkdtemp() + cls.path_test_cdl = path_join(cls.temp_dir, "geos_problem.cdl") + cls.path_test_nc = path_join(cls.temp_dir, "geos_problem.nc") + # Create reference CDL and netcdf files from the CDL text. + ncgen_from_cdl( + cdl_str=cls._geostationary_problem_cdl, + cdl_path=cls.path_test_cdl, + nc_path=cls.path_test_nc, + ) + + @classmethod + def tearDownClass(cls): + # Destroy the temp directory. + shutil.rmtree(cls.temp_dir) + + def test_geostationary_no_false_offsets(self): + # Check we can load the test data and coordinate system properties are correct. + cube = iris.load_cube(self.path_test_nc) + # Check the coordinate system properties has the correct default properties. + cs = cube.coord_system() + self.assertIsInstance(cs, iris.coord_systems.Geostationary) + self.assertEqual(cs.false_easting, 0.0) + self.assertEqual(cs.false_northing, 0.0) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_general.py b/lib/iris/tests/integration/netcdf/test_general.py new file mode 100644 index 0000000000..63b977674d --- /dev/null +++ b/lib/iris/tests/integration/netcdf/test_general.py @@ -0,0 +1,360 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for loading and saving netcdf files.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +from itertools import repeat +import os.path +import shutil +import tempfile +import warnings + +import numpy as np +import numpy.ma as ma +import pytest + +import iris +import iris.coord_systems +from iris.coords import CellMethod +from iris.cube import Cube, CubeList +import iris.exceptions +from iris.fileformats.netcdf import Saver, UnknownCellMethodWarning +from iris.tests.stock.netcdf import ncgen_from_cdl + + +class TestLazySave(tests.IrisTest): + @tests.skip_data + def test_lazy_preserved_save(self): + fpath = tests.get_data_path( + ("NetCDF", "label_and_climate", "small_FC_167_mon_19601101.nc") + ) + acube = iris.load_cube(fpath, "air_temperature") + self.assertTrue(acube.has_lazy_data()) + # Also check a coord with lazy points + bounds. + self.assertTrue(acube.coord("forecast_period").has_lazy_points()) + self.assertTrue(acube.coord("forecast_period").has_lazy_bounds()) + with self.temp_filename(".nc") as nc_path: + with Saver(nc_path, "NETCDF4") as saver: + saver.write(acube) + # Check that cube data is not realised, also coord points + bounds. + self.assertTrue(acube.has_lazy_data()) + self.assertTrue(acube.coord("forecast_period").has_lazy_points()) + self.assertTrue(acube.coord("forecast_period").has_lazy_bounds()) + + +@tests.skip_data +class TestCellMeasures(tests.IrisTest): + def setUp(self): + self.fname = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) + + def test_load_raw(self): + (cube,) = iris.load_raw(self.fname) + self.assertEqual(len(cube.cell_measures()), 1) + self.assertEqual(cube.cell_measures()[0].measure, "area") + + def test_load(self): + cube = iris.load_cube(self.fname) + self.assertEqual(len(cube.cell_measures()), 1) + self.assertEqual(cube.cell_measures()[0].measure, "area") + + def test_merge_cell_measure_aware(self): + (cube1,) = iris.load_raw(self.fname) + (cube2,) = iris.load_raw(self.fname) + cube2._cell_measures_and_dims[0][0].var_name = "not_areat" + cubes = CubeList([cube1, cube2]).merge() + self.assertEqual(len(cubes), 2) + + def test_concatenate_cell_measure_aware(self): + (cube1,) = iris.load_raw(self.fname) + cube1 = cube1[:, :, 0, 0] + cm_and_dims = cube1._cell_measures_and_dims + (cube2,) = iris.load_raw(self.fname) + cube2 = cube2[:, :, 0, 0] + cube2._cell_measures_and_dims[0][0].var_name = "not_areat" + cube2.coord("time").points = cube2.coord("time").points + 1 + cubes = CubeList([cube1, cube2]).concatenate() + self.assertEqual(cubes[0]._cell_measures_and_dims, cm_and_dims) + self.assertEqual(len(cubes), 2) + + def test_concatenate_cell_measure_match(self): + (cube1,) = iris.load_raw(self.fname) + cube1 = cube1[:, :, 0, 0] + cm_and_dims = cube1._cell_measures_and_dims + (cube2,) = iris.load_raw(self.fname) + cube2 = cube2[:, :, 0, 0] + cube2.coord("time").points = cube2.coord("time").points + 1 + cubes = CubeList([cube1, cube2]).concatenate() + self.assertEqual(cubes[0]._cell_measures_and_dims, cm_and_dims) + self.assertEqual(len(cubes), 1) + + def test_round_trip(self): + (cube,) = iris.load(self.fname) + with self.temp_filename(suffix=".nc") as filename: + iris.save(cube, filename, unlimited_dimensions=[]) + (round_cube,) = iris.load_raw(filename) + self.assertEqual(len(round_cube.cell_measures()), 1) + self.assertEqual(round_cube.cell_measures()[0].measure, "area") + + def test_print(self): + cube = iris.load_cube(self.fname) + printed = cube.__str__() + self.assertIn( + ( + "Cell measures:\n" + " cell_area - - " + " x x" + ), + printed, + ) + + +class TestCellMethod_unknown(tests.IrisTest): + def test_unknown_method(self): + cube = Cube([1, 2], long_name="odd_phenomenon") + cube.add_cell_method(CellMethod(method="oddity", coords=("x",))) + temp_dirpath = tempfile.mkdtemp() + try: + temp_filepath = os.path.join(temp_dirpath, "tmp.nc") + iris.save(cube, temp_filepath) + with warnings.catch_warnings(record=True) as warning_records: + iris.load(temp_filepath) + # Filter to get the warning we are interested in. + warning_messages = [record.message for record in warning_records] + warning_messages = [ + warn + for warn in warning_messages + if isinstance(warn, UnknownCellMethodWarning) + ] + self.assertEqual(len(warning_messages), 1) + message = warning_messages[0].args[0] + msg = ( + "NetCDF variable 'odd_phenomenon' contains unknown cell " + "method 'oddity'" + ) + self.assertIn(msg, message) + finally: + shutil.rmtree(temp_dirpath) + + +def _get_scale_factor_add_offset(cube, datatype): + """Utility function used by netCDF data packing tests.""" + if isinstance(datatype, dict): + dt = np.dtype(datatype["dtype"]) + else: + dt = np.dtype(datatype) + cmax = cube.data.max() + cmin = cube.data.min() + n = dt.itemsize * 8 + if ma.isMaskedArray(cube.data): + masked = True + else: + masked = False + if masked: + scale_factor = (cmax - cmin) / (2**n - 2) + else: + scale_factor = (cmax - cmin) / (2**n - 1) + if dt.kind == "u": + add_offset = cmin + elif dt.kind == "i": + if masked: + add_offset = (cmax + cmin) / 2 + else: + add_offset = cmin + 2 ** (n - 1) * scale_factor + return (scale_factor, add_offset) + + +@tests.skip_data +class TestPackedData(tests.IrisTest): + def _single_test(self, datatype, CDLfilename, manual=False): + # Read PP input file. + file_in = tests.get_data_path( + ( + "PP", + "cf_processing", + "000003000000.03.236.000128.1990.12.01.00.00.b.pp", + ) + ) + cube = iris.load_cube(file_in) + scale_factor, offset = _get_scale_factor_add_offset(cube, datatype) + if manual: + packspec = dict( + dtype=datatype, scale_factor=scale_factor, add_offset=offset + ) + else: + packspec = datatype + # Write Cube to netCDF file. + with self.temp_filename(suffix=".nc") as file_out: + iris.save(cube, file_out, packing=packspec) + decimal = int(-np.log10(scale_factor)) + packedcube = iris.load_cube(file_out) + # Check that packed cube is accurate to expected precision + self.assertArrayAlmostEqual( + cube.data, packedcube.data, decimal=decimal + ) + # Check the netCDF file against CDL expected output. + self.assertCDL( + file_out, + ( + "integration", + "netcdf", + "general", + "TestPackedData", + CDLfilename, + ), + ) + + def test_single_packed_signed(self): + """Test saving a single CF-netCDF file with packing.""" + self._single_test("i2", "single_packed_signed.cdl") + + def test_single_packed_unsigned(self): + """Test saving a single CF-netCDF file with packing into unsigned.""" + self._single_test("u1", "single_packed_unsigned.cdl") + + def test_single_packed_manual_scale(self): + """Test saving a single CF-netCDF file with packing with scale + factor and add_offset set manually.""" + self._single_test("i2", "single_packed_manual.cdl", manual=True) + + def _multi_test(self, CDLfilename, multi_dtype=False): + """Test saving multiple packed cubes with pack_dtype list.""" + # Read PP input file. + file_in = tests.get_data_path( + ("PP", "cf_processing", "abcza_pa19591997_daily_29.b.pp") + ) + cubes = iris.load(file_in) + # ensure cube order is the same: + cubes.sort(key=lambda cube: cube.cell_methods[0].method) + datatype = "i2" + scale_factor, offset = _get_scale_factor_add_offset(cubes[0], datatype) + if multi_dtype: + packdict = dict( + dtype=datatype, scale_factor=scale_factor, add_offset=offset + ) + packspec = [packdict, None, "u2"] + dtypes = packspec + else: + packspec = datatype + dtypes = repeat(packspec) + + # Write Cube to netCDF file. + with self.temp_filename(suffix=".nc") as file_out: + iris.save(cubes, file_out, packing=packspec) + # Check the netCDF file against CDL expected output. + self.assertCDL( + file_out, + ( + "integration", + "netcdf", + "general", + "TestPackedData", + CDLfilename, + ), + ) + packedcubes = iris.load(file_out) + packedcubes.sort(key=lambda cube: cube.cell_methods[0].method) + for cube, packedcube, dtype in zip(cubes, packedcubes, dtypes): + if dtype: + sf, ao = _get_scale_factor_add_offset(cube, dtype) + decimal = int(-np.log10(sf)) + # Check that packed cube is accurate to expected precision + self.assertArrayAlmostEqual( + cube.data, packedcube.data, decimal=decimal + ) + else: + self.assertArrayEqual(cube.data, packedcube.data) + + def test_multi_packed_single_dtype(self): + """Test saving multiple packed cubes with the same pack_dtype.""" + # Read PP input file. + self._multi_test("multi_packed_single_dtype.cdl") + + def test_multi_packed_multi_dtype(self): + """Test saving multiple packed cubes with pack_dtype list.""" + # Read PP input file. + self._multi_test("multi_packed_multi_dtype.cdl", multi_dtype=True) + + +class TestScalarCube(tests.IrisTest): + def test_scalar_cube_save_load(self): + cube = iris.cube.Cube(1, long_name="scalar_cube") + with self.temp_filename(suffix=".nc") as fout: + iris.save(cube, fout) + scalar_cube = iris.load_cube(fout) + self.assertEqual(scalar_cube.name(), "scalar_cube") + + +@tests.skip_data +class TestConstrainedLoad(tests.IrisTest): + filename = tests.get_data_path( + ("NetCDF", "label_and_climate", "A1B-99999a-river-sep-2070-2099.nc") + ) + + def test_netcdf_with_NameConstraint(self): + constr = iris.NameConstraint(var_name="cdf_temp_dmax_tmean_abs") + cubes = iris.load(self.filename, constr) + self.assertEqual(len(cubes), 1) + self.assertEqual(cubes[0].var_name, "cdf_temp_dmax_tmean_abs") + + def test_netcdf_with_no_constraint(self): + cubes = iris.load(self.filename) + self.assertEqual(len(cubes), 3) + + +class TestSkippedCoord: + # If a coord/cell measure/etcetera cannot be added to the loaded Cube, a + # Warning is raised and the coord is skipped. + # This 'catching' is generic to all CannotAddErrors, but currently the only + # such problem that can exist in a NetCDF file is a mismatch of dimensions + # between phenomenon and coord. + + cdl_core = """ +dimensions: + length_scale = 1 ; + lat = 3 ; +variables: + float lat(lat) ; + lat:standard_name = "latitude" ; + lat:units = "degrees_north" ; + short lst_unc_sys(length_scale) ; + lst_unc_sys:long_name = "uncertainty from large-scale systematic + errors" ; + lst_unc_sys:units = "kelvin" ; + lst_unc_sys:coordinates = "lat" ; + +data: + lat = 0, 1, 2; + """ + + @pytest.fixture(autouse=True) + def create_nc_file(self, tmp_path): + file_name = "dim_mismatch" + cdl = f"netcdf {file_name}" + "{\n" + self.cdl_core + "\n}" + self.nc_path = (tmp_path / file_name).with_suffix(".nc") + ncgen_from_cdl( + cdl_str=cdl, + cdl_path=None, + nc_path=str(self.nc_path), + ) + yield + self.nc_path.unlink() + + def test_lat_not_loaded(self): + # iris#5068 includes discussion of possible retention of the skipped + # coords in the future. + with pytest.warns( + match="Missing data dimensions for multi-valued DimCoord" + ): + cube = iris.load_cube(self.nc_path) + with pytest.raises(iris.exceptions.CoordinateNotFoundError): + _ = cube.coord("lat") + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_self_referencing.py b/lib/iris/tests/integration/netcdf/test_self_referencing.py new file mode 100644 index 0000000000..3395296e11 --- /dev/null +++ b/lib/iris/tests/integration/netcdf/test_self_referencing.py @@ -0,0 +1,126 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +"""Integration tests for iris#3367 - loading a self-referencing NetCDF file.""" + +# Import iris.tests first so that some things can be initialised before +# importing anything else. +import iris.tests as tests # isort:skip + +import os +import tempfile +from unittest import mock + +import numpy as np + +import iris +from iris.fileformats.netcdf import _thread_safe_nc + + +@tests.skip_data +class TestCMIP6VolcelloLoad(tests.IrisTest): + def setUp(self): + self.fname = tests.get_data_path( + ( + "NetCDF", + "volcello", + "volcello_Ofx_CESM2_deforest-globe_r1i1p1f1_gn.nc", + ) + ) + + def test_cmip6_volcello_load_issue_3367(self): + # Ensure that reading a file which references itself in + # `cell_measures` can be read. At the same time, ensure that we + # still receive a warning about other variables mentioned in + # `cell_measures` i.e. a warning should be raised about missing + # areacello. + areacello_str = "areacello" + volcello_str = "volcello" + expected_msg = ( + "Missing CF-netCDF measure variable %r, " + "referenced by netCDF variable %r" % (areacello_str, volcello_str) + ) + + with mock.patch("warnings.warn") as warn: + # ensure file loads without failure + cube = iris.load_cube(self.fname) + warn.assert_has_calls([mock.call(expected_msg)]) + + # extra check to ensure correct variable was found + assert cube.standard_name == "ocean_volume" + + +class TestSelfReferencingVarLoad(tests.IrisTest): + def setUp(self): + self.temp_dir_path = os.path.join( + tempfile.mkdtemp(), "issue_3367_volcello_test_file.nc" + ) + dataset = _thread_safe_nc.DatasetWrapper(self.temp_dir_path, "w") + + dataset.createDimension("lat", 4) + dataset.createDimension("lon", 5) + dataset.createDimension("lev", 3) + + latitudes = dataset.createVariable("lat", np.float64, ("lat",)) + longitudes = dataset.createVariable("lon", np.float64, ("lon",)) + levels = dataset.createVariable("lev", np.float64, ("lev",)) + volcello = dataset.createVariable( + "volcello", np.float32, ("lat", "lon", "lev") + ) + + latitudes.standard_name = "latitude" + latitudes.units = "degrees_north" + latitudes.axis = "Y" + latitudes[:] = np.linspace(-90, 90, 4) + + longitudes.standard_name = "longitude" + longitudes.units = "degrees_east" + longitudes.axis = "X" + longitudes[:] = np.linspace(0, 360, 5) + + levels.standard_name = "olevel" + levels.units = "centimeters" + levels.positive = "down" + levels.axis = "Z" + levels[:] = np.linspace(0, 10**5, 3) + + volcello.id = "volcello" + volcello.out_name = "volcello" + volcello.standard_name = "ocean_volume" + volcello.units = "m3" + volcello.realm = "ocean" + volcello.frequency = "fx" + volcello.cell_measures = "area: areacello volume: volcello" + volcello = np.arange(4 * 5 * 3).reshape((4, 5, 3)) + + dataset.close() + + def test_self_referencing_load_issue_3367(self): + # Ensure that reading a file which references itself in + # `cell_measures` can be read. At the same time, ensure that we + # still receive a warning about other variables mentioned in + # `cell_measures` i.e. a warning should be raised about missing + # areacello. + areacello_str = "areacello" + volcello_str = "volcello" + expected_msg = ( + "Missing CF-netCDF measure variable %r, " + "referenced by netCDF variable %r" % (areacello_str, volcello_str) + ) + + with mock.patch("warnings.warn") as warn: + # ensure file loads without failure + cube = iris.load_cube(self.temp_dir_path) + warn.assert_called_with(expected_msg) + + # extra check to ensure correct variable was found + assert cube.standard_name == "ocean_volume" + + def tearDown(self): + os.remove(self.temp_dir_path) + + +if __name__ == "__main__": + tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_thread_safety.py b/lib/iris/tests/integration/netcdf/test_thread_safety.py new file mode 100644 index 0000000000..280e0f8418 --- /dev/null +++ b/lib/iris/tests/integration/netcdf/test_thread_safety.py @@ -0,0 +1,109 @@ +# Copyright Iris contributors +# +# This file is part of Iris and is released under the LGPL license. +# See COPYING and COPYING.LESSER in the root of the repository for full +# licensing details. +""" +Integration tests covering thread safety during loading/saving netcdf files. + +These tests are intended to catch non-thread-safe behaviour by producing CI +'irregularities' that are noticed and investigated. They cannot reliably +produce standard pytest failures, since the tools for 'correctly' +testing non-thread-safe behaviour are not available at the Python layer. +Thread safety problems can be either produce errors (like a normal test) OR +segfaults (test doesn't complete, pytest-xdiff starts a new group worker, the +end exit code is still non-0), and some problems do not occur in every test +run. + +Token assertions are included after the line that is expected to reveal +a thread safety problem, as this seems to be good testing practice. + +""" +from pathlib import Path + +import dask +from dask import array as da +import numpy as np +import pytest + +import iris +from iris.cube import Cube, CubeList +from iris.tests import get_data_path + + +@pytest.fixture +def tiny_chunks(): + """Guarantee that Dask will use >1 thread by guaranteeing >1 chunk.""" + + def _check_tiny_loaded_chunks(cube: Cube): + assert cube.has_lazy_data() + cube_lazy_data = cube.core_data() + assert np.product(cube_lazy_data.chunksize) < cube_lazy_data.size + + with dask.config.set({"array.chunk-size": "1KiB"}): + yield _check_tiny_loaded_chunks + + +@pytest.fixture +def save_common(tmp_path): + save_path = tmp_path / "tmp.nc" + + def _func(cube: Cube): + assert not save_path.exists() + iris.save(cube, save_path) + assert save_path.exists() + + yield _func + + +@pytest.fixture +def get_cubes_from_netcdf(): + load_dir_path = Path(get_data_path(["NetCDF", "global", "xyt"])) + loaded = iris.load(load_dir_path.glob("*"), "tcco2") + smaller = CubeList([c[0] for c in loaded]) + yield smaller + + +def test_realise_data(tiny_chunks, get_cubes_from_netcdf): + cube = get_cubes_from_netcdf[0] + tiny_chunks(cube) + _ = cube.data # Any problems are expected here. + assert not cube.has_lazy_data() + + +def test_realise_data_multisource(get_cubes_from_netcdf): + """Load from multiple sources to force Dask to use multiple threads.""" + cubes = get_cubes_from_netcdf + final_cube = sum(cubes) + _ = final_cube.data # Any problems are expected here. + assert not final_cube.has_lazy_data() + + +def test_save(tiny_chunks, save_common): + cube = Cube(da.ones(10000)) + tiny_chunks(cube) + save_common(cube) # Any problems are expected here. + + +def test_stream(tiny_chunks, get_cubes_from_netcdf, save_common): + cube = get_cubes_from_netcdf[0] + tiny_chunks(cube) + save_common(cube) # Any problems are expected here. + + +def test_stream_multisource(get_cubes_from_netcdf, save_common): + """Load from multiple sources to force Dask to use multiple threads.""" + cubes = get_cubes_from_netcdf + final_cube = sum(cubes) + save_common(final_cube) # Any problems are expected here. + + +def test_comparison(get_cubes_from_netcdf): + """ + Comparing multiple loaded files forces co-realisation. + + See :func:`iris._lazy_data._co_realise_lazy_arrays` . + """ + cubes = get_cubes_from_netcdf + _ = cubes[:-1] == cubes[1:] # Any problems are expected here. + assert all([c.has_lazy_data() for c in cubes]) diff --git a/lib/iris/tests/integration/test_netcdf.py b/lib/iris/tests/integration/test_netcdf.py deleted file mode 100644 index 851c539ade..0000000000 --- a/lib/iris/tests/integration/test_netcdf.py +++ /dev/null @@ -1,958 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for loading and saving netcdf files.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from contextlib import contextmanager -from itertools import repeat -import os.path -from os.path import join as path_join -import shutil -import tempfile -from unittest import mock -import warnings - -import netCDF4 as nc -import numpy as np -import numpy.ma as ma -import pytest - -import iris -import iris.coord_systems -from iris.coords import CellMethod, DimCoord -from iris.cube import Cube, CubeList -import iris.exceptions -from iris.fileformats.netcdf import ( - CF_CONVENTIONS_VERSION, - Saver, - UnknownCellMethodWarning, -) -import iris.tests.stock as stock -from iris.tests.stock.netcdf import ncgen_from_cdl -import iris.tests.unit.fileformats.netcdf.test_load_cubes as tlc - - -@tests.skip_data -class TestAtmosphereSigma(tests.IrisTest): - def setUp(self): - # Modify stock cube so it is suitable to have a atmosphere sigma - # factory added to it. - cube = stock.realistic_4d_no_derived() - cube.coord("surface_altitude").rename("surface_air_pressure") - cube.coord("surface_air_pressure").units = "Pa" - cube.coord("sigma").units = "1" - ptop_coord = iris.coords.AuxCoord(1000.0, var_name="ptop", units="Pa") - cube.add_aux_coord(ptop_coord, ()) - cube.remove_coord("level_height") - # Construct and add atmosphere sigma factory. - factory = iris.aux_factory.AtmosphereSigmaFactory( - cube.coord("ptop"), - cube.coord("sigma"), - cube.coord("surface_air_pressure"), - ) - cube.add_aux_factory(factory) - self.cube = cube - - def test_save(self): - with self.temp_filename(suffix=".nc") as filename: - iris.save(self.cube, filename) - self.assertCDL(filename) - - def test_save_load_loop(self): - # Ensure that the AtmosphereSigmaFactory is automatically loaded - # when loading the file. - with self.temp_filename(suffix=".nc") as filename: - iris.save(self.cube, filename) - cube = iris.load_cube(filename, "air_potential_temperature") - assert cube.coords("air_pressure") - - -@tests.skip_data -class TestHybridPressure(tests.IrisTest): - def setUp(self): - # Modify stock cube so it is suitable to have a - # hybrid pressure factory added to it. - cube = stock.realistic_4d_no_derived() - cube.coord("surface_altitude").rename("surface_air_pressure") - cube.coord("surface_air_pressure").units = "Pa" - cube.coord("level_height").rename("level_pressure") - cube.coord("level_pressure").units = "Pa" - # Construct and add hybrid pressure factory. - factory = iris.aux_factory.HybridPressureFactory( - cube.coord("level_pressure"), - cube.coord("sigma"), - cube.coord("surface_air_pressure"), - ) - cube.add_aux_factory(factory) - self.cube = cube - - def test_save(self): - with self.temp_filename(suffix=".nc") as filename: - iris.save(self.cube, filename) - self.assertCDL(filename) - - def test_save_load_loop(self): - # Tests an issue where the variable names in the formula - # terms changed to the standard_names instead of the variable names - # when loading a previously saved cube. - with self.temp_filename(suffix=".nc") as filename, self.temp_filename( - suffix=".nc" - ) as other_filename: - iris.save(self.cube, filename) - cube = iris.load_cube(filename, "air_potential_temperature") - iris.save(cube, other_filename) - other_cube = iris.load_cube( - other_filename, "air_potential_temperature" - ) - self.assertEqual(cube, other_cube) - - -@tests.skip_data -class TestSaveMultipleAuxFactories(tests.IrisTest): - def test_hybrid_height_and_pressure(self): - cube = stock.realistic_4d() - cube.add_aux_coord( - iris.coords.DimCoord( - 1200.0, long_name="level_pressure", units="hPa" - ) - ) - cube.add_aux_coord( - iris.coords.DimCoord(0.5, long_name="other sigma", units="1") - ) - cube.add_aux_coord( - iris.coords.DimCoord( - 1000.0, long_name="surface_air_pressure", units="hPa" - ) - ) - factory = iris.aux_factory.HybridPressureFactory( - cube.coord("level_pressure"), - cube.coord("other sigma"), - cube.coord("surface_air_pressure"), - ) - cube.add_aux_factory(factory) - with self.temp_filename(suffix=".nc") as filename: - iris.save(cube, filename) - self.assertCDL(filename) - - def test_shared_primary(self): - cube = stock.realistic_4d() - factory = iris.aux_factory.HybridHeightFactory( - cube.coord("level_height"), - cube.coord("sigma"), - cube.coord("surface_altitude"), - ) - factory.rename("another altitude") - cube.add_aux_factory(factory) - with self.temp_filename( - suffix=".nc" - ) as filename, self.assertRaisesRegex( - ValueError, "multiple aux factories" - ): - iris.save(cube, filename) - - def test_hybrid_height_cubes(self): - hh1 = stock.simple_4d_with_hybrid_height() - hh1.attributes["cube"] = "hh1" - hh2 = stock.simple_4d_with_hybrid_height() - hh2.attributes["cube"] = "hh2" - sa = hh2.coord("surface_altitude") - sa.points = sa.points * 10 - with self.temp_filename(".nc") as fname: - iris.save([hh1, hh2], fname) - cubes = iris.load(fname, "air_temperature") - cubes = sorted(cubes, key=lambda cube: cube.attributes["cube"]) - self.assertCML(cubes) - - def test_hybrid_height_cubes_on_dimension_coordinate(self): - hh1 = stock.hybrid_height() - hh2 = stock.hybrid_height() - sa = hh2.coord("surface_altitude") - sa.points = sa.points * 10 - emsg = "Unable to create dimensonless vertical coordinate." - with self.temp_filename(".nc") as fname, self.assertRaisesRegex( - ValueError, emsg - ): - iris.save([hh1, hh2], fname) - - -class TestUmVersionAttribute(tests.IrisTest): - def test_single_saves_as_global(self): - cube = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"um_version": "4.3"}, - ) - with self.temp_filename(".nc") as nc_path: - iris.save(cube, nc_path) - self.assertCDL(nc_path) - - def test_multiple_same_saves_as_global(self): - cube_a = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"um_version": "4.3"}, - ) - cube_b = Cube( - [1.0], - standard_name="air_pressure", - units="hPa", - attributes={"um_version": "4.3"}, - ) - with self.temp_filename(".nc") as nc_path: - iris.save(CubeList([cube_a, cube_b]), nc_path) - self.assertCDL(nc_path) - - def test_multiple_different_saves_on_variables(self): - cube_a = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"um_version": "4.3"}, - ) - cube_b = Cube( - [1.0], - standard_name="air_pressure", - units="hPa", - attributes={"um_version": "4.4"}, - ) - with self.temp_filename(".nc") as nc_path: - iris.save(CubeList([cube_a, cube_b]), nc_path) - self.assertCDL(nc_path) - - -@contextmanager -def _patch_site_configuration(): - def cf_patch_conventions(conventions): - return ", ".join([conventions, "convention1, convention2"]) - - def update(config): - config["cf_profile"] = mock.Mock(name="cf_profile") - config["cf_patch"] = mock.Mock(name="cf_patch") - config["cf_patch_conventions"] = cf_patch_conventions - - orig_site_config = iris.site_configuration.copy() - update(iris.site_configuration) - yield - iris.site_configuration = orig_site_config - - -class TestConventionsAttributes(tests.IrisTest): - def test_patching_conventions_attribute(self): - # Ensure that user defined conventions are wiped and those which are - # saved patched through site_config can be loaded without an exception - # being raised. - cube = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"Conventions": "some user defined conventions"}, - ) - - # Patch the site configuration dictionary. - with _patch_site_configuration(), self.temp_filename(".nc") as nc_path: - iris.save(cube, nc_path) - res = iris.load_cube(nc_path) - - self.assertEqual( - res.attributes["Conventions"], - "{}, {}, {}".format( - CF_CONVENTIONS_VERSION, "convention1", "convention2" - ), - ) - - -class TestLazySave(tests.IrisTest): - @tests.skip_data - def test_lazy_preserved_save(self): - fpath = tests.get_data_path( - ("NetCDF", "label_and_climate", "small_FC_167_mon_19601101.nc") - ) - acube = iris.load_cube(fpath, "air_temperature") - self.assertTrue(acube.has_lazy_data()) - # Also check a coord with lazy points + bounds. - self.assertTrue(acube.coord("forecast_period").has_lazy_points()) - self.assertTrue(acube.coord("forecast_period").has_lazy_bounds()) - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(acube) - # Check that cube data is not realised, also coord points + bounds. - self.assertTrue(acube.has_lazy_data()) - self.assertTrue(acube.coord("forecast_period").has_lazy_points()) - self.assertTrue(acube.coord("forecast_period").has_lazy_bounds()) - - -@tests.skip_data -class TestCellMeasures(tests.IrisTest): - def setUp(self): - self.fname = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) - - def test_load_raw(self): - (cube,) = iris.load_raw(self.fname) - self.assertEqual(len(cube.cell_measures()), 1) - self.assertEqual(cube.cell_measures()[0].measure, "area") - - def test_load(self): - cube = iris.load_cube(self.fname) - self.assertEqual(len(cube.cell_measures()), 1) - self.assertEqual(cube.cell_measures()[0].measure, "area") - - def test_merge_cell_measure_aware(self): - (cube1,) = iris.load_raw(self.fname) - (cube2,) = iris.load_raw(self.fname) - cube2._cell_measures_and_dims[0][0].var_name = "not_areat" - cubes = CubeList([cube1, cube2]).merge() - self.assertEqual(len(cubes), 2) - - def test_concatenate_cell_measure_aware(self): - (cube1,) = iris.load_raw(self.fname) - cube1 = cube1[:, :, 0, 0] - cm_and_dims = cube1._cell_measures_and_dims - (cube2,) = iris.load_raw(self.fname) - cube2 = cube2[:, :, 0, 0] - cube2._cell_measures_and_dims[0][0].var_name = "not_areat" - cube2.coord("time").points = cube2.coord("time").points + 1 - cubes = CubeList([cube1, cube2]).concatenate() - self.assertEqual(cubes[0]._cell_measures_and_dims, cm_and_dims) - self.assertEqual(len(cubes), 2) - - def test_concatenate_cell_measure_match(self): - (cube1,) = iris.load_raw(self.fname) - cube1 = cube1[:, :, 0, 0] - cm_and_dims = cube1._cell_measures_and_dims - (cube2,) = iris.load_raw(self.fname) - cube2 = cube2[:, :, 0, 0] - cube2.coord("time").points = cube2.coord("time").points + 1 - cubes = CubeList([cube1, cube2]).concatenate() - self.assertEqual(cubes[0]._cell_measures_and_dims, cm_and_dims) - self.assertEqual(len(cubes), 1) - - def test_round_trip(self): - (cube,) = iris.load(self.fname) - with self.temp_filename(suffix=".nc") as filename: - iris.save(cube, filename, unlimited_dimensions=[]) - (round_cube,) = iris.load_raw(filename) - self.assertEqual(len(round_cube.cell_measures()), 1) - self.assertEqual(round_cube.cell_measures()[0].measure, "area") - - def test_print(self): - cube = iris.load_cube(self.fname) - printed = cube.__str__() - self.assertIn( - ( - "Cell measures:\n" - " cell_area - - " - " x x" - ), - printed, - ) - - -@tests.skip_data -class TestCMIP6VolcelloLoad(tests.IrisTest): - def setUp(self): - self.fname = tests.get_data_path( - ( - "NetCDF", - "volcello", - "volcello_Ofx_CESM2_deforest-globe_r1i1p1f1_gn.nc", - ) - ) - - def test_cmip6_volcello_load_issue_3367(self): - # Ensure that reading a file which references itself in - # `cell_measures` can be read. At the same time, ensure that we - # still receive a warning about other variables mentioned in - # `cell_measures` i.e. a warning should be raised about missing - # areacello. - areacello_str = "areacello" - volcello_str = "volcello" - expected_msg = ( - "Missing CF-netCDF measure variable %r, " - "referenced by netCDF variable %r" % (areacello_str, volcello_str) - ) - - with mock.patch("warnings.warn") as warn: - # ensure file loads without failure - cube = iris.load_cube(self.fname) - warn.assert_has_calls([mock.call(expected_msg)]) - - # extra check to ensure correct variable was found - assert cube.standard_name == "ocean_volume" - - -class TestSelfReferencingVarLoad(tests.IrisTest): - def setUp(self): - self.temp_dir_path = os.path.join( - tempfile.mkdtemp(), "issue_3367_volcello_test_file.nc" - ) - dataset = nc.Dataset(self.temp_dir_path, "w") - - dataset.createDimension("lat", 4) - dataset.createDimension("lon", 5) - dataset.createDimension("lev", 3) - - latitudes = dataset.createVariable("lat", np.float64, ("lat",)) - longitudes = dataset.createVariable("lon", np.float64, ("lon",)) - levels = dataset.createVariable("lev", np.float64, ("lev",)) - volcello = dataset.createVariable( - "volcello", np.float32, ("lat", "lon", "lev") - ) - - latitudes.standard_name = "latitude" - latitudes.units = "degrees_north" - latitudes.axis = "Y" - latitudes[:] = np.linspace(-90, 90, 4) - - longitudes.standard_name = "longitude" - longitudes.units = "degrees_east" - longitudes.axis = "X" - longitudes[:] = np.linspace(0, 360, 5) - - levels.standard_name = "olevel" - levels.units = "centimeters" - levels.positive = "down" - levels.axis = "Z" - levels[:] = np.linspace(0, 10**5, 3) - - volcello.id = "volcello" - volcello.out_name = "volcello" - volcello.standard_name = "ocean_volume" - volcello.units = "m3" - volcello.realm = "ocean" - volcello.frequency = "fx" - volcello.cell_measures = "area: areacello volume: volcello" - volcello = np.arange(4 * 5 * 3).reshape((4, 5, 3)) - - dataset.close() - - def test_self_referencing_load_issue_3367(self): - # Ensure that reading a file which references itself in - # `cell_measures` can be read. At the same time, ensure that we - # still receive a warning about other variables mentioned in - # `cell_measures` i.e. a warning should be raised about missing - # areacello. - areacello_str = "areacello" - volcello_str = "volcello" - expected_msg = ( - "Missing CF-netCDF measure variable %r, " - "referenced by netCDF variable %r" % (areacello_str, volcello_str) - ) - - with mock.patch("warnings.warn") as warn: - # ensure file loads without failure - cube = iris.load_cube(self.temp_dir_path) - warn.assert_called_with(expected_msg) - - # extra check to ensure correct variable was found - assert cube.standard_name == "ocean_volume" - - def tearDown(self): - os.remove(self.temp_dir_path) - - -class TestCellMethod_unknown(tests.IrisTest): - def test_unknown_method(self): - cube = Cube([1, 2], long_name="odd_phenomenon") - cube.add_cell_method(CellMethod(method="oddity", coords=("x",))) - temp_dirpath = tempfile.mkdtemp() - try: - temp_filepath = os.path.join(temp_dirpath, "tmp.nc") - iris.save(cube, temp_filepath) - with warnings.catch_warnings(record=True) as warning_records: - iris.load(temp_filepath) - # Filter to get the warning we are interested in. - warning_messages = [record.message for record in warning_records] - warning_messages = [ - warn - for warn in warning_messages - if isinstance(warn, UnknownCellMethodWarning) - ] - self.assertEqual(len(warning_messages), 1) - message = warning_messages[0].args[0] - msg = ( - "NetCDF variable 'odd_phenomenon' contains unknown cell " - "method 'oddity'" - ) - self.assertIn(msg, message) - finally: - shutil.rmtree(temp_dirpath) - - -@tests.skip_data -class TestCoordSystem(tests.IrisTest): - def setUp(self): - tlc.setUpModule() - - def tearDown(self): - tlc.tearDownModule() - - def test_load_laea_grid(self): - cube = iris.load_cube( - tests.get_data_path( - ("NetCDF", "lambert_azimuthal_equal_area", "euro_air_temp.nc") - ) - ) - self.assertCML(cube, ("netcdf", "netcdf_laea.cml")) - - datum_cf_var_cdl = """ - netcdf output { - dimensions: - y = 4 ; - x = 3 ; - variables: - float data(y, x) ; - data :standard_name = "toa_brightness_temperature" ; - data :units = "K" ; - data :grid_mapping = "mercator" ; - int mercator ; - mercator:grid_mapping_name = "mercator" ; - mercator:longitude_of_prime_meridian = 0. ; - mercator:earth_radius = 6378169. ; - mercator:horizontal_datum_name = "OSGB36" ; - float y(y) ; - y:axis = "Y" ; - y:units = "m" ; - y:standard_name = "projection_y_coordinate" ; - float x(x) ; - x:axis = "X" ; - x:units = "m" ; - x:standard_name = "projection_x_coordinate" ; - - // global attributes: - :Conventions = "CF-1.7" ; - :standard_name_vocabulary = "CF Standard Name Table v27" ; - - data: - - data = - 0, 1, 2, - 3, 4, 5, - 6, 7, 8, - 9, 10, 11 ; - - mercator = _ ; - - y = 1, 2, 3, 5 ; - - x = -6, -4, -2 ; - - } - """ - - datum_wkt_cdl = """ -netcdf output5 { -dimensions: - y = 4 ; - x = 3 ; -variables: - float data(y, x) ; - data :standard_name = "toa_brightness_temperature" ; - data :units = "K" ; - data :grid_mapping = "mercator" ; - int mercator ; - mercator:grid_mapping_name = "mercator" ; - mercator:longitude_of_prime_meridian = 0. ; - mercator:earth_radius = 6378169. ; - mercator:longitude_of_projection_origin = 0. ; - mercator:false_easting = 0. ; - mercator:false_northing = 0. ; - mercator:scale_factor_at_projection_origin = 1. ; - mercator:crs_wkt = "PROJCRS[\\"unknown\\",BASEGEOGCRS[\\"unknown\\",DATUM[\\"OSGB36\\",ELLIPSOID[\\"unknown\\",6378169,0,LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]],PRIMEM[\\"Greenwich\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8901]]],CONVERSION[\\"unknown\\",METHOD[\\"Mercator (variant B)\\",ID[\\"EPSG\\",9805]],PARAMETER[\\"Latitude of 1st standard parallel\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8823]],PARAMETER[\\"Longitude of natural origin\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8802]],PARAMETER[\\"False easting\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8806]],PARAMETER[\\"False northing\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8807]]],CS[Cartesian,2],AXIS[\\"(E)\\",east,ORDER[1],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]],AXIS[\\"(N)\\",north,ORDER[2],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]]" ; - float y(y) ; - y:axis = "Y" ; - y:units = "m" ; - y:standard_name = "projection_y_coordinate" ; - float x(x) ; - x:axis = "X" ; - x:units = "m" ; - x:standard_name = "projection_x_coordinate" ; - -// global attributes: - :standard_name_vocabulary = "CF Standard Name Table v27" ; - :Conventions = "CF-1.7" ; -data: - - data = - 0, 1, 2, - 3, 4, 5, - 6, 7, 8, - 9, 10, 11 ; - - mercator = _ ; - - y = 1, 2, 3, 5 ; - - x = -6, -4, -2 ; -} - """ - - def test_load_datum_wkt(self): - expected = "OSGB 1936" - nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) - with iris.FUTURE.context(datum_support=True): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(expected, actual) - - def test_no_load_datum_wkt(self): - nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) - with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(actual, "unknown") - - def test_load_datum_cf_var(self): - expected = "OSGB 1936" - nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) - with iris.FUTURE.context(datum_support=True): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(expected, actual) - - def test_no_load_datum_cf_var(self): - nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) - with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(actual, "unknown") - - def test_save_datum(self): - expected = "OSGB 1936" - saved_crs = iris.coord_systems.Mercator( - ellipsoid=iris.coord_systems.GeogCS.from_datum("OSGB36") - ) - - base_cube = stock.realistic_3d() - base_lat_coord = base_cube.coord("grid_latitude") - test_lat_coord = DimCoord( - base_lat_coord.points, - standard_name="projection_y_coordinate", - coord_system=saved_crs, - ) - base_lon_coord = base_cube.coord("grid_longitude") - test_lon_coord = DimCoord( - base_lon_coord.points, - standard_name="projection_x_coordinate", - coord_system=saved_crs, - ) - test_cube = Cube( - base_cube.data, - standard_name=base_cube.standard_name, - units=base_cube.units, - dim_coords_and_dims=( - (base_cube.coord("time"), 0), - (test_lat_coord, 1), - (test_lon_coord, 2), - ), - ) - - with self.temp_filename(suffix=".nc") as filename: - iris.save(test_cube, filename) - with iris.FUTURE.context(datum_support=True): - cube = iris.load_cube(filename) - - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(expected, actual) - - -def _get_scale_factor_add_offset(cube, datatype): - """Utility function used by netCDF data packing tests.""" - if isinstance(datatype, dict): - dt = np.dtype(datatype["dtype"]) - else: - dt = np.dtype(datatype) - cmax = cube.data.max() - cmin = cube.data.min() - n = dt.itemsize * 8 - if ma.isMaskedArray(cube.data): - masked = True - else: - masked = False - if masked: - scale_factor = (cmax - cmin) / (2**n - 2) - else: - scale_factor = (cmax - cmin) / (2**n - 1) - if dt.kind == "u": - add_offset = cmin - elif dt.kind == "i": - if masked: - add_offset = (cmax + cmin) / 2 - else: - add_offset = cmin + 2 ** (n - 1) * scale_factor - return (scale_factor, add_offset) - - -@tests.skip_data -class TestPackedData(tests.IrisTest): - def _single_test(self, datatype, CDLfilename, manual=False): - # Read PP input file. - file_in = tests.get_data_path( - ( - "PP", - "cf_processing", - "000003000000.03.236.000128.1990.12.01.00.00.b.pp", - ) - ) - cube = iris.load_cube(file_in) - scale_factor, offset = _get_scale_factor_add_offset(cube, datatype) - if manual: - packspec = dict( - dtype=datatype, scale_factor=scale_factor, add_offset=offset - ) - else: - packspec = datatype - # Write Cube to netCDF file. - with self.temp_filename(suffix=".nc") as file_out: - iris.save(cube, file_out, packing=packspec) - decimal = int(-np.log10(scale_factor)) - packedcube = iris.load_cube(file_out) - # Check that packed cube is accurate to expected precision - self.assertArrayAlmostEqual( - cube.data, packedcube.data, decimal=decimal - ) - # Check the netCDF file against CDL expected output. - self.assertCDL( - file_out, - ("integration", "netcdf", "TestPackedData", CDLfilename), - ) - - def test_single_packed_signed(self): - """Test saving a single CF-netCDF file with packing.""" - self._single_test("i2", "single_packed_signed.cdl") - - def test_single_packed_unsigned(self): - """Test saving a single CF-netCDF file with packing into unsigned.""" - self._single_test("u1", "single_packed_unsigned.cdl") - - def test_single_packed_manual_scale(self): - """Test saving a single CF-netCDF file with packing with scale - factor and add_offset set manually.""" - self._single_test("i2", "single_packed_manual.cdl", manual=True) - - def _multi_test(self, CDLfilename, multi_dtype=False): - """Test saving multiple packed cubes with pack_dtype list.""" - # Read PP input file. - file_in = tests.get_data_path( - ("PP", "cf_processing", "abcza_pa19591997_daily_29.b.pp") - ) - cubes = iris.load(file_in) - # ensure cube order is the same: - cubes.sort(key=lambda cube: cube.cell_methods[0].method) - datatype = "i2" - scale_factor, offset = _get_scale_factor_add_offset(cubes[0], datatype) - if multi_dtype: - packdict = dict( - dtype=datatype, scale_factor=scale_factor, add_offset=offset - ) - packspec = [packdict, None, "u2"] - dtypes = packspec - else: - packspec = datatype - dtypes = repeat(packspec) - - # Write Cube to netCDF file. - with self.temp_filename(suffix=".nc") as file_out: - iris.save(cubes, file_out, packing=packspec) - # Check the netCDF file against CDL expected output. - self.assertCDL( - file_out, - ("integration", "netcdf", "TestPackedData", CDLfilename), - ) - packedcubes = iris.load(file_out) - packedcubes.sort(key=lambda cube: cube.cell_methods[0].method) - for cube, packedcube, dtype in zip(cubes, packedcubes, dtypes): - if dtype: - sf, ao = _get_scale_factor_add_offset(cube, dtype) - decimal = int(-np.log10(sf)) - # Check that packed cube is accurate to expected precision - self.assertArrayAlmostEqual( - cube.data, packedcube.data, decimal=decimal - ) - else: - self.assertArrayEqual(cube.data, packedcube.data) - - def test_multi_packed_single_dtype(self): - """Test saving multiple packed cubes with the same pack_dtype.""" - # Read PP input file. - self._multi_test("multi_packed_single_dtype.cdl") - - def test_multi_packed_multi_dtype(self): - """Test saving multiple packed cubes with pack_dtype list.""" - # Read PP input file. - self._multi_test("multi_packed_multi_dtype.cdl", multi_dtype=True) - - -class TestScalarCube(tests.IrisTest): - def test_scalar_cube_save_load(self): - cube = iris.cube.Cube(1, long_name="scalar_cube") - with self.temp_filename(suffix=".nc") as fout: - iris.save(cube, fout) - scalar_cube = iris.load_cube(fout) - self.assertEqual(scalar_cube.name(), "scalar_cube") - - -class TestStandardName(tests.IrisTest): - def test_standard_name_roundtrip(self): - standard_name = "air_temperature detection_minimum" - cube = iris.cube.Cube(1, standard_name=standard_name) - with self.temp_filename(suffix=".nc") as fout: - iris.save(cube, fout) - detection_limit_cube = iris.load_cube(fout) - self.assertEqual(detection_limit_cube.standard_name, standard_name) - - -class TestLoadMinimalGeostationary(tests.IrisTest): - """ - Check we can load data with a geostationary grid-mapping, even when the - 'false-easting' and 'false_northing' properties are missing. - - """ - - _geostationary_problem_cdl = """ -netcdf geostationary_problem_case { -dimensions: - y = 2 ; - x = 3 ; -variables: - short radiance(y, x) ; - radiance:standard_name = "toa_outgoing_radiance_per_unit_wavelength" ; - radiance:units = "W m-2 sr-1 um-1" ; - radiance:coordinates = "y x" ; - radiance:grid_mapping = "imager_grid_mapping" ; - short y(y) ; - y:units = "rad" ; - y:axis = "Y" ; - y:long_name = "fixed grid projection y-coordinate" ; - y:standard_name = "projection_y_coordinate" ; - short x(x) ; - x:units = "rad" ; - x:axis = "X" ; - x:long_name = "fixed grid projection x-coordinate" ; - x:standard_name = "projection_x_coordinate" ; - int imager_grid_mapping ; - imager_grid_mapping:grid_mapping_name = "geostationary" ; - imager_grid_mapping:perspective_point_height = 35786023. ; - imager_grid_mapping:semi_major_axis = 6378137. ; - imager_grid_mapping:semi_minor_axis = 6356752.31414 ; - imager_grid_mapping:latitude_of_projection_origin = 0. ; - imager_grid_mapping:longitude_of_projection_origin = -75. ; - imager_grid_mapping:sweep_angle_axis = "x" ; - -data: - - // coord values, just so these can be dim-coords - y = 0, 1 ; - x = 0, 1, 2 ; - -} -""" - - @classmethod - def setUpClass(cls): - # Create a temp directory for transient test files. - cls.temp_dir = tempfile.mkdtemp() - cls.path_test_cdl = path_join(cls.temp_dir, "geos_problem.cdl") - cls.path_test_nc = path_join(cls.temp_dir, "geos_problem.nc") - # Create reference CDL and netcdf files from the CDL text. - ncgen_from_cdl( - cdl_str=cls._geostationary_problem_cdl, - cdl_path=cls.path_test_cdl, - nc_path=cls.path_test_nc, - ) - - @classmethod - def tearDownClass(cls): - # Destroy the temp directory. - shutil.rmtree(cls.temp_dir) - - def test_geostationary_no_false_offsets(self): - # Check we can load the test data and coordinate system properties are correct. - cube = iris.load_cube(self.path_test_nc) - # Check the coordinate system properties has the correct default properties. - cs = cube.coord_system() - self.assertIsInstance(cs, iris.coord_systems.Geostationary) - self.assertEqual(cs.false_easting, 0.0) - self.assertEqual(cs.false_northing, 0.0) - - -@tests.skip_data -class TestConstrainedLoad(tests.IrisTest): - filename = tests.get_data_path( - ("NetCDF", "label_and_climate", "A1B-99999a-river-sep-2070-2099.nc") - ) - - def test_netcdf_with_NameConstraint(self): - constr = iris.NameConstraint(var_name="cdf_temp_dmax_tmean_abs") - cubes = iris.load(self.filename, constr) - self.assertEqual(len(cubes), 1) - self.assertEqual(cubes[0].var_name, "cdf_temp_dmax_tmean_abs") - - def test_netcdf_with_no_constraint(self): - cubes = iris.load(self.filename) - self.assertEqual(len(cubes), 3) - - -class TestSkippedCoord: - # If a coord/cell measure/etcetera cannot be added to the loaded Cube, a - # Warning is raised and the coord is skipped. - # This 'catching' is generic to all CannotAddErrors, but currently the only - # such problem that can exist in a NetCDF file is a mismatch of dimensions - # between phenomenon and coord. - - cdl_core = """ -dimensions: - length_scale = 1 ; - lat = 3 ; -variables: - float lat(lat) ; - lat:standard_name = "latitude" ; - lat:units = "degrees_north" ; - short lst_unc_sys(length_scale) ; - lst_unc_sys:long_name = "uncertainty from large-scale systematic - errors" ; - lst_unc_sys:units = "kelvin" ; - lst_unc_sys:coordinates = "lat" ; - -data: - lat = 0, 1, 2; - """ - - @pytest.fixture(autouse=True) - def create_nc_file(self, tmp_path): - file_name = "dim_mismatch" - cdl = f"netcdf {file_name}" + "{\n" + self.cdl_core + "\n}" - self.nc_path = (tmp_path / file_name).with_suffix(".nc") - ncgen_from_cdl( - cdl_str=cdl, - cdl_path=None, - nc_path=str(self.nc_path), - ) - yield - self.nc_path.unlink() - - def test_lat_not_loaded(self): - # iris#5068 includes discussion of possible retention of the skipped - # coords in the future. - with pytest.warns( - match="Missing data dimensions for multi-valued DimCoord" - ): - cube = iris.load_cube(self.nc_path) - with pytest.raises(iris.exceptions.CoordinateNotFoundError): - _ = cube.coord("lat") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/results/integration/netcdf/TestUmVersionAttribute/multiple_different_saves_on_variables.cdl b/lib/iris/tests/results/integration/netcdf/attributes/TestUmVersionAttribute/multiple_different_saves_on_variables.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestUmVersionAttribute/multiple_different_saves_on_variables.cdl rename to lib/iris/tests/results/integration/netcdf/attributes/TestUmVersionAttribute/multiple_different_saves_on_variables.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestUmVersionAttribute/multiple_same_saves_as_global.cdl b/lib/iris/tests/results/integration/netcdf/attributes/TestUmVersionAttribute/multiple_same_saves_as_global.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestUmVersionAttribute/multiple_same_saves_as_global.cdl rename to lib/iris/tests/results/integration/netcdf/attributes/TestUmVersionAttribute/multiple_same_saves_as_global.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestUmVersionAttribute/single_saves_as_global.cdl b/lib/iris/tests/results/integration/netcdf/attributes/TestUmVersionAttribute/single_saves_as_global.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestUmVersionAttribute/single_saves_as_global.cdl rename to lib/iris/tests/results/integration/netcdf/attributes/TestUmVersionAttribute/single_saves_as_global.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestAtmosphereSigma/save.cdl b/lib/iris/tests/results/integration/netcdf/aux_factories/TestAtmosphereSigma/save.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestAtmosphereSigma/save.cdl rename to lib/iris/tests/results/integration/netcdf/aux_factories/TestAtmosphereSigma/save.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestHybridPressure/save.cdl b/lib/iris/tests/results/integration/netcdf/aux_factories/TestHybridPressure/save.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestHybridPressure/save.cdl rename to lib/iris/tests/results/integration/netcdf/aux_factories/TestHybridPressure/save.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl b/lib/iris/tests/results/integration/netcdf/aux_factories/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl rename to lib/iris/tests/results/integration/netcdf/aux_factories/TestSaveMultipleAuxFactories/hybrid_height_and_pressure.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml b/lib/iris/tests/results/integration/netcdf/aux_factories/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml rename to lib/iris/tests/results/integration/netcdf/aux_factories/TestSaveMultipleAuxFactories/hybrid_height_cubes.cml diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/multi_packed_multi_dtype.cdl b/lib/iris/tests/results/integration/netcdf/general/TestPackedData/multi_packed_multi_dtype.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestPackedData/multi_packed_multi_dtype.cdl rename to lib/iris/tests/results/integration/netcdf/general/TestPackedData/multi_packed_multi_dtype.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/multi_packed_single_dtype.cdl b/lib/iris/tests/results/integration/netcdf/general/TestPackedData/multi_packed_single_dtype.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestPackedData/multi_packed_single_dtype.cdl rename to lib/iris/tests/results/integration/netcdf/general/TestPackedData/multi_packed_single_dtype.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_manual.cdl b/lib/iris/tests/results/integration/netcdf/general/TestPackedData/single_packed_manual.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_manual.cdl rename to lib/iris/tests/results/integration/netcdf/general/TestPackedData/single_packed_manual.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_signed.cdl b/lib/iris/tests/results/integration/netcdf/general/TestPackedData/single_packed_signed.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_signed.cdl rename to lib/iris/tests/results/integration/netcdf/general/TestPackedData/single_packed_signed.cdl diff --git a/lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_unsigned.cdl b/lib/iris/tests/results/integration/netcdf/general/TestPackedData/single_packed_unsigned.cdl similarity index 100% rename from lib/iris/tests/results/integration/netcdf/TestPackedData/single_packed_unsigned.cdl rename to lib/iris/tests/results/integration/netcdf/general/TestPackedData/single_packed_unsigned.cdl diff --git a/lib/iris/tests/stock/netcdf.py b/lib/iris/tests/stock/netcdf.py index e32f065625..bf93f01f6b 100644 --- a/lib/iris/tests/stock/netcdf.py +++ b/lib/iris/tests/stock/netcdf.py @@ -12,9 +12,9 @@ import dask from dask import array as da -import netCDF4 import numpy as np +from iris.fileformats.netcdf import _thread_safe_nc from iris.tests import env_bin_path NCGEN_PATHSTR = str(env_bin_path("ncgen")) @@ -100,7 +100,7 @@ def _add_standard_data(nc_path, unlimited_dim_size=0): """ - ds = netCDF4.Dataset(nc_path, "r+") + ds = _thread_safe_nc.DatasetWrapper(nc_path, "r+") unlimited_dim_names = [ dim for dim in ds.dimensions if ds.dimensions[dim].isunlimited() diff --git a/lib/iris/tests/test_cf.py b/lib/iris/tests/test_cf.py index bf3cddb8b7..3abd6b981b 100644 --- a/lib/iris/tests/test_cf.py +++ b/lib/iris/tests/test_cf.py @@ -15,6 +15,8 @@ import io from unittest import mock +import pytest + import iris import iris.fileformats.cf as cf @@ -52,11 +54,14 @@ def test_cached(self): @tests.skip_data class TestCFReader(tests.IrisTest): - def setUp(self): + @pytest.fixture(autouse=True) + def set_up(self): filename = tests.get_data_path( ("NetCDF", "rotated", "xyt", "small_rotPole_precipitation.nc") ) self.cfr = cf.CFReader(filename) + with self.cfr: + yield def test_ancillary_variables_pass_0(self): self.assertEqual(self.cfr.cf_group.ancillary_variables, {}) @@ -348,7 +353,8 @@ def test_cell_methods(self): @tests.skip_data class TestClimatology(tests.IrisTest): - def setUp(self): + @pytest.fixture(autouse=True) + def set_up(self): filename = tests.get_data_path( ( "NetCDF", @@ -357,6 +363,8 @@ def setUp(self): ) ) self.cfr = cf.CFReader(filename) + with self.cfr: + yield def test_bounds(self): time = self.cfr.cf_group["temp_dmax_tmean_abs"].cf_group.coordinates[ @@ -373,7 +381,8 @@ def test_bounds(self): @tests.skip_data class TestLabels(tests.IrisTest): - def setUp(self): + @pytest.fixture(autouse=True) + def set_up(self): filename = tests.get_data_path( ( "NetCDF", @@ -388,6 +397,10 @@ def setUp(self): ) self.cfr_end = cf.CFReader(filename) + with self.cfr_start: + with self.cfr_end: + yield + def test_label_dim_start(self): cf_data_var = self.cfr_start.cf_group["temp_dmax_tmean_abs"] diff --git a/lib/iris/tests/test_coding_standards.py b/lib/iris/tests/test_coding_standards.py index 01f6f777fa..b52934c568 100644 --- a/lib/iris/tests/test_coding_standards.py +++ b/lib/iris/tests/test_coding_standards.py @@ -12,9 +12,12 @@ from fnmatch import fnmatch from glob import glob import os +from pathlib import Path import subprocess import iris +from iris.fileformats.netcdf import _thread_safe_nc +from iris.tests import system_test LICENSE_TEMPLATE = """# Copyright Iris contributors # @@ -40,6 +43,29 @@ IRIS_REPO_DIRPATH = os.environ.get("IRIS_REPO_DIR", IRIS_INSTALL_DIR) +def test_netcdf4_import(): + """Use of netCDF4 must be via iris.fileformats.netcdf._thread_safe_nc .""" + # Please avoid including these phrases in any comments/strings throughout + # Iris (e.g. use "from the netCDF4 library" instead) - this allows the + # below search to remain quick and simple. + import_strings = ("import netCDF4", "from netCDF4") + + files_including_import = [] + for file_path in Path(IRIS_DIR).rglob("*.py"): + with file_path.open("r") as open_file: + file_text = open_file.read() + + if any([i in file_text for i in import_strings]): + files_including_import.append(file_path) + + expected = [ + Path(_thread_safe_nc.__file__), + Path(system_test.__file__), + Path(__file__), + ] + assert set(files_including_import) == set(expected) + + class TestLicenseHeaders(tests.IrisTest): @staticmethod def whatchanged_parse(whatchanged_output): diff --git a/lib/iris/tests/test_load.py b/lib/iris/tests/test_load.py index 4749236abc..adb33924e5 100644 --- a/lib/iris/tests/test_load.py +++ b/lib/iris/tests/test_load.py @@ -14,9 +14,8 @@ import pathlib from unittest import mock -import netCDF4 - import iris +from iris.fileformats.netcdf import _thread_safe_nc import iris.io @@ -193,10 +192,11 @@ def test_netCDF_Dataset_call(self): filename = tests.get_data_path( ("NetCDF", "global", "xyt", "SMALL_total_column_co2.nc") ) - fake_dataset = netCDF4.Dataset(filename) + fake_dataset = _thread_safe_nc.DatasetWrapper(filename) with mock.patch( - "netCDF4.Dataset", return_value=fake_dataset + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=fake_dataset, ) as dataset_loader: next(iris.io.load_http([self.url], callback=None)) dataset_loader.assert_called_with(self.url, mode="r") diff --git a/lib/iris/tests/test_netcdf.py b/lib/iris/tests/test_netcdf.py index 92e15a414a..d182de84f6 100644 --- a/lib/iris/tests/test_netcdf.py +++ b/lib/iris/tests/test_netcdf.py @@ -19,7 +19,6 @@ import tempfile from unittest import mock -import netCDF4 as nc import numpy as np import numpy.ma as ma @@ -29,6 +28,7 @@ import iris.coord_systems as icoord_systems from iris.fileformats._nc_load_rules import helpers as ncload_helpers import iris.fileformats.netcdf +from iris.fileformats.netcdf import _thread_safe_nc from iris.fileformats.netcdf import load_cubes as nc_load_cubes import iris.std_names import iris.tests.stock as stock @@ -81,7 +81,7 @@ def test_missing_time_bounds(self): ("NetCDF", "global", "xyt", "SMALL_hires_wind_u_for_ipcc4.nc") ) shutil.copyfile(src, filename) - dataset = nc.Dataset(filename, mode="a") + dataset = _thread_safe_nc.DatasetWrapper(filename, mode="a") dataset.renameVariable("time_bnds", "foo") dataset.close() _ = iris.load_cube(filename, "eastward_wind") @@ -204,7 +204,7 @@ def test_missing_climatology(self): ("NetCDF", "transverse_mercator", "tmean_1910_1910.nc") ) shutil.copyfile(src, filename) - dataset = nc.Dataset(filename, mode="a") + dataset = _thread_safe_nc.DatasetWrapper(filename, mode="a") dataset.renameVariable("climatology_bounds", "foo") dataset.close() _ = iris.load_cube(filename, "Mean temperature") @@ -632,7 +632,7 @@ def test_netcdf_save_format(self): with self.temp_filename(suffix=".nc") as file_out: # Test default NETCDF4 file format saving. iris.save(cube, file_out) - ds = nc.Dataset(file_out) + ds = _thread_safe_nc.DatasetWrapper(file_out) self.assertEqual( ds.file_format, "NETCDF4", "Failed to save as NETCDF4 format" ) @@ -640,7 +640,7 @@ def test_netcdf_save_format(self): # Test NETCDF4_CLASSIC file format saving. iris.save(cube, file_out, netcdf_format="NETCDF4_CLASSIC") - ds = nc.Dataset(file_out) + ds = _thread_safe_nc.DatasetWrapper(file_out) self.assertEqual( ds.file_format, "NETCDF4_CLASSIC", @@ -650,7 +650,7 @@ def test_netcdf_save_format(self): # Test NETCDF3_CLASSIC file format saving. iris.save(cube, file_out, netcdf_format="NETCDF3_CLASSIC") - ds = nc.Dataset(file_out) + ds = _thread_safe_nc.DatasetWrapper(file_out) self.assertEqual( ds.file_format, "NETCDF3_CLASSIC", @@ -660,7 +660,7 @@ def test_netcdf_save_format(self): # Test NETCDF4_64BIT file format saving. iris.save(cube, file_out, netcdf_format="NETCDF3_64BIT") - ds = nc.Dataset(file_out) + ds = _thread_safe_nc.DatasetWrapper(file_out) self.assertTrue( ds.file_format in ["NETCDF3_64BIT", "NETCDF3_64BIT_OFFSET"], "Failed to save as NETCDF3_64BIT format", @@ -1047,7 +1047,7 @@ def test_attributes(self): with self.temp_filename(suffix=".nc") as filename: iris.save(self.cube, filename) # Load the dataset. - ds = nc.Dataset(filename, "r") + ds = _thread_safe_nc.DatasetWrapper(filename, "r") exceptions = [] # Should be global attributes. for gkey in aglobals: @@ -1211,7 +1211,7 @@ def test_shared(self): self.assertCDL(filename) # Also check that only one, shared ancillary variable was written. - ds = nc.Dataset(filename) + ds = _thread_safe_nc.DatasetWrapper(filename) self.assertIn("air_potential_temperature", ds.variables) self.assertIn("alternate_data", ds.variables) self.assertEqual( diff --git a/lib/iris/tests/test_pp_cf.py b/lib/iris/tests/test_pp_cf.py index 2b497cb53b..49bedaf1e2 100644 --- a/lib/iris/tests/test_pp_cf.py +++ b/lib/iris/tests/test_pp_cf.py @@ -10,10 +10,9 @@ import os import tempfile -import netCDF4 - import iris import iris.coords +from iris.fileformats.netcdf import _thread_safe_nc from iris.fileformats.pp import STASH import iris.tests.pp as pp import iris.util @@ -95,7 +94,7 @@ def _test_file(self, name): for index, cube in enumerate(cubes): # Explicitly set a fill-value as a workaround for # https://github.com/Unidata/netcdf4-python/issues/725 - fill_value = netCDF4.default_fillvals[cube.dtype.str[1:]] + fill_value = _thread_safe_nc.default_fillvals[cube.dtype.str[1:]] file_nc = tempfile.NamedTemporaryFile( suffix=".nc", delete=False diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py index e44aee730a..d9de814b05 100644 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py +++ b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py @@ -94,7 +94,10 @@ def setUp(self): # Restrict the CFUGridReader functionality to only performing # translations and building first level cf-groups for variables. self.patch("iris.experimental.ugrid.cf.CFUGridReader._reset") - self.patch("netCDF4.Dataset", return_value=self.dataset) + self.patch( + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, + ) cf_reader = CFUGridReader("dummy") self.cf_group = cf_reader.cf_group diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py index dee28e98cc..9e5cf9b7a5 100644 --- a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py +++ b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py @@ -70,7 +70,10 @@ def setUp(self): ) def test_create_global_attributes(self): - with mock.patch("netCDF4.Dataset", return_value=self.dataset): + with mock.patch( + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, + ): global_attrs = CFReader("dummy").cf_group.global_attributes self.assertEqual( global_attrs["dimensions"], "something something_else" @@ -145,7 +148,10 @@ def setUp(self): self.addCleanup(reset_patch.stop) def test_create_formula_terms(self): - with mock.patch("netCDF4.Dataset", return_value=self.dataset): + with mock.patch( + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, + ): cf_group = CFReader("dummy").cf_group self.assertEqual(len(cf_group), len(self.variables)) # Check there is a singular data variable. @@ -247,7 +253,10 @@ def setUp(self): self.addCleanup(patcher.stop) def test_associate_formula_terms_with_data_variable(self): - with mock.patch("netCDF4.Dataset", return_value=self.dataset): + with mock.patch( + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, + ): cf_group = CFReader("dummy").cf_group self.assertEqual(len(cf_group), len(self.variables)) # Check the cf-group associated with the data variable. @@ -296,7 +305,10 @@ def test_associate_formula_terms_with_data_variable(self): ) def test_promote_reference(self): - with mock.patch("netCDF4.Dataset", return_value=self.dataset): + with mock.patch( + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, + ): cf_group = CFReader("dummy").cf_group self.assertEqual(len(cf_group), len(self.variables)) # Check the number of data variables. @@ -316,7 +328,8 @@ def test_promote_reference(self): def test_formula_terms_ignore(self): self.orography.dimensions = ["lat", "wibble"] with mock.patch( - "netCDF4.Dataset", return_value=self.dataset + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, ), mock.patch("warnings.warn") as warn: cf_group = CFReader("dummy").cf_group group = cf_group.promoted @@ -327,7 +340,8 @@ def test_formula_terms_ignore(self): def test_auxiliary_ignore(self): self.x.dimensions = ["lat", "wibble"] with mock.patch( - "netCDF4.Dataset", return_value=self.dataset + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, ), mock.patch("warnings.warn") as warn: cf_group = CFReader("dummy").cf_group promoted = ["x", "orography"] @@ -342,7 +356,8 @@ def test_promoted_auxiliary_ignore(self): self.variables["wibble"] = self.wibble self.orography.coordinates = "wibble" with mock.patch( - "netCDF4.Dataset", return_value=self.dataset + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", + return_value=self.dataset, ), mock.patch("warnings.warn") as warn: cf_group = CFReader("dummy").cf_group.promoted promoted = ["wibble", "orography"] diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py index 0cc3d09426..399a987f11 100644 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py +++ b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py @@ -80,42 +80,44 @@ def load_cube_from_cdl(self, cdl_string, cdl_path, nc_path): # Simulate the inner part of the file reading process. cf = CFReader(nc_path) - # Grab a data variable : FOR NOW always grab the 'phenom' variable. - cf_var = cf.cf_group.data_variables["phenom"] - - engine = iris.fileformats.netcdf.loader._actions_engine() - - # If debug enabled, switch on the activation summary debug output. - # Use 'patch' so it is restored after the test. - self.patch("iris.fileformats.netcdf.loader.DEBUG", self.debug) - - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Ignoring a datum in netCDF load for consistency with existing " - "behaviour. In a future version of Iris, this datum will be " - "applied. To apply the datum when loading, use the " - "iris.FUTURE.datum_support flag.", - category=FutureWarning, - ) - # Call the main translation function to load a single cube. - # _load_cube establishes per-cube facts, activates rules and - # produces an actual cube. - cube = _load_cube(engine, cf, cf_var, nc_path) - - # Also Record, on the cubes, which hybrid coord elements were identified - # by the rules operation. - # Unlike the other translations, _load_cube does *not* convert this - # information into actual cube elements. That is instead done by - # `iris.fileformats.netcdf._load_aux_factory`. - # For rules testing, it is anyway more convenient to deal with the raw - # data, as each factory type has different validity requirements to - # build it, and none of that is relevant to the rules operation. - cube._formula_type_name = engine.requires.get("formula_type") - cube._formula_terms_byname = engine.requires.get("formula_terms") - - # Always returns a single cube. - return cube + + with cf: + # Grab a data variable : FOR NOW always grab the 'phenom' variable. + cf_var = cf.cf_group.data_variables["phenom"] + + engine = iris.fileformats.netcdf.loader._actions_engine() + + # If debug enabled, switch on the activation summary debug output. + # Use 'patch' so it is restored after the test. + self.patch("iris.fileformats.netcdf.loader.DEBUG", self.debug) + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message="Ignoring a datum in netCDF load for consistency with existing " + "behaviour. In a future version of Iris, this datum will be " + "applied. To apply the datum when loading, use the " + "iris.FUTURE.datum_support flag.", + category=FutureWarning, + ) + # Call the main translation function to load a single cube. + # _load_cube establishes per-cube facts, activates rules and + # produces an actual cube. + cube = _load_cube(engine, cf, cf_var, nc_path) + + # Also Record, on the cubes, which hybrid coord elements were identified + # by the rules operation. + # Unlike the other translations, _load_cube does *not* convert this + # information into actual cube elements. That is instead done by + # `iris.fileformats.netcdf._load_aux_factory`. + # For rules testing, it is anyway more convenient to deal with the raw + # data, as each factory type has different validity requirements to + # build it, and none of that is relevant to the rules operation. + cube._formula_type_name = engine.requires.get("formula_type") + cube._formula_terms_byname = engine.requires.get("formula_terms") + + # Always returns a single cube. + return cube def run_testcase(self, warning_regex=None, **testcase_kwargs): """ diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py index 174a46fdb7..6fa9e9e096 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py @@ -13,7 +13,6 @@ from contextlib import contextmanager from unittest import mock -import netCDF4 as nc import numpy as np from numpy import ma @@ -32,7 +31,7 @@ ) from iris.coords import AuxCoord, DimCoord from iris.cube import Cube -from iris.fileformats.netcdf import Saver +from iris.fileformats.netcdf import Saver, _thread_safe_nc import iris.tests.stock as stock @@ -203,12 +202,12 @@ def test_big_endian(self): def test_zlib(self): cube = self._simple_cube(">f4") - api = self.patch("iris.fileformats.netcdf.saver.netCDF4") + api = self.patch("iris.fileformats.netcdf.saver._thread_safe_nc") # Define mocked default fill values to prevent deprecation warning (#4374). api.default_fillvals = collections.defaultdict(lambda: -99.0) with Saver("/dummy/path", "NETCDF4") as saver: saver.write(cube, zlib=True) - dataset = api.Dataset.return_value + dataset = api.DatasetWrapper.return_value create_var_call = mock.call( "air_pressure_anomaly", np.dtype("float32"), @@ -249,7 +248,7 @@ def test_default_unlimited_dimensions(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) self.assertFalse(ds.dimensions["dim0"].isunlimited()) self.assertFalse(ds.dimensions["dim1"].isunlimited()) ds.close() @@ -259,7 +258,7 @@ def test_no_unlimited_dimensions(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=None) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) for dim in ds.dimensions.values(): self.assertFalse(dim.isunlimited()) ds.close() @@ -281,7 +280,7 @@ def test_custom_unlimited_dimensions(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=unlimited_dimensions) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) for dim in unlimited_dimensions: self.assertTrue(ds.dimensions[dim].isunlimited()) ds.close() @@ -290,7 +289,7 @@ def test_custom_unlimited_dimensions(self): coords = [cube.coord(dim) for dim in unlimited_dimensions] with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=coords) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) for dim in unlimited_dimensions: self.assertTrue(ds.dimensions[dim].isunlimited()) ds.close() @@ -301,7 +300,7 @@ def test_reserved_attributes(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) res = ds.getncattr("dimensions") ds.close() self.assertEqual(res, "something something_else") @@ -323,7 +322,7 @@ def test_dimensional_to_scalar(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) # Confirm that the only dimension is the one denoting the number # of bounds - have successfully saved the 2D bounds array into 1D. self.assertEqual(["bnds"], list(ds.dimensions.keys())) @@ -363,7 +362,7 @@ def _check_bounds_setting(self, climatological=False): saver._ensure_valid_dtype.return_value = mock.Mock( shape=coord.bounds.shape, dtype=coord.bounds.dtype ) - var = mock.MagicMock(spec=nc.Variable) + var = mock.MagicMock(spec=_thread_safe_nc.VariableWrapper) # Make the main call. Saver._create_cf_bounds(saver, coord, var, "time") @@ -404,7 +403,7 @@ def test_valid_range_saved(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=[]) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) self.assertArrayEqual(ds.valid_range, vrange) ds.close() @@ -416,7 +415,7 @@ def test_valid_min_saved(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=[]) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) self.assertArrayEqual(ds.valid_min, 1) ds.close() @@ -428,7 +427,7 @@ def test_valid_max_saved(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=[]) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) self.assertArrayEqual(ds.valid_max, 2) ds.close() @@ -448,7 +447,7 @@ def test_valid_range_saved(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=[]) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) self.assertArrayEqual( ds.variables["longitude"].valid_range, vrange ) @@ -462,7 +461,7 @@ def test_valid_min_saved(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=[]) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) self.assertArrayEqual(ds.variables["longitude"].valid_min, 1) ds.close() @@ -474,7 +473,7 @@ def test_valid_max_saved(self): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, unlimited_dimensions=[]) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) self.assertArrayEqual(ds.variables["longitude"].valid_max, 2) ds.close() @@ -506,7 +505,7 @@ def _netCDF_var(self, cube, **kwargs): with self.temp_filename(".nc") as nc_path: with Saver(nc_path, "NETCDF4") as saver: saver.write(cube, **kwargs) - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) (var,) = [ var for var in ds.variables.values() @@ -572,7 +571,7 @@ def test_contains_default_fill_value(self): # Test that a warning is raised if the data contains the default fill # value if no fill_value argument is supplied. cube = self._make_cube(">f4") - cube.data[0, 0] = nc.default_fillvals["f4"] + cube.data[0, 0] = _thread_safe_nc.default_fillvals["f4"] with self.assertWarnsRegex( UserWarning, "contains unmasked data points equal to the fill-value", @@ -647,7 +646,9 @@ def setUp(self): self.container = mock.Mock(name="container", attributes={}) self.data_dtype = np.dtype("int32") - patch = mock.patch("netCDF4.Dataset") + patch = mock.patch( + "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper" + ) _ = patch.start() self.addCleanup(patch.stop) diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py index 18e86a9f57..323b498d9c 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py @@ -18,7 +18,6 @@ import shutil import tempfile -import netCDF4 as nc import numpy as np from iris import save @@ -26,6 +25,7 @@ from iris.cube import Cube, CubeList from iris.experimental.ugrid.mesh import Connectivity, Mesh from iris.experimental.ugrid.save import save_mesh +from iris.fileformats.netcdf import _thread_safe_nc from iris.tests.stock import realistic_4d XY_LOCS = ("x", "y") @@ -259,7 +259,7 @@ def scan_dataset(filepath): variable's dims. """ - ds = nc.Dataset(filepath) + ds = _thread_safe_nc.DatasetWrapper(filepath) # dims dict is {name: len} dimsdict = {name: dim.size for name, dim in ds.dimensions.items()} # vars dict is {name: {attr:val}} @@ -824,7 +824,7 @@ def test_nonuniform_connectivity(self): self.assertNotIn("_FillValue", fn_props) # For what it's worth, *also* check the actual data array in the file - ds = nc.Dataset(tempfile_path) + ds = _thread_safe_nc.DatasetWrapper(tempfile_path) conn_var = ds.variables[ff_conn_name] data = conn_var[:] ds.close() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_save.py b/lib/iris/tests/unit/fileformats/netcdf/test_save.py index 030edbfce2..b274a8be0d 100644 --- a/lib/iris/tests/unit/fileformats/netcdf/test_save.py +++ b/lib/iris/tests/unit/fileformats/netcdf/test_save.py @@ -14,14 +14,17 @@ from tempfile import mkdtemp from unittest import mock -import netCDF4 as nc import numpy as np import iris from iris.coords import AuxCoord, DimCoord from iris.cube import Cube, CubeList from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD -from iris.fileformats.netcdf import CF_CONVENTIONS_VERSION, save +from iris.fileformats.netcdf import ( + CF_CONVENTIONS_VERSION, + _thread_safe_nc, + save, +) from iris.tests.stock import lat_lon_cube from iris.tests.stock.mesh import sample_mesh_cube @@ -38,7 +41,7 @@ def test_custom_conventions__ignored(self): # CF convention. with self.temp_filename(".nc") as nc_path: save(self.cube, nc_path, "NETCDF4") - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) res = ds.getncattr("Conventions") ds.close() self.assertEqual(res, CF_CONVENTIONS_VERSION) @@ -49,7 +52,7 @@ def test_custom_conventions__allowed(self): with mock.patch.object(self.options, "conventions_override", True): with self.temp_filename(".nc") as nc_path: save(self.cube, nc_path, "NETCDF4") - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) res = ds.getncattr("Conventions") ds.close() self.assertEqual(res, self.custom_conventions) @@ -61,7 +64,7 @@ def test_custom_conventions__allowed__missing(self): with mock.patch.object(self.options, "conventions_override", True): with self.temp_filename(".nc") as nc_path: save(self.cube, nc_path, "NETCDF4") - ds = nc.Dataset(nc_path) + ds = _thread_safe_nc.DatasetWrapper(nc_path) res = ds.getncattr("Conventions") ds.close() self.assertEqual(res, CF_CONVENTIONS_VERSION) @@ -76,7 +79,7 @@ def test_attributes_arrays(self): with self.temp_filename("foo.nc") as nc_out: save([c1, c2], nc_out) - ds = nc.Dataset(nc_out) + ds = _thread_safe_nc.DatasetWrapper(nc_out) res = ds.getncattr("bar") ds.close() self.assertArrayEqual(res, np.arange(2)) @@ -92,7 +95,7 @@ def test_no_special_attribute_clash(self): with self.temp_filename("foo.nc") as nc_out: save([c1, c2], nc_out) - ds = nc.Dataset(nc_out) + ds = _thread_safe_nc.DatasetWrapper(nc_out) res = ds.variables["test"].getncattr("name") res_1 = ds.variables["test_1"].getncattr("name") ds.close() @@ -105,7 +108,7 @@ def test_no_unlimited_dims(self): cube = lat_lon_cube() with self.temp_filename("foo.nc") as nc_out: save(cube, nc_out) - ds = nc.Dataset(nc_out) + ds = _thread_safe_nc.DatasetWrapper(nc_out) self.assertFalse(ds.dimensions["latitude"].isunlimited()) def test_unlimited_dim_latitude(self): @@ -113,7 +116,7 @@ def test_unlimited_dim_latitude(self): unlim_dim_name = "latitude" with self.temp_filename("foo.nc") as nc_out: save(cube, nc_out, unlimited_dimensions=[unlim_dim_name]) - ds = nc.Dataset(nc_out) + ds = _thread_safe_nc.DatasetWrapper(nc_out) self.assertTrue(ds.dimensions[unlim_dim_name].isunlimited()) diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 910a390493..9dd269eff6 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 234b47d943728b5abe70fba0fd74c6adc10e4f1e2a14b919344f8a693b5b3e6f +# input_hash: b3eba68adec85dc6750f8775b6e48189678a75c3baa3dc3f6ed9b9351137fe50 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 @@ -191,7 +191,7 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.14.0-pyhd8ed1ab_0.conda#01ea04980fa39d7b6dbdd6c67016d177 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 @@ -242,7 +242,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771 https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py310h0a86a1f_103.conda#7f69695b684f2595d9ba1ce26d693b7d +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.2-nompi_py310h55e1e36_100.tar.bz2#4dd7aa28fb7d9a6de061c9579a30e7dd https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 @@ -267,3 +267,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py310hcb7e713_0.conda#bd14eaad9bbf54b78e48ecb8b644fcf6 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a + diff --git a/requirements/ci/nox.lock/py38-linux-64.lock b/requirements/ci/nox.lock/py38-linux-64.lock index e87b21a994..3e3349cb4b 100644 --- a/requirements/ci/nox.lock/py38-linux-64.lock +++ b/requirements/ci/nox.lock/py38-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: 0543fd9bbb31e9f896ccf547f3b155d68bb748634268c28dde6ff3ac77aa74d3 +# input_hash: fb647c05bdf2998763af9a184ece4f66796aff1cff2ae207f504c94e6062acaf @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 @@ -132,7 +132,7 @@ https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py38h578d9bd_3.tar.bz2#a7866449fb9e5e4008a02df276549d34 +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py38h578d9bd_3.tar.bz2#34e1f12e3ed15aff218644e9d865b722 https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 @@ -190,7 +190,7 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.14.0-pyhd8ed1ab_0.conda#01ea04980fa39d7b6dbdd6c67016d177 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 @@ -242,7 +242,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py38hd6c3c57_0.conda#dd63f6486ba95c036b6bfe0b5c53d875 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py38h6b4b75c_103.conda#ea3d2204fc3a7db7d831daa437a58717 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.2-nompi_py38h2250339_100.tar.bz2#dd97e93b1f64f1cc58879d53c23ec93f https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 @@ -268,3 +268,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py38h3d2c718_0.conda#55ba6e3a49c4293302262286a49607d8 https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a + diff --git a/requirements/ci/nox.lock/py39-linux-64.lock b/requirements/ci/nox.lock/py39-linux-64.lock index f2eb79bc0a..c58911fb63 100644 --- a/requirements/ci/nox.lock/py39-linux-64.lock +++ b/requirements/ci/nox.lock/py39-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: de178c2d53980747bafc10c4a4387eeb8c700311af7b35a2fcb49f1b441b960b +# input_hash: 23dff964b0b7254aa6b68bd471a7276f62e9eaa86280f550ef4f34a2022201e0 @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 @@ -133,7 +133,7 @@ https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_0.tar.bz https://conda.anaconda.org/conda-forge/noarch/cycler-0.11.0-pyhd8ed1ab_0.tar.bz2#a50559fad0affdbb33729a68669ca1cb https://conda.anaconda.org/conda-forge/linux-64/dbus-1.13.6-h5008d03_3.tar.bz2#ecfff944ba3960ecb334b9a2663d708d https://conda.anaconda.org/conda-forge/noarch/distlib-0.3.6-pyhd8ed1ab_0.tar.bz2#b65b4d50dbd2d50fa0aeac367ec9eed7 -https://conda.anaconda.org/conda-forge/linux-64/docutils-0.16-py39hf3d152e_3.tar.bz2#4f0fa7459a1f40a969aaad418b1c428c +https://conda.anaconda.org/conda-forge/linux-64/docutils-0.17.1-py39hf3d152e_3.tar.bz2#3caf51fb6a259d377f05d6913193b11c https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.1.0-pyhd8ed1ab_0.conda#a385c3e8968b4cf8fbc426ace915fd1a https://conda.anaconda.org/conda-forge/noarch/execnet-1.9.0-pyhd8ed1ab_0.tar.bz2#0e521f7a5e60d508b121d38b04874fb2 https://conda.anaconda.org/conda-forge/noarch/filelock-3.9.0-pyhd8ed1ab_0.conda#1addc115923d646ca19ed90edc413506 @@ -191,7 +191,7 @@ https://conda.anaconda.org/conda-forge/noarch/wheel-0.38.4-pyhd8ed1ab_0.tar.bz2# https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-h166bdaf_0.tar.bz2#c9b568bd804cb2903c6be6f5f68182e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.4-h7f98852_1.tar.bz2#536cc5db4d0a3ba0630541aec064b5e4 https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.10-h7f98852_1003.tar.bz2#f59c1242cc1dd93e72c2ee2b360979eb -https://conda.anaconda.org/conda-forge/noarch/zipp-3.13.0-pyhd8ed1ab_0.conda#41b09d997939e83b231c4557a90c3b13 +https://conda.anaconda.org/conda-forge/noarch/zipp-3.14.0-pyhd8ed1ab_0.conda#01ea04980fa39d7b6dbdd6c67016d177 https://conda.anaconda.org/conda-forge/noarch/babel-2.11.0-pyhd8ed1ab_0.tar.bz2#2ea70fde8d581ba9425a761609eed6ba https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.11.2-pyha770c72_0.conda#88b59f6989f0ed5ab3433af0b82555e1 https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.bz2#d1a88f3ed5b52e1024b80d4bcd26a7a0 @@ -243,7 +243,7 @@ https://conda.anaconda.org/conda-forge/linux-64/gst-plugins-base-1.22.0-h4243ec0 https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda#e07a5691c27e65d8d3d9278c578c7771 https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.7.0-py39he190548_0.conda#62d6ddd9e534f4d325d12470cc4961ab https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557 -https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.0-nompi_py39h94a714e_103.conda#ee29e7176b5854fa09ec17b101945401 +https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.2-nompi_py39hfaa66c4_100.tar.bz2#b5f2db23900499e96f88e39199ffc7b8 https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 @@ -269,3 +269,4 @@ https://conda.anaconda.org/conda-forge/noarch/sphinx-gallery-0.11.1-pyhd8ed1ab_0 https://conda.anaconda.org/conda-forge/noarch/sphinx-panels-0.6.0-pyhd8ed1ab_0.tar.bz2#6eec6480601f5d15babf9c3b3987f34a https://conda.anaconda.org/conda-forge/linux-64/cartopy-0.21.1-py39h6e7ad6e_0.conda#7cb72bd5b1e7c5a23a062db90889356b https://conda.anaconda.org/conda-forge/noarch/imagehash-4.3.1-pyhd8ed1ab_0.tar.bz2#132ad832787a2156be1f1b309835001a + diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index d79015c055..72d353c5c8 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -16,7 +16,7 @@ dependencies: - cftime >=1.5 - dask-core >=2.26 - matplotlib >=3.5 - - netcdf4 <1.6.1 + - netcdf4 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py38.yml b/requirements/ci/py38.yml index b68e8ccf45..e18be0efe4 100644 --- a/requirements/ci/py38.yml +++ b/requirements/ci/py38.yml @@ -16,7 +16,7 @@ dependencies: - cftime >=1.5 - dask-core >=2.26 - matplotlib >=3.5 - - netcdf4 <1.6.1 + - netcdf4 - numpy >=1.19 - python-xxhash - pyproj diff --git a/requirements/ci/py39.yml b/requirements/ci/py39.yml index 9fec76cfde..50dcd77cd9 100644 --- a/requirements/ci/py39.yml +++ b/requirements/ci/py39.yml @@ -16,7 +16,7 @@ dependencies: - cftime >=1.5 - dask-core >=2.26 - matplotlib >=3.5 - - netcdf4 <1.6.1 + - netcdf4 - numpy >=1.19 - python-xxhash - pyproj diff --git a/setup.cfg b/setup.cfg index 75647e6623..b40ace671e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,7 +52,7 @@ install_requires = cftime>=1.5.0 dask[array]>=2.26 matplotlib>=3.5 - netcdf4<1.6.1 + netcdf4 numpy>=1.19 scipy shapely!=1.8.3 From 081b5b83620006e979ea41c2c166d5730b0a32d6 Mon Sep 17 00:00:00 2001 From: Martin Yeo Date: Tue, 21 Feb 2023 13:23:11 +0000 Subject: [PATCH 317/319] Whats new updates for v3.4.1 . --- docs/src/whatsnew/3.4.rst | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/src/whatsnew/3.4.rst b/docs/src/whatsnew/3.4.rst index 1ad676c049..02fc574e51 100644 --- a/docs/src/whatsnew/3.4.rst +++ b/docs/src/whatsnew/3.4.rst @@ -26,15 +26,29 @@ This document explains the changes made to Iris for this release * We have **begun refactoring Iris' regridding**, which has already improved performance and functionality, with more potential in future! * We have made several other significant `🚀 Performance Enhancements`_. - * Please note that **Iris cannot currently work with the latest NetCDF4 - releases**. The pin is set to ``` if you have any issues or feature requests for improving Iris. Enjoy! +v3.4.1 (21 Feb 2023) +==================== + +.. dropdown:: :opticon:`alert` v3.4.1 Patches + :container: + shadow + :title: text-primary text-center font-weight-bold + :body: bg-light + :animate: fade-in + + The patches in this release of Iris include: + + #. `@trexfeathers`_ and `@pp-mo`_ made Iris' use of the `netCDF4`_ library + thread-safe. (:pull:`5095`) + + #. `@trexfeathers`_ and `@pp-mo`_ removed the netCDF4 pin mentioned in + `🔗 Dependencies`_ point 3. (:pull:`5095`) + + 📢 Announcements ================ From 0b54d58040ffcb9bec11d7f7a016ace5714dae8f Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Wed, 22 Feb 2023 17:40:53 +0000 Subject: [PATCH 318/319] Add coverage testing (#4765) * Add coverage testing * Update lockfile * Include __repr__ in coverage * use posargs for coverage flag instead of env var * update whatsnew to mentions posargs --- .github/workflows/ci-tests.yml | 12 ++++++++++-- docs/src/whatsnew/latest.rst | 6 ++++++ lib/iris/tests/runner/_runner.py | 18 ++++++------------ noxfile.py | 10 +++++++--- pyproject.toml | 16 ++++++++++++++++ requirements/ci/nox.lock/py310-linux-64.lock | 10 ++++++---- requirements/ci/py310.yml | 1 + 7 files changed, 52 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index cee98dc33d..81f5132ccf 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -36,8 +36,12 @@ jobs: matrix: os: ["ubuntu-latest"] python-version: ["3.10"] - session: ["tests", "doctest", "gallery", "linkcheck"] + session: ["doctest", "gallery", "linkcheck"] include: + - os: "ubuntu-latest" + python-version: "3.10" + session: "tests" + coverage: "--coverage" - os: "ubuntu-latest" python-version: "3.9" session: "tests" @@ -133,4 +137,8 @@ jobs: env: PY_VER: ${{ matrix.python-version }} run: | - nox --session ${{ matrix.session }} -- --verbose + nox --session ${{ matrix.session }} -- --verbose ${{ matrix.coverage }} + + - name: Upload coverage report + uses: codecov/codecov-action@v3 + if: ${{ matrix.coverage }} diff --git a/docs/src/whatsnew/latest.rst b/docs/src/whatsnew/latest.rst index b23687af7a..c5c3b2d173 100644 --- a/docs/src/whatsnew/latest.rst +++ b/docs/src/whatsnew/latest.rst @@ -109,6 +109,12 @@ This document explains the changes made to Iris for this release #. `@rcomer`_ removed some old infrastructure that printed test timings. (:pull:`5101`) +#. `@lbdreyer`_ and `@trexfeathers`_ (reviewer) added coverage testing. This + can be enabled by using the "--coverage" flag when running the tests with + nox i.e. ``nox --session tests -- --coverage``. (:pull:`4765`) + +#. `@lbdreyer`_ and `@trexfeathers`_ (reviewer) removed the ``--coding-tests`` + option from Iris' test runner. (:pull:`4765`) .. comment Whatsnew author names (@github name) in alphabetical order. Note that, diff --git a/lib/iris/tests/runner/_runner.py b/lib/iris/tests/runner/_runner.py index bfb2cc2402..7f9439d4b6 100644 --- a/lib/iris/tests/runner/_runner.py +++ b/lib/iris/tests/runner/_runner.py @@ -35,18 +35,13 @@ class TestRunner: ("system-tests", "s", "Run the limited subset of system tests."), ("gallery-tests", "e", "Run the gallery code tests."), ("default-tests", "d", "Run the default tests."), - ( - "coding-tests", - "c", - "Run the coding standards tests. (These are a " - "subset of the default tests.)", - ), ( "num-processors=", "p", "The number of processors used for running " "the tests.", ), ("create-missing", "m", "Create missing test result files."), + ("coverage", "c", "Enable coverage testing"), ] boolean_options = [ "no-data", @@ -54,8 +49,8 @@ class TestRunner: "stop", "gallery-tests", "default-tests", - "coding-tests", "create-missing", + "coverage", ] def initialize_options(self): @@ -64,9 +59,9 @@ def initialize_options(self): self.system_tests = False self.gallery_tests = False self.default_tests = False - self.coding_tests = False self.num_processors = None self.create_missing = False + self.coverage = False def finalize_options(self): # These environment variables will be propagated to all the @@ -84,8 +79,6 @@ def finalize_options(self): tests.append("system") if self.default_tests: tests.append("default") - if self.coding_tests: - tests.append("coding") if self.gallery_tests: tests.append("gallery") if not tests: @@ -109,8 +102,6 @@ def run(self): tests.append("lib/iris/tests/system_test.py") if self.default_tests: tests.append("lib/iris/tests") - if self.coding_tests: - tests.append("lib/iris/tests/test_coding_standards.py") if self.gallery_tests: import iris.config @@ -136,6 +127,9 @@ def run(self): if self.stop: args.append("-x") + if self.coverage: + args.extend(["--cov=lib/iris", "--cov-report=xml"]) + result = True for test in tests: args[0] = test diff --git a/noxfile.py b/noxfile.py index 8aabf862fb..c7b0a0e05b 100755 --- a/noxfile.py +++ b/noxfile.py @@ -176,6 +176,8 @@ def tests(session: nox.sessions.Session): """ Perform iris system, integration and unit tests. + Coverage testing is enabled if the "--coverage" or "-c" flag is used. + Parameters ---------- session: object @@ -185,13 +187,15 @@ def tests(session: nox.sessions.Session): prepare_venv(session) session.install("--no-deps", "--editable", ".") session.env.update(ENV) - session.run( + run_args = [ "python", "-m", "iris.tests.runner", "--default-tests", - "--system-tests", - ) + ] + if "-c" in session.posargs or "--coverage" in session.posargs: + run_args.append("--coverage") + session.run(*run_args) @nox.session(python=_PY_VERSION_DOCSBUILD, venv_backend="conda") diff --git a/pyproject.toml b/pyproject.toml index bdb8a431e5..b44187191b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,3 +46,19 @@ verbose = "False" [tool.pytest.ini_options] addopts = "-ra" testpaths = "lib/iris" + +[tool.coverage.run] +branch = true +source = [ + "lib/iris", +] +omit = [ + "lib/iris/tests/*", + "lib/iris/etc/*", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "if __name__ == .__main__.:" +] diff --git a/requirements/ci/nox.lock/py310-linux-64.lock b/requirements/ci/nox.lock/py310-linux-64.lock index 9dd269eff6..0d97158f45 100644 --- a/requirements/ci/nox.lock/py310-linux-64.lock +++ b/requirements/ci/nox.lock/py310-linux-64.lock @@ -1,6 +1,6 @@ # Generated by conda-lock. # platform: linux-64 -# input_hash: b3eba68adec85dc6750f8775b6e48189678a75c3baa3dc3f6ed9b9351137fe50 +# input_hash: f8af5f4aafcb766f463a1a897d3dab9e04f05f1494bced5931d78175ca0c66df @EXPLICIT https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2#d7c89558ba9fa0495403155b64376d81 https://conda.anaconda.org/conda-forge/linux-64/ca-certificates-2022.12.7-ha878542_0.conda#ff9f73d45c4a07d6f424495288a26080 @@ -150,7 +150,7 @@ https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.4.4-py310hbf28c38_1 https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.14-hfd0df8a_1.conda#c2566c2ea5f153ddd6bf4acaf7547d97 https://conda.anaconda.org/conda-forge/linux-64/libclang13-15.0.7-default_h3e3d535_1.conda#a3a0f7a6f0885f5e1e0ec691566afb77 https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h36d4200_3.conda#c9f4416a34bc91e0eb029f912c68f81f -https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.0-hdc1c0ab_0.conda#c44acb3847ff118c068b662aff858afd +https://conda.anaconda.org/conda-forge/linux-64/libcurl-7.88.1-hdc1c0ab_0.conda#81eaeb3b35163c8e90e57532bc93754d https://conda.anaconda.org/conda-forge/linux-64/libpq-15.2-hb675445_0.conda#4654b17eccaba55b8581d6b9c77f53cc https://conda.anaconda.org/conda-forge/linux-64/libsystemd0-252-h2a991cd_0.tar.bz2#3c5ae9f61f663b3d5e1bf7f7da0c85f5 https://conda.anaconda.org/conda-forge/linux-64/libwebp-1.2.4-h1daa5a0_1.conda#77003f63d1763c1e6569a02c1742c9f4 @@ -198,7 +198,8 @@ https://conda.anaconda.org/conda-forge/linux-64/cairo-1.16.0-ha61ee94_1014.tar.b https://conda.anaconda.org/conda-forge/linux-64/cffi-1.15.1-py310h255011f_3.conda#800596144bb613cd7ac58b80900ce835 https://conda.anaconda.org/conda-forge/linux-64/cftime-1.6.2-py310hde88566_1.tar.bz2#94ce7a76b0c912279f6958e0b6b21d2b https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.0.7-py310hdf3cbec_0.conda#7bf9d8c765b6b04882c719509652c6d6 -https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.0-hdc1c0ab_0.conda#5d9ac94ee84305ada32c3d287d0ec602 +https://conda.anaconda.org/conda-forge/linux-64/coverage-7.1.0-py310h1fa729e_0.conda#da7c45dbe780f5e162011a3af44e5009 +https://conda.anaconda.org/conda-forge/linux-64/curl-7.88.1-hdc1c0ab_0.conda#1968e4fef727858ac04746560e820928 https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.38.0-py310h5764c6d_1.tar.bz2#12ebe92a8a578bc903bd844744f4d040 https://conda.anaconda.org/conda-forge/linux-64/glib-2.74.1-h6239696_1.tar.bz2#f3220a9e9d3abcbfca43419a219df7e4 https://conda.anaconda.org/conda-forge/linux-64/hdf5-1.12.2-mpi_mpich_h5d83325_1.conda#811c4d55cf17b42336ffa314239717b0 @@ -234,6 +235,7 @@ https://conda.anaconda.org/conda-forge/linux-64/pandas-1.5.3-py310h9b08913_0.con https://conda.anaconda.org/conda-forge/noarch/platformdirs-3.0.0-pyhd8ed1ab_0.conda#c34694044915d7f291ef257029f2e2af https://conda.anaconda.org/conda-forge/linux-64/pyproj-3.4.1-py310h15e2413_1.conda#5be35366687def87437d210fd673100c https://conda.anaconda.org/conda-forge/linux-64/pyqt5-sip-12.11.0-py310heca2aa9_3.conda#3b1946b676534472ce65181dda0b9554 +https://conda.anaconda.org/conda-forge/noarch/pytest-cov-4.0.0-pyhd8ed1ab_0.tar.bz2#c9e3f8bfdb9bfc34aa1836a6ed4b25d7 https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.2.0-pyhd8ed1ab_0.conda#70ab87b96126f35d1e68de2ad9fb6423 https://conda.anaconda.org/conda-forge/noarch/setuptools-scm-7.1.0-pyhd8ed1ab_0.conda#6613dbb3b25cc648a107f33ca9f80fc1 https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-napoleon-0.7-py_0.tar.bz2#0bc25ff6f2e34af63ded59692df5f749 @@ -243,7 +245,7 @@ https://conda.anaconda.org/conda-forge/noarch/identify-2.5.18-pyhd8ed1ab_0.conda https://conda.anaconda.org/conda-forge/noarch/nc-time-axis-1.4.1-pyhd8ed1ab_0.tar.bz2#281b58948bf60a2582de9e548bcc5369 https://conda.anaconda.org/conda-forge/linux-64/netcdf-fortran-4.6.0-mpi_mpich_h1e13492_2.conda#d4ed7704f0fa589e4d7656780fa87557 https://conda.anaconda.org/conda-forge/linux-64/netcdf4-1.6.2-nompi_py310h55e1e36_100.tar.bz2#4dd7aa28fb7d9a6de061c9579a30e7dd -https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.12-hd33c08f_1.conda#667dc93c913f0156e1237032e3a22046 +https://conda.anaconda.org/conda-forge/linux-64/pango-1.50.13-hd33c08f_0.conda#e3b13445b8ee9d6a3d53a714f89ccd76 https://conda.anaconda.org/conda-forge/linux-64/parallelio-2.5.10-mpi_mpich_h862c5c2_100.conda#56e43c5226670aa0943fae9a2628a934 https://conda.anaconda.org/conda-forge/noarch/pyopenssl-23.0.0-pyhd8ed1ab_0.conda#d41957700e83bbb925928764cb7f8878 https://conda.anaconda.org/conda-forge/noarch/virtualenv-20.19.0-pyhd8ed1ab_0.conda#afaa9bf6992f67a82d75fad47a93ec84 diff --git a/requirements/ci/py310.yml b/requirements/ci/py310.yml index 72d353c5c8..ae66d66a77 100644 --- a/requirements/ci/py310.yml +++ b/requirements/ci/py310.yml @@ -39,6 +39,7 @@ dependencies: - pre-commit - psutil - pytest + - pytest-cov - pytest-xdist - requests From 7964a794f3eb38d78fc0ec74cef76ce3610df3cf Mon Sep 17 00:00:00 2001 From: lbdreyer Date: Thu, 23 Feb 2023 17:13:42 +0000 Subject: [PATCH 319/319] Remove tests --- lib/iris/tests/integration/__init__.py | 6 - .../tests/integration/analysis/__init__.py | 6 - .../analysis/test_area_weighted.py | 75 - .../tests/integration/aux_factory/__init__.py | 6 - .../aux_factory/test_OceanSigmaZFactory.py | 196 - .../tests/integration/concatenate/__init__.py | 6 - .../concatenate/test_concatenate.py | 327 -- .../integration/experimental/__init__.py | 6 - .../experimental/test_CubeRepresentation.py | 164 - .../test_regrid_ProjectedUnstructured.py | 163 - .../experimental/test_ugrid_load.py | 247 - .../experimental/test_ugrid_save.py | 125 - .../ugrid_conventions_examples/README.txt | 16 - .../ugrid_ex1_1d_mesh.cdl | 55 - .../ugrid_ex2_2d_triangular.cdl | 84 - .../ugrid_ex3_2d_flexible.cdl | 99 - .../ugrid_ex4_3d_layered.cdl | 120 - .../tests/integration/fast_load/__init__.py | 6 - .../integration/fast_load/test_fast_load.py | 690 --- lib/iris/tests/integration/merge/__init__.py | 6 - .../tests/integration/merge/test_merge.py | 37 - lib/iris/tests/integration/netcdf/__init__.py | 6 - .../integration/netcdf/test_attributes.py | 119 - .../integration/netcdf/test_aux_factories.py | 160 - .../integration/netcdf/test_coord_systems.py | 281 - .../tests/integration/netcdf/test_general.py | 360 -- .../netcdf/test_self_referencing.py | 126 - .../integration/netcdf/test_thread_safety.py | 109 - lib/iris/tests/integration/plot/__init__.py | 6 - .../tests/integration/plot/test_animate.py | 78 - .../tests/integration/plot/test_colorbar.py | 109 - .../tests/integration/plot/test_netcdftime.py | 55 - .../tests/integration/plot/test_nzdateline.py | 41 - .../integration/plot/test_plot_2d_coords.py | 87 - .../integration/plot/test_vector_plots.py | 233 - lib/iris/tests/integration/test_Datums.py | 52 - .../tests/integration/test_PartialDateTime.py | 31 - .../tests/integration/test_climatology.py | 113 - lib/iris/tests/integration/test_cube.py | 63 - lib/iris/tests/integration/test_ff.py | 97 - lib/iris/tests/integration/test_new_axis.py | 28 - lib/iris/tests/integration/test_pickle.py | 59 - lib/iris/tests/integration/test_pp.py | 808 --- .../test_pp_constrained_load_cubes.py | 48 - .../integration/test_regrid_equivalence.py | 228 - lib/iris/tests/integration/test_regridding.py | 248 - lib/iris/tests/integration/test_subset.py | 45 - lib/iris/tests/integration/test_trajectory.py | 353 -- lib/iris/tests/integration/um/__init__.py | 6 - .../tests/integration/um/test_fieldsfile.py | 42 - lib/iris/tests/unit/__init__.py | 6 - lib/iris/tests/unit/analysis/__init__.py | 6 - .../unit/analysis/area_weighted/__init__.py | 6 - .../test_AreaWeightedRegridder.py | 287 - .../unit/analysis/cartography/__init__.py | 6 - .../cartography/test__get_lon_lat_coords.py | 114 - .../cartography/test__quadrant_area.py | 123 - .../analysis/cartography/test__xy_range.py | 57 - .../analysis/cartography/test_area_weights.py | 39 - .../cartography/test_gridcell_angles.py | 345 -- .../unit/analysis/cartography/test_project.py | 169 - .../cartography/test_rotate_grid_vectors.py | 138 - .../analysis/cartography/test_rotate_winds.py | 515 -- .../tests/unit/analysis/geometry/__init__.py | 6 - .../test__extract_relevant_cube_slice.py | 98 - .../geometry/test_geometry_area_weights.py | 171 - .../unit/analysis/interpolation/__init__.py | 6 - .../test_RectilinearInterpolator.py | 612 --- .../interpolation/test_get_xy_dim_coords.py | 127 - .../tests/unit/analysis/maths/__init__.py | 321 -- .../maths/test__arith__derived_coords.py | 39 - .../analysis/maths/test__arith__meshcoords.py | 184 - .../unit/analysis/maths/test__get_dtype.py | 98 - .../maths/test__inplace_common_checks.py | 151 - .../unit/analysis/maths/test__output_dtype.py | 235 - .../tests/unit/analysis/maths/test_add.py | 69 - .../tests/unit/analysis/maths/test_divide.py | 87 - .../unit/analysis/maths/test_multiply.py | 69 - .../unit/analysis/maths/test_subtract.py | 69 - .../tests/unit/analysis/regrid/__init__.py | 6 - .../regrid/test_RectilinearRegridder.py | 1463 ----- .../regrid/test__CurvilinearRegridder.py | 397 -- .../analysis/scipy_interpolate/__init__.py | 6 - .../test__RegularGridInterpolator.py | 84 - .../tests/unit/analysis/stats/__init__.py | 6 - .../unit/analysis/stats/test_pearsonr.py | 176 - .../tests/unit/analysis/test_Aggregator.py | 335 -- .../tests/unit/analysis/test_AreaWeighted.py | 59 - lib/iris/tests/unit/analysis/test_COUNT.py | 111 - lib/iris/tests/unit/analysis/test_Linear.py | 147 - lib/iris/tests/unit/analysis/test_MAX.py | 78 - lib/iris/tests/unit/analysis/test_MAX_RUN.py | 313 -- lib/iris/tests/unit/analysis/test_MEAN.py | 108 - lib/iris/tests/unit/analysis/test_MIN.py | 78 - lib/iris/tests/unit/analysis/test_Nearest.py | 141 - .../tests/unit/analysis/test_PERCENTILE.py | 437 -- .../tests/unit/analysis/test_PROPORTION.py | 62 - .../analysis/test_PercentileAggregator.py | 143 - .../tests/unit/analysis/test_PointInCell.py | 36 - lib/iris/tests/unit/analysis/test_RMS.py | 187 - lib/iris/tests/unit/analysis/test_STD_DEV.py | 82 - lib/iris/tests/unit/analysis/test_SUM.py | 164 - lib/iris/tests/unit/analysis/test_VARIANCE.py | 85 - .../tests/unit/analysis/test_WPERCENTILE.py | 238 - .../test_WeightedPercentileAggregator.py | 149 - .../analysis/test__axis_to_single_trailing.py | 150 - .../unit/analysis/trajectory/__init__.py | 6 - .../analysis/trajectory/test_Trajectory.py | 197 - ...t_UnstructuredNearestNeighbourRegridder.py | 328 -- ...est__nearest_neighbour_indices_ndcoords.py | 228 - .../analysis/trajectory/test_interpolate.py | 364 -- lib/iris/tests/unit/aux_factory/__init__.py | 6 - .../test_AtmosphereSigmaFactory.py | 296 - .../unit/aux_factory/test_AuxCoordFactory.py | 166 - .../aux_factory/test_HybridPressureFactory.py | 309 -- .../unit/aux_factory/test_OceanSFactory.py | 325 -- .../unit/aux_factory/test_OceanSg1Factory.py | 298 - .../unit/aux_factory/test_OceanSg2Factory.py | 298 - .../aux_factory/test_OceanSigmaFactory.py | 174 - .../aux_factory/test_OceanSigmaZFactory.py | 450 -- lib/iris/tests/unit/common/__init__.py | 6 - .../tests/unit/common/lenient/__init__.py | 6 - .../tests/unit/common/lenient/test_Lenient.py | 186 - .../unit/common/lenient/test__Lenient.py | 835 --- .../common/lenient/test__lenient_client.py | 182 - .../common/lenient/test__lenient_service.py | 116 - .../unit/common/lenient/test__qualname.py | 66 - .../tests/unit/common/metadata/__init__.py | 6 - .../test_AncillaryVariableMetadata.py | 492 -- .../unit/common/metadata/test_BaseMetadata.py | 1653 ------ .../metadata/test_CellMeasureMetadata.py | 661 --- .../common/metadata/test_CoordMetadata.py | 722 --- .../unit/common/metadata/test_CubeMetadata.py | 829 --- .../common/metadata/test__NamedTupleMeta.py | 148 - .../unit/common/metadata/test_hexdigest.py | 179 - .../common/metadata/test_metadata_filter.py | 136 - .../metadata/test_metadata_manager_factory.py | 203 - lib/iris/tests/unit/common/mixin/__init__.py | 6 - .../unit/common/mixin/test_CFVariableMixin.py | 380 -- .../common/mixin/test_LimitedAttributeDict.py | 70 - .../mixin/test__get_valid_standard_name.py | 72 - .../tests/unit/common/resolve/__init__.py | 6 - .../tests/unit/common/resolve/test_Resolve.py | 4826 ----------------- lib/iris/tests/unit/concatenate/__init__.py | 6 - .../unit/concatenate/test__CubeSignature.py | 86 - .../unit/concatenate/test_concatenate.py | 358 -- lib/iris/tests/unit/config/__init__.py | 6 - lib/iris/tests/unit/config/test_NetCDF.py | 45 - lib/iris/tests/unit/constraints/__init__.py | 6 - .../constraints/test_Constraint_equality.py | 274 - .../unit/constraints/test_NameConstraint.py | 229 - .../unit/coord_categorisation/__init__.py | 6 - .../test_add_categorised_coord.py | 133 - .../coord_categorisation/test_add_hour.py | 82 - lib/iris/tests/unit/coord_systems/__init__.py | 6 - .../coord_systems/test_AlbersEqualArea.py | 152 - .../tests/unit/coord_systems/test_GeogCS.py | 62 - .../unit/coord_systems/test_Geostationary.py | 116 - .../test_LambertAzimuthalEqualArea.py | 127 - .../coord_systems/test_LambertConformal.py | 78 - .../tests/unit/coord_systems/test_Mercator.py | 222 - .../unit/coord_systems/test_Orthographic.py | 101 - .../coord_systems/test_PolarStereographic.py | 251 - .../unit/coord_systems/test_RotatedPole.py | 85 - .../unit/coord_systems/test_Stereographic.py | 212 - .../coord_systems/test_TransverseMercator.py | 58 - .../coord_systems/test_VerticalPerspective.py | 89 - lib/iris/tests/unit/coords/__init__.py | 150 - .../unit/coords/test_AncillaryVariable.py | 700 --- lib/iris/tests/unit/coords/test_AuxCoord.py | 804 --- lib/iris/tests/unit/coords/test_Cell.py | 181 - .../tests/unit/coords/test_CellMeasure.py | 138 - lib/iris/tests/unit/coords/test_CellMethod.py | 92 - lib/iris/tests/unit/coords/test_Coord.py | 1179 ---- lib/iris/tests/unit/coords/test_DimCoord.py | 623 --- .../unit/coords/test__DimensionalMetadata.py | 1078 ---- lib/iris/tests/unit/cube/__init__.py | 6 - lib/iris/tests/unit/cube/test_Cube.py | 3217 ----------- lib/iris/tests/unit/cube/test_CubeList.py | 770 --- .../unit/cube/test_Cube__aggregated_by.py | 893 --- .../tests/unit/cube/test_Cube__operators.py | 252 - lib/iris/tests/unit/data_manager/__init__.py | 6 - .../unit/data_manager/test_DataManager.py | 598 -- lib/iris/tests/unit/experimental/__init__.py | 6 - .../unit/experimental/raster/__init__.py | 6 - .../raster/test_export_geotiff.py | 186 - .../unit/experimental/regrid/__init__.py | 6 - ..._area_weighted_rectilinear_src_and_grid.py | 192 - ...rid_weighted_curvilinear_to_rectilinear.py | 378 -- .../experimental/representation/__init__.py | 6 - .../test_CubeListRepresentation.py | 69 - .../representation/test_CubeRepresentation.py | 447 -- .../unit/experimental/stratify/__init__.py | 6 - .../experimental/stratify/test_relevel.py | 128 - .../tests/unit/experimental/ugrid/__init__.py | 6 - .../unit/experimental/ugrid/cf/__init__.py | 6 - ...test_CFUGridAuxiliaryCoordinateVariable.py | 238 - .../cf/test_CFUGridConnectivityVariable.py | 224 - .../ugrid/cf/test_CFUGridGroup.py | 99 - .../ugrid/cf/test_CFUGridMeshVariable.py | 263 - .../ugrid/cf/test_CFUGridReader.py | 135 - .../unit/experimental/ugrid/load/__init__.py | 6 - .../ugrid/load/test_ParseUgridOnLoad.py | 46 - .../experimental/ugrid/load/test_load_mesh.py | 50 - .../ugrid/load/test_load_meshes.py | 231 - .../unit/experimental/ugrid/mesh/__init__.py | 6 - .../ugrid/mesh/test_Connectivity.py | 368 -- .../unit/experimental/ugrid/mesh/test_Mesh.py | 1348 ----- .../experimental/ugrid/mesh/test_MeshCoord.py | 943 ---- .../ugrid/mesh/test_Mesh__from_coords.py | 253 - .../experimental/ugrid/metadata/__init__.py | 6 - .../metadata/test_ConnectivityMetadata.py | 774 --- .../ugrid/metadata/test_MeshCoordMetadata.py | 732 --- .../ugrid/metadata/test_MeshMetadata.py | 783 --- .../unit/experimental/ugrid/utils/__init__.py | 6 - .../ugrid/utils/test_recombine_submeshes.py | 437 -- lib/iris/tests/unit/fileformats/__init__.py | 69 - .../tests/unit/fileformats/abf/__init__.py | 6 - .../unit/fileformats/abf/test_ABFField.py | 53 - .../tests/unit/fileformats/cf/__init__.py | 6 - .../tests/unit/fileformats/cf/test_CFGroup.py | 51 - .../unit/fileformats/cf/test_CFReader.py | 371 -- .../tests/unit/fileformats/dot/__init__.py | 6 - .../unit/fileformats/dot/test__dot_path.py | 71 - .../tests/unit/fileformats/ff/__init__.py | 6 - .../unit/fileformats/ff/test_ArakawaC.py | 89 - .../tests/unit/fileformats/ff/test_ENDGame.py | 49 - .../tests/unit/fileformats/ff/test_FF2PP.py | 600 -- .../unit/fileformats/ff/test_FFHeader.py | 77 - .../tests/unit/fileformats/ff/test_Grid.py | 110 - .../unit/fileformats/ff/test_NewDynamics.py | 51 - .../unit/fileformats/name_loaders/__init__.py | 6 - .../name_loaders/test__build_cell_methods.py | 136 - ...test__build_lat_lon_for_NAME_timeseries.py | 52 - .../test__calc_integration_period.py | 65 - .../name_loaders/test__cf_height_from_name.py | 317 -- .../name_loaders/test__generate_cubes.py | 168 - .../fileformats/nc_load_rules/__init__.py | 10 - .../nc_load_rules/actions/__init__.py | 159 - .../actions/test__grid_mappings.py | 884 --- .../actions/test__hybrid_formulae.py | 313 -- .../actions/test__latlon_dimcoords.py | 337 -- .../actions/test__miscellaneous.py | 222 - .../actions/test__time_coords.py | 462 -- .../nc_load_rules/engine/__init__.py | 10 - .../nc_load_rules/engine/test_engine.py | 103 - .../nc_load_rules/helpers/__init__.py | 10 - ...ild_albers_equal_area_coordinate_system.py | 92 - .../helpers/test_build_ancil_var.py | 77 - .../test_build_auxiliary_coordinate.py | 330 -- .../helpers/test_build_cell_measure.py | 74 - .../helpers/test_build_cube_metadata.py | 126 - .../test_build_dimension_coordinate.py | 567 -- ...t_build_geostationary_coordinate_system.py | 84 - ..._azimuthal_equal_area_coordinate_system.py | 90 - ...ild_lambert_conformal_coordinate_system.py | 92 - .../test_build_mercator_coordinate_system.py | 136 - ...d_polar_stereographic_coordinate_system.py | 150 - ...t_build_stereographic_coordinate_system.py | 84 - ...d_transverse_mercator_coordinate_system.py | 88 - .../test_build_verticalp_coordinate_system.py | 84 - .../helpers/test_get_attr_units.py | 53 - .../helpers/test_get_cf_bounds_var.py | 55 - .../nc_load_rules/helpers/test_get_names.py | 292 - .../test_has_supported_mercator_parameters.py | 129 - ...upported_polar_stereographic_parameters.py | 242 - .../helpers/test_reorder_bounds_data.py | 56 - .../tests/unit/fileformats/netcdf/__init__.py | 6 - .../fileformats/netcdf/loader/__init__.py | 6 - .../netcdf/loader/test__get_cf_var_data.py | 73 - .../netcdf/loader/test__load_aux_factory.py | 193 - .../netcdf/loader/test__load_cube.py | 181 - ...__translate_constraints_to_var_callback.py | 102 - .../unit/fileformats/netcdf/saver/__init__.py | 6 - .../test__FillValueMaskCheckAndStoreTarget.py | 92 - .../unit/fileformats/netcdf/test_Saver.py | 1088 ---- .../fileformats/netcdf/test_Saver__lazy.py | 128 - .../fileformats/netcdf/test_Saver__ugrid.py | 1280 ----- .../fileformats/netcdf/test_load_cubes.py | 316 -- .../netcdf/test_parse_cell_methods.py | 196 - .../unit/fileformats/netcdf/test_save.py | 363 -- .../fileformats/nimrod_load_rules/__init__.py | 6 - .../nimrod_load_rules/test_units.py | 147 - .../nimrod_load_rules/test_vertical_coord.py | 84 - .../tests/unit/fileformats/pp/__init__.py | 6 - .../unit/fileformats/pp/test_PPDataProxy.py | 37 - .../tests/unit/fileformats/pp/test_PPField.py | 354 -- .../pp/test__convert_constraints.py | 96 - .../fileformats/pp/test__create_field_data.py | 89 - .../pp/test__data_bytes_to_shaped_array.py | 209 - .../unit/fileformats/pp/test__field_gen.py | 112 - .../fileformats/pp/test__interpret_field.py | 138 - .../unit/fileformats/pp/test_as_fields.py | 34 - .../tests/unit/fileformats/pp/test_load.py | 42 - .../tests/unit/fileformats/pp/test_save.py | 362 -- .../unit/fileformats/pp/test_save_fields.py | 50 - .../pp/test_save_pairs_from_cube.py | 50 - .../fileformats/pp_load_rules/__init__.py | 6 - .../pp_load_rules/test__all_other_rules.py | 306 -- ...__collapse_degenerate_points_and_bounds.py | 97 - ...est__convert_scalar_pseudo_level_coords.py | 35 - ...test__convert_scalar_realization_coords.py | 35 - .../test__convert_time_coords.py | 796 --- .../test__convert_vertical_coords.py | 815 --- .../pp_load_rules/test__dim_or_aux.py | 58 - .../pp_load_rules/test__epoch_date_hours.py | 154 - .../pp_load_rules/test__model_level_number.py | 30 - .../test__reduced_points_and_bounds.py | 93 - .../test__reshape_vector_args.py | 139 - .../fileformats/pp_load_rules/test_convert.py | 459 -- .../tests/unit/fileformats/rules/__init__.py | 6 - .../unit/fileformats/rules/test_Loader.py | 48 - .../unit/fileformats/rules/test__make_cube.py | 66 - .../__init__.py | 10 - .../test_ArrayStructure.py | 230 - .../test_GroupStructure.py | 198 - lib/iris/tests/unit/fileformats/test_rules.py | 269 - .../tests/unit/fileformats/um/__init__.py | 6 - .../unit/fileformats/um/fast_load/__init__.py | 9 - .../um/fast_load/test_FieldCollation.py | 71 - .../um/fast_load/test__convert_collation.py | 450 -- .../fast_load_structured_fields/__init__.py | 10 - .../test_BasicFieldCollation.py | 231 - .../test_group_structured_fields.py | 134 - .../um/optimal_array_structuring/__init__.py | 10 - .../test_optimal_array_structure.py | 260 - .../unit/fileformats/um/test_um_to_pp.py | 56 - lib/iris/tests/unit/io/__init__.py | 6 - .../tests/unit/io/test__generate_cubes.py | 37 - .../tests/unit/io/test_expand_filespecs.py | 125 - lib/iris/tests/unit/io/test_run_callback.py | 83 - lib/iris/tests/unit/io/test_save.py | 51 - lib/iris/tests/unit/lazy_data/__init__.py | 6 - .../unit/lazy_data/test_as_concrete_data.py | 81 - .../tests/unit/lazy_data/test_as_lazy_data.py | 170 - .../unit/lazy_data/test_co_realise_cubes.py | 81 - .../tests/unit/lazy_data/test_is_lazy_data.py | 30 - .../unit/lazy_data/test_lazy_elementwise.py | 50 - .../lazy_data/test_map_complete_blocks.py | 104 - .../lazy_data/test_multidim_lazy_stack.py | 49 - .../tests/unit/lazy_data/test_non_lazy.py | 37 - lib/iris/tests/unit/merge/__init__.py | 6 - lib/iris/tests/unit/merge/test_ProtoCube.py | 538 -- lib/iris/tests/unit/pandas/__init__.py | 6 - lib/iris/tests/unit/pandas/test_pandas.py | 1357 ----- lib/iris/tests/unit/plot/__init__.py | 118 - lib/iris/tests/unit/plot/_blockplot_common.py | 120 - .../test__check_bounds_contiguity_and_mask.py | 101 - ..._check_geostationary_coords_and_convert.py | 64 - lib/iris/tests/unit/plot/test__fixup_dates.py | 82 - .../tests/unit/plot/test__get_plot_defn.py | 45 - ...est__get_plot_defn_custom_coords_picked.py | 90 - .../tests/unit/plot/test__get_plot_objects.py | 45 - .../test__replace_axes_with_cartopy_axes.py | 45 - lib/iris/tests/unit/plot/test_contour.py | 74 - lib/iris/tests/unit/plot/test_contourf.py | 115 - lib/iris/tests/unit/plot/test_outline.py | 78 - lib/iris/tests/unit/plot/test_pcolor.py | 50 - lib/iris/tests/unit/plot/test_pcolormesh.py | 50 - lib/iris/tests/unit/plot/test_plot.py | 272 - lib/iris/tests/unit/plot/test_points.py | 76 - lib/iris/tests/unit/plot/test_scatter.py | 64 - lib/iris/tests/unit/quickplot/__init__.py | 6 - lib/iris/tests/unit/quickplot/test_contour.py | 48 - .../tests/unit/quickplot/test_contourf.py | 55 - lib/iris/tests/unit/quickplot/test_outline.py | 50 - lib/iris/tests/unit/quickplot/test_pcolor.py | 52 - .../tests/unit/quickplot/test_pcolormesh.py | 52 - lib/iris/tests/unit/quickplot/test_plot.py | 58 - lib/iris/tests/unit/quickplot/test_points.py | 48 - lib/iris/tests/unit/quickplot/test_scatter.py | 33 - .../tests/unit/representation/__init__.py | 6 - .../representation/cube_printout/__init__.py | 6 - .../cube_printout/test_CubePrintout.py | 567 -- .../cube_printout/test_Table.py | 159 - .../representation/cube_summary/__init__.py | 6 - .../cube_summary/test_CubeSummary.py | 327 -- lib/iris/tests/unit/test_Future.py | 126 - lib/iris/tests/unit/test_sample_data_path.py | 91 - lib/iris/tests/unit/tests/__init__.py | 6 - lib/iris/tests/unit/tests/stock/__init__.py | 6 - .../tests/unit/tests/stock/test_netcdf.py | 142 - lib/iris/tests/unit/tests/test_IrisTest.py | 129 - lib/iris/tests/unit/time/__init__.py | 6 - .../tests/unit/time/test_PartialDateTime.py | 278 - lib/iris/tests/unit/util/__init__.py | 6 - .../tests/unit/util/test__coord_regular.py | 111 - lib/iris/tests/unit/util/test__is_circular.py | 28 - lib/iris/tests/unit/util/test__mask_array.py | 173 - .../unit/util/test__slice_data_with_keys.py | 436 -- lib/iris/tests/unit/util/test_array_equal.py | 125 - .../unit/util/test_broadcast_to_shape.py | 63 - .../unit/util/test_column_slices_generator.py | 36 - .../test_demote_dim_coord_to_aux_coord.py | 76 - .../tests/unit/util/test_describe_diff.py | 60 - .../unit/util/test_equalise_attributes.py | 157 - .../unit/util/test_file_is_newer_than.py | 130 - .../unit/util/test_find_discontiguities.py | 87 - lib/iris/tests/unit/util/test_mask_cube.py | 195 - lib/iris/tests/unit/util/test_new_axis.py | 310 -- .../test_promote_aux_coord_to_dim_coord.py | 105 - lib/iris/tests/unit/util/test_reverse.py | 220 - .../tests/unit/util/test_rolling_window.py | 119 - lib/iris/tests/unit/util/test_squeeze.py | 51 - .../tests/unit/util/test_unify_time_units.py | 117 - 405 files changed, 80794 deletions(-) delete mode 100644 lib/iris/tests/integration/__init__.py delete mode 100644 lib/iris/tests/integration/analysis/__init__.py delete mode 100644 lib/iris/tests/integration/analysis/test_area_weighted.py delete mode 100644 lib/iris/tests/integration/aux_factory/__init__.py delete mode 100644 lib/iris/tests/integration/aux_factory/test_OceanSigmaZFactory.py delete mode 100644 lib/iris/tests/integration/concatenate/__init__.py delete mode 100644 lib/iris/tests/integration/concatenate/test_concatenate.py delete mode 100644 lib/iris/tests/integration/experimental/__init__.py delete mode 100644 lib/iris/tests/integration/experimental/test_CubeRepresentation.py delete mode 100644 lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py delete mode 100644 lib/iris/tests/integration/experimental/test_ugrid_load.py delete mode 100644 lib/iris/tests/integration/experimental/test_ugrid_save.py delete mode 100644 lib/iris/tests/integration/experimental/ugrid_conventions_examples/README.txt delete mode 100644 lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl delete mode 100644 lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl delete mode 100644 lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl delete mode 100644 lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl delete mode 100644 lib/iris/tests/integration/fast_load/__init__.py delete mode 100644 lib/iris/tests/integration/fast_load/test_fast_load.py delete mode 100644 lib/iris/tests/integration/merge/__init__.py delete mode 100644 lib/iris/tests/integration/merge/test_merge.py delete mode 100644 lib/iris/tests/integration/netcdf/__init__.py delete mode 100644 lib/iris/tests/integration/netcdf/test_attributes.py delete mode 100644 lib/iris/tests/integration/netcdf/test_aux_factories.py delete mode 100644 lib/iris/tests/integration/netcdf/test_coord_systems.py delete mode 100644 lib/iris/tests/integration/netcdf/test_general.py delete mode 100644 lib/iris/tests/integration/netcdf/test_self_referencing.py delete mode 100644 lib/iris/tests/integration/netcdf/test_thread_safety.py delete mode 100644 lib/iris/tests/integration/plot/__init__.py delete mode 100644 lib/iris/tests/integration/plot/test_animate.py delete mode 100644 lib/iris/tests/integration/plot/test_colorbar.py delete mode 100644 lib/iris/tests/integration/plot/test_netcdftime.py delete mode 100644 lib/iris/tests/integration/plot/test_nzdateline.py delete mode 100644 lib/iris/tests/integration/plot/test_plot_2d_coords.py delete mode 100644 lib/iris/tests/integration/plot/test_vector_plots.py delete mode 100755 lib/iris/tests/integration/test_Datums.py delete mode 100644 lib/iris/tests/integration/test_PartialDateTime.py delete mode 100644 lib/iris/tests/integration/test_climatology.py delete mode 100644 lib/iris/tests/integration/test_cube.py delete mode 100644 lib/iris/tests/integration/test_ff.py delete mode 100644 lib/iris/tests/integration/test_new_axis.py delete mode 100644 lib/iris/tests/integration/test_pickle.py delete mode 100644 lib/iris/tests/integration/test_pp.py delete mode 100644 lib/iris/tests/integration/test_pp_constrained_load_cubes.py delete mode 100644 lib/iris/tests/integration/test_regrid_equivalence.py delete mode 100644 lib/iris/tests/integration/test_regridding.py delete mode 100644 lib/iris/tests/integration/test_subset.py delete mode 100644 lib/iris/tests/integration/test_trajectory.py delete mode 100644 lib/iris/tests/integration/um/__init__.py delete mode 100644 lib/iris/tests/integration/um/test_fieldsfile.py delete mode 100644 lib/iris/tests/unit/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/area_weighted/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/area_weighted/test_AreaWeightedRegridder.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test__quadrant_area.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test__xy_range.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test_area_weights.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test_project.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test_rotate_grid_vectors.py delete mode 100644 lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py delete mode 100644 lib/iris/tests/unit/analysis/geometry/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/geometry/test__extract_relevant_cube_slice.py delete mode 100644 lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py delete mode 100644 lib/iris/tests/unit/analysis/interpolation/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/interpolation/test_RectilinearInterpolator.py delete mode 100644 lib/iris/tests/unit/analysis/interpolation/test_get_xy_dim_coords.py delete mode 100644 lib/iris/tests/unit/analysis/maths/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test__get_dtype.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test__inplace_common_checks.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test__output_dtype.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test_add.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test_divide.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test_multiply.py delete mode 100644 lib/iris/tests/unit/analysis/maths/test_subtract.py delete mode 100644 lib/iris/tests/unit/analysis/regrid/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py delete mode 100644 lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py delete mode 100644 lib/iris/tests/unit/analysis/scipy_interpolate/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py delete mode 100644 lib/iris/tests/unit/analysis/stats/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/stats/test_pearsonr.py delete mode 100644 lib/iris/tests/unit/analysis/test_Aggregator.py delete mode 100644 lib/iris/tests/unit/analysis/test_AreaWeighted.py delete mode 100644 lib/iris/tests/unit/analysis/test_COUNT.py delete mode 100644 lib/iris/tests/unit/analysis/test_Linear.py delete mode 100644 lib/iris/tests/unit/analysis/test_MAX.py delete mode 100755 lib/iris/tests/unit/analysis/test_MAX_RUN.py delete mode 100644 lib/iris/tests/unit/analysis/test_MEAN.py delete mode 100644 lib/iris/tests/unit/analysis/test_MIN.py delete mode 100644 lib/iris/tests/unit/analysis/test_Nearest.py delete mode 100644 lib/iris/tests/unit/analysis/test_PERCENTILE.py delete mode 100644 lib/iris/tests/unit/analysis/test_PROPORTION.py delete mode 100644 lib/iris/tests/unit/analysis/test_PercentileAggregator.py delete mode 100644 lib/iris/tests/unit/analysis/test_PointInCell.py delete mode 100644 lib/iris/tests/unit/analysis/test_RMS.py delete mode 100644 lib/iris/tests/unit/analysis/test_STD_DEV.py delete mode 100644 lib/iris/tests/unit/analysis/test_SUM.py delete mode 100644 lib/iris/tests/unit/analysis/test_VARIANCE.py delete mode 100644 lib/iris/tests/unit/analysis/test_WPERCENTILE.py delete mode 100644 lib/iris/tests/unit/analysis/test_WeightedPercentileAggregator.py delete mode 100644 lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py delete mode 100644 lib/iris/tests/unit/analysis/trajectory/__init__.py delete mode 100644 lib/iris/tests/unit/analysis/trajectory/test_Trajectory.py delete mode 100644 lib/iris/tests/unit/analysis/trajectory/test_UnstructuredNearestNeighbourRegridder.py delete mode 100644 lib/iris/tests/unit/analysis/trajectory/test__nearest_neighbour_indices_ndcoords.py delete mode 100644 lib/iris/tests/unit/analysis/trajectory/test_interpolate.py delete mode 100644 lib/iris/tests/unit/aux_factory/__init__.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_OceanSFactory.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py delete mode 100644 lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py delete mode 100644 lib/iris/tests/unit/common/__init__.py delete mode 100644 lib/iris/tests/unit/common/lenient/__init__.py delete mode 100644 lib/iris/tests/unit/common/lenient/test_Lenient.py delete mode 100644 lib/iris/tests/unit/common/lenient/test__Lenient.py delete mode 100644 lib/iris/tests/unit/common/lenient/test__lenient_client.py delete mode 100644 lib/iris/tests/unit/common/lenient/test__lenient_service.py delete mode 100644 lib/iris/tests/unit/common/lenient/test__qualname.py delete mode 100644 lib/iris/tests/unit/common/metadata/__init__.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_BaseMetadata.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_CoordMetadata.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_CubeMetadata.py delete mode 100644 lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_hexdigest.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_metadata_filter.py delete mode 100644 lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py delete mode 100644 lib/iris/tests/unit/common/mixin/__init__.py delete mode 100644 lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py delete mode 100644 lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py delete mode 100644 lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py delete mode 100644 lib/iris/tests/unit/common/resolve/__init__.py delete mode 100644 lib/iris/tests/unit/common/resolve/test_Resolve.py delete mode 100644 lib/iris/tests/unit/concatenate/__init__.py delete mode 100644 lib/iris/tests/unit/concatenate/test__CubeSignature.py delete mode 100644 lib/iris/tests/unit/concatenate/test_concatenate.py delete mode 100644 lib/iris/tests/unit/config/__init__.py delete mode 100644 lib/iris/tests/unit/config/test_NetCDF.py delete mode 100644 lib/iris/tests/unit/constraints/__init__.py delete mode 100644 lib/iris/tests/unit/constraints/test_Constraint_equality.py delete mode 100644 lib/iris/tests/unit/constraints/test_NameConstraint.py delete mode 100644 lib/iris/tests/unit/coord_categorisation/__init__.py delete mode 100644 lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py delete mode 100644 lib/iris/tests/unit/coord_categorisation/test_add_hour.py delete mode 100644 lib/iris/tests/unit/coord_systems/__init__.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_AlbersEqualArea.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_GeogCS.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_Geostationary.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_LambertAzimuthalEqualArea.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_LambertConformal.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_Mercator.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_Orthographic.py delete mode 100755 lib/iris/tests/unit/coord_systems/test_PolarStereographic.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_RotatedPole.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_Stereographic.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_TransverseMercator.py delete mode 100644 lib/iris/tests/unit/coord_systems/test_VerticalPerspective.py delete mode 100644 lib/iris/tests/unit/coords/__init__.py delete mode 100644 lib/iris/tests/unit/coords/test_AncillaryVariable.py delete mode 100644 lib/iris/tests/unit/coords/test_AuxCoord.py delete mode 100644 lib/iris/tests/unit/coords/test_Cell.py delete mode 100644 lib/iris/tests/unit/coords/test_CellMeasure.py delete mode 100644 lib/iris/tests/unit/coords/test_CellMethod.py delete mode 100644 lib/iris/tests/unit/coords/test_Coord.py delete mode 100644 lib/iris/tests/unit/coords/test_DimCoord.py delete mode 100644 lib/iris/tests/unit/coords/test__DimensionalMetadata.py delete mode 100644 lib/iris/tests/unit/cube/__init__.py delete mode 100644 lib/iris/tests/unit/cube/test_Cube.py delete mode 100644 lib/iris/tests/unit/cube/test_CubeList.py delete mode 100644 lib/iris/tests/unit/cube/test_Cube__aggregated_by.py delete mode 100644 lib/iris/tests/unit/cube/test_Cube__operators.py delete mode 100644 lib/iris/tests/unit/data_manager/__init__.py delete mode 100644 lib/iris/tests/unit/data_manager/test_DataManager.py delete mode 100644 lib/iris/tests/unit/experimental/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/raster/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/raster/test_export_geotiff.py delete mode 100644 lib/iris/tests/unit/experimental/regrid/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py delete mode 100644 lib/iris/tests/unit/experimental/regrid/test_regrid_weighted_curvilinear_to_rectilinear.py delete mode 100644 lib/iris/tests/unit/experimental/representation/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py delete mode 100644 lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py delete mode 100644 lib/iris/tests/unit/experimental/stratify/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/stratify/test_relevel.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/cf/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridGroup.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/load/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/mesh/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/mesh/test_Connectivity.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh__from_coords.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/metadata/test_ConnectivityMetadata.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshCoordMetadata.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshMetadata.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/utils/__init__.py delete mode 100644 lib/iris/tests/unit/experimental/ugrid/utils/test_recombine_submeshes.py delete mode 100644 lib/iris/tests/unit/fileformats/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/abf/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/abf/test_ABFField.py delete mode 100644 lib/iris/tests/unit/fileformats/cf/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/cf/test_CFGroup.py delete mode 100644 lib/iris/tests/unit/fileformats/cf/test_CFReader.py delete mode 100644 lib/iris/tests/unit/fileformats/dot/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/dot/test__dot_path.py delete mode 100644 lib/iris/tests/unit/fileformats/ff/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/ff/test_ArakawaC.py delete mode 100644 lib/iris/tests/unit/fileformats/ff/test_ENDGame.py delete mode 100644 lib/iris/tests/unit/fileformats/ff/test_FF2PP.py delete mode 100644 lib/iris/tests/unit/fileformats/ff/test_FFHeader.py delete mode 100644 lib/iris/tests/unit/fileformats/ff/test_Grid.py delete mode 100644 lib/iris/tests/unit/fileformats/ff/test_NewDynamics.py delete mode 100644 lib/iris/tests/unit/fileformats/name_loaders/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py delete mode 100644 lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py delete mode 100644 lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py delete mode 100644 lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py delete mode 100644 lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__latlon_dimcoords.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/engine/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/engine/test_engine.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_albers_equal_area_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cube_metadata.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_geostationary_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_azimuthal_equal_area_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_conformal_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py delete mode 100755 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_stereographic_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_transverse_mercator_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_verticalp_coordinate_system.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_attr_units.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_cf_bounds_var.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_names.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py delete mode 100755 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py delete mode 100644 lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_reorder_bounds_data.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/loader/test__get_cf_var_data.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/loader/test__load_cube.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/saver/test__FillValueMaskCheckAndStoreTarget.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/test_Saver.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/test_Saver__lazy.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/test_load_cubes.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py delete mode 100644 lib/iris/tests/unit/fileformats/netcdf/test_save.py delete mode 100644 lib/iris/tests/unit/fileformats/nimrod_load_rules/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py delete mode 100644 lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test_PPDataProxy.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test_PPField.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test__convert_constraints.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test__create_field_data.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test__field_gen.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test__interpret_field.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test_as_fields.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test_load.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test_save.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test_save_fields.py delete mode 100644 lib/iris/tests/unit/fileformats/pp/test_save_pairs_from_cube.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py delete mode 100644 lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py delete mode 100644 lib/iris/tests/unit/fileformats/rules/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/rules/test_Loader.py delete mode 100644 lib/iris/tests/unit/fileformats/rules/test__make_cube.py delete mode 100644 lib/iris/tests/unit/fileformats/structured_array_identification/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py delete mode 100644 lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py delete mode 100644 lib/iris/tests/unit/fileformats/test_rules.py delete mode 100644 lib/iris/tests/unit/fileformats/um/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/um/fast_load/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/um/fast_load/test_FieldCollation.py delete mode 100644 lib/iris/tests/unit/fileformats/um/fast_load/test__convert_collation.py delete mode 100644 lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_BasicFieldCollation.py delete mode 100644 lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_group_structured_fields.py delete mode 100644 lib/iris/tests/unit/fileformats/um/optimal_array_structuring/__init__.py delete mode 100644 lib/iris/tests/unit/fileformats/um/optimal_array_structuring/test_optimal_array_structure.py delete mode 100644 lib/iris/tests/unit/fileformats/um/test_um_to_pp.py delete mode 100644 lib/iris/tests/unit/io/__init__.py delete mode 100755 lib/iris/tests/unit/io/test__generate_cubes.py delete mode 100644 lib/iris/tests/unit/io/test_expand_filespecs.py delete mode 100644 lib/iris/tests/unit/io/test_run_callback.py delete mode 100755 lib/iris/tests/unit/io/test_save.py delete mode 100644 lib/iris/tests/unit/lazy_data/__init__.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_as_concrete_data.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_as_lazy_data.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_is_lazy_data.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py delete mode 100644 lib/iris/tests/unit/lazy_data/test_non_lazy.py delete mode 100644 lib/iris/tests/unit/merge/__init__.py delete mode 100644 lib/iris/tests/unit/merge/test_ProtoCube.py delete mode 100644 lib/iris/tests/unit/pandas/__init__.py delete mode 100644 lib/iris/tests/unit/pandas/test_pandas.py delete mode 100644 lib/iris/tests/unit/plot/__init__.py delete mode 100644 lib/iris/tests/unit/plot/_blockplot_common.py delete mode 100644 lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py delete mode 100644 lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py delete mode 100644 lib/iris/tests/unit/plot/test__fixup_dates.py delete mode 100644 lib/iris/tests/unit/plot/test__get_plot_defn.py delete mode 100644 lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py delete mode 100644 lib/iris/tests/unit/plot/test__get_plot_objects.py delete mode 100644 lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py delete mode 100644 lib/iris/tests/unit/plot/test_contour.py delete mode 100644 lib/iris/tests/unit/plot/test_contourf.py delete mode 100644 lib/iris/tests/unit/plot/test_outline.py delete mode 100644 lib/iris/tests/unit/plot/test_pcolor.py delete mode 100644 lib/iris/tests/unit/plot/test_pcolormesh.py delete mode 100644 lib/iris/tests/unit/plot/test_plot.py delete mode 100644 lib/iris/tests/unit/plot/test_points.py delete mode 100644 lib/iris/tests/unit/plot/test_scatter.py delete mode 100644 lib/iris/tests/unit/quickplot/__init__.py delete mode 100644 lib/iris/tests/unit/quickplot/test_contour.py delete mode 100644 lib/iris/tests/unit/quickplot/test_contourf.py delete mode 100644 lib/iris/tests/unit/quickplot/test_outline.py delete mode 100644 lib/iris/tests/unit/quickplot/test_pcolor.py delete mode 100644 lib/iris/tests/unit/quickplot/test_pcolormesh.py delete mode 100644 lib/iris/tests/unit/quickplot/test_plot.py delete mode 100644 lib/iris/tests/unit/quickplot/test_points.py delete mode 100644 lib/iris/tests/unit/quickplot/test_scatter.py delete mode 100644 lib/iris/tests/unit/representation/__init__.py delete mode 100644 lib/iris/tests/unit/representation/cube_printout/__init__.py delete mode 100644 lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py delete mode 100644 lib/iris/tests/unit/representation/cube_printout/test_Table.py delete mode 100644 lib/iris/tests/unit/representation/cube_summary/__init__.py delete mode 100644 lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py delete mode 100644 lib/iris/tests/unit/test_Future.py delete mode 100644 lib/iris/tests/unit/test_sample_data_path.py delete mode 100644 lib/iris/tests/unit/tests/__init__.py delete mode 100644 lib/iris/tests/unit/tests/stock/__init__.py delete mode 100644 lib/iris/tests/unit/tests/stock/test_netcdf.py delete mode 100644 lib/iris/tests/unit/tests/test_IrisTest.py delete mode 100644 lib/iris/tests/unit/time/__init__.py delete mode 100644 lib/iris/tests/unit/time/test_PartialDateTime.py delete mode 100644 lib/iris/tests/unit/util/__init__.py delete mode 100644 lib/iris/tests/unit/util/test__coord_regular.py delete mode 100644 lib/iris/tests/unit/util/test__is_circular.py delete mode 100644 lib/iris/tests/unit/util/test__mask_array.py delete mode 100644 lib/iris/tests/unit/util/test__slice_data_with_keys.py delete mode 100644 lib/iris/tests/unit/util/test_array_equal.py delete mode 100644 lib/iris/tests/unit/util/test_broadcast_to_shape.py delete mode 100644 lib/iris/tests/unit/util/test_column_slices_generator.py delete mode 100644 lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py delete mode 100644 lib/iris/tests/unit/util/test_describe_diff.py delete mode 100644 lib/iris/tests/unit/util/test_equalise_attributes.py delete mode 100644 lib/iris/tests/unit/util/test_file_is_newer_than.py delete mode 100644 lib/iris/tests/unit/util/test_find_discontiguities.py delete mode 100644 lib/iris/tests/unit/util/test_mask_cube.py delete mode 100644 lib/iris/tests/unit/util/test_new_axis.py delete mode 100644 lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py delete mode 100644 lib/iris/tests/unit/util/test_reverse.py delete mode 100644 lib/iris/tests/unit/util/test_rolling_window.py delete mode 100644 lib/iris/tests/unit/util/test_squeeze.py delete mode 100644 lib/iris/tests/unit/util/test_unify_time_units.py diff --git a/lib/iris/tests/integration/__init__.py b/lib/iris/tests/integration/__init__.py deleted file mode 100644 index 71b911cbb0..0000000000 --- a/lib/iris/tests/integration/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for the :mod:`iris` package.""" diff --git a/lib/iris/tests/integration/analysis/__init__.py b/lib/iris/tests/integration/analysis/__init__.py deleted file mode 100644 index 20b6250b70..0000000000 --- a/lib/iris/tests/integration/analysis/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for the :mod:`iris.analysis` package.""" diff --git a/lib/iris/tests/integration/analysis/test_area_weighted.py b/lib/iris/tests/integration/analysis/test_area_weighted.py deleted file mode 100644 index d01da79a56..0000000000 --- a/lib/iris/tests/integration/analysis/test_area_weighted.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for area weighted regridding.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris -from iris.analysis import AreaWeighted - - -@tests.skip_data -class AreaWeightedTests(tests.IrisTest): - def setUp(self): - # Prepare a cube and a template - - cube_file_path = tests.get_data_path( - ["NetCDF", "regrid", "regrid_xyt.nc"] - ) - self.cube = iris.load_cube(cube_file_path) - - template_file_path = tests.get_data_path( - ["NetCDF", "regrid", "regrid_template_global_latlon.nc"] - ) - self.template_cube = iris.load_cube(template_file_path) - - def test_regrid_area_w_lazy(self): - # Regrid the cube onto the template. - out = self.cube.regrid(self.template_cube, AreaWeighted()) - # Check data is still lazy - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(out.has_lazy_data()) - # Save the data - with self.temp_filename(suffix=".nc") as fname: - iris.save(out, fname) - - def test_regrid_area_w_lazy_chunked(self): - # Chunked data makes the regridder run repeatedly - self.cube.data = self.cube.lazy_data().rechunk((1, -1, -1)) - # Regrid the cube onto the template. - out = self.cube.regrid(self.template_cube, AreaWeighted()) - # Check data is still lazy - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(out.has_lazy_data()) - # Save the data - with self.temp_filename(suffix=".nc") as fname: - iris.save(out, fname) - - def test_regrid_area_w_real_save(self): - real_cube = self.cube.copy() - real_cube.data - # Regrid the cube onto the template. - out = real_cube.regrid(self.template_cube, AreaWeighted()) - # Realise the data - out.data - # Save the data - with self.temp_filename(suffix=".nc") as fname: - iris.save(out, fname) - - def test_regrid_area_w_real_start(self): - real_cube = self.cube.copy() - real_cube.data - # Regrid the cube onto the template. - out = real_cube.regrid(self.template_cube, AreaWeighted()) - # Save the data - with self.temp_filename(suffix=".nc") as fname: - iris.save(out, fname) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/aux_factory/__init__.py b/lib/iris/tests/integration/aux_factory/__init__.py deleted file mode 100644 index 58ba6fb82b..0000000000 --- a/lib/iris/tests/integration/aux_factory/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for the :mod:`iris.aux_factory` package.""" diff --git a/lib/iris/tests/integration/aux_factory/test_OceanSigmaZFactory.py b/lib/iris/tests/integration/aux_factory/test_OceanSigmaZFactory.py deleted file mode 100644 index 4b2464b272..0000000000 --- a/lib/iris/tests/integration/aux_factory/test_OceanSigmaZFactory.py +++ /dev/null @@ -1,196 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Integratation tests for the -`iris.aux_factory.OceanSigmaZFactory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import itertools - -import numpy as np - -from iris._lazy_data import as_lazy_data -from iris.tests.stock import ocean_sigma_z as stock_sample_osz -import iris.util - - -class Test_sample(tests.IrisTest): - def setUp(self): - self.cube = stock_sample_osz() - # Snapshot result, printed with ... - # >>> np.set_printoptions(linewidth=180, - # formatter={'float':lambda x:'{:-09.3f}'.format(x)}) - # >>> print(repr(coord.points)) - self.basic_derived_result = np.array( - [ - [ - [ - [-0000.632, -0000.526, -0000.421, -0000.316], - [-0000.789, -0000.684, -0000.579, -0000.474], - [-0000.947, -0000.842, -0000.737, -0000.632], - ], - [ - [-0014.358, -0014.264, -0014.169, -0014.074], - [-0014.501, -0014.406, -0014.311, -0014.216], - [-0014.643, -0014.548, -0014.453, -0014.358], - ], - [ - [-0082.993, -0082.951, -0082.908, -0082.866], - [-0083.056, -0083.014, -0082.972, -0082.929], - [-0083.119, -0083.077, -0083.035, -0082.993], - ], - [ - [-0368.400, -0368.400, -0368.400, -0368.400], - [-0368.400, -0368.400, -0368.400, -0368.400], - [-0368.400, -0368.400, -0368.400, -0368.400], - ], - [ - [-1495.600, -1495.600, -1495.600, -1495.600], - [-1495.600, -1495.600, -1495.600, -1495.600], - [-1495.600, -1495.600, -1495.600, -1495.600], - ], - ], - [ - [ - [-0000.842, -0000.737, -0000.632, -0000.526], - [-0001.000, -0000.895, -0000.789, -0000.684], - [-0001.158, -0001.053, -0000.947, -0000.842], - ], - [ - [-0014.548, -0014.453, -0014.358, -0014.264], - [-0014.690, -0014.595, -0014.501, -0014.406], - [-0014.832, -0014.737, -0014.643, -0014.548], - ], - [ - [-0083.077, -0083.035, -0082.993, -0082.951], - [-0083.140, -0083.098, -0083.056, -0083.014], - [-0083.203, -0083.161, -0083.119, -0083.077], - ], - [ - [-0368.400, -0368.400, -0368.400, -0368.400], - [-0368.400, -0368.400, -0368.400, -0368.400], - [-0368.400, -0368.400, -0368.400, -0368.400], - ], - [ - [-1495.600, -1495.600, -1495.600, -1495.600], - [-1495.600, -1495.600, -1495.600, -1495.600], - [-1495.600, -1495.600, -1495.600, -1495.600], - ], - ], - ] - ) - - self.derived_coord_name = ( - "sea_surface_height_above_reference_ellipsoid" - ) - - def _check_result(self, cube, expected_result=None, **kwargs): - if expected_result is None: - expected_result = self.basic_derived_result - coord = cube.coord(self.derived_coord_name) - result = coord.points - self.assertArrayAllClose(result, expected_result, atol=0.005, **kwargs) - - def test_basic(self): - self._check_result(self.cube) - - def _lazy_testcube(self): - cube = self.cube - for dep_name in ("depth", "layer_depth", "ocean_sigma_z_coordinate"): - coord = cube.coord(dep_name) - coord.points = as_lazy_data(coord.points, coord.shape) - return cube - - def test_nonlazy_cube_has_lazy_derived(self): - # Check same results when key coords are made lazy. - cube = self.cube - self.assertEqual(cube.coord("depth").has_lazy_points(), False) - self.assertEqual( - cube.coord(self.derived_coord_name).has_lazy_points(), True - ) - - def test_lazy_cube_same_result(self): - cube = self._lazy_testcube() - self.assertEqual(cube.coord("depth").has_lazy_points(), True) - self.assertEqual( - cube.coord(self.derived_coord_name).has_lazy_points(), True - ) - self._check_result(cube) - - def test_transpose(self): - # Check it works with all possible dimension orders. - for dims_list in itertools.permutations(range(self.cube.ndim)): - cube = self.cube.copy() - cube.transpose(dims_list) - expected = self.basic_derived_result.transpose(dims_list) - msg = "Unexpected result when cube transposed by {}" - msg = msg.format(dims_list) - self._check_result(cube, expected, err_msg=msg) - - def test_lazy_transpose(self): - # Check lazy calc works with all possible dimension orders. - for dims_list in itertools.permutations(range(self.cube.ndim)): - cube = self._lazy_testcube().copy() - cube.transpose(dims_list) - expected = self.basic_derived_result.transpose(dims_list) - msg = "Unexpected result when cube transposed by {}" - msg = msg.format(dims_list) - self._check_result(cube, expected, err_msg=msg) - - def test_extra_dims(self): - # Insert some extra cube dimensions + check it still works. - cube = self.cube - cube = iris.util.new_axis(cube) - cube = iris.util.new_axis(cube) - cube = iris.util.new_axis(cube) - # N.B. shape is now (1, 1, 1, t, z, y, x) - cube.transpose((0, 3, 1, 4, 5, 2, 6)) - # N.B. shape is now (1, t, 1, z, y, 1, x) - # Should get same original result, as derived dims are the same. - self._check_result(cube) - - def test_no_sigma(self): - # Check it still works when 'sigma' is removed. - # NOTE: the unit test for this does not cover all cases because it - # doesn't provide a time dimension. - - # Set all sigma points to zero + snapshot the resulting derived points. - trial_cube = self.cube.copy() - trial_cube.coord("ocean_sigma_z_coordinate").points[:] = 0.0 - expected = trial_cube.coord(self.derived_coord_name).points - - # Remove sigma altogether + check the result is the same. - cube = self.cube - cube.remove_coord("ocean_sigma_z_coordinate") - self._check_result(cube, expected) - - def test_no_eta(self): - # Check it still works when 'eta' is removed. - # NOTE: the unit test for this does not cover all cases because it - # doesn't provide a time dimension. - - # Set all sigma points to zero + snapshot the resulting derived points. - trial_cube = self.cube.copy() - trial_cube.coord("sea_surface_height").points[:] = 0.0 - expected = trial_cube.coord(self.derived_coord_name).points - # Check this has no variation between the two timepoints. - self.assertArrayAllClose(expected[0], expected[1]) - # Take first time, as no sigma --> result *has* no time dimension. - expected = expected[0] - - # Remove eta altogether + check the result is the same. - cube = self.cube - cube.remove_coord("sea_surface_height") - self._check_result(cube, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/concatenate/__init__.py b/lib/iris/tests/integration/concatenate/__init__.py deleted file mode 100644 index fb136098ee..0000000000 --- a/lib/iris/tests/integration/concatenate/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for the :mod:`iris._concatenate` package.""" diff --git a/lib/iris/tests/integration/concatenate/test_concatenate.py b/lib/iris/tests/integration/concatenate/test_concatenate.py deleted file mode 100644 index 091ecd4378..0000000000 --- a/lib/iris/tests/integration/concatenate/test_concatenate.py +++ /dev/null @@ -1,327 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Integration tests for concatenating cubes with differing time coord epochs -using :func:`iris.util.unify_time_units`. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -import cf_units -import numpy as np - -from iris._concatenate import concatenate -import iris.coords -import iris.cube -import iris.tests.stock as stock -from iris.util import unify_time_units - - -class Test_concatenate__epoch(tests.IrisTest): - def simple_1d_time_cubes(self, reftimes, coords_points): - cubes = [] - data_points = [273, 275, 278, 277, 274] - for reftime, coord_points in zip(reftimes, coords_points): - cube = iris.cube.Cube( - np.array(data_points, dtype=np.float32), - standard_name="air_temperature", - units="K", - ) - unit = cf_units.Unit(reftime, calendar="standard") - coord = iris.coords.DimCoord( - points=np.array(coord_points, dtype=np.float32), - standard_name="time", - units=unit, - ) - cube.add_dim_coord(coord, 0) - cubes.append(cube) - return cubes - - def test_concat_1d_with_differing_time_units(self): - reftimes = [ - "hours since 1970-01-01 00:00:00", - "hours since 1970-01-02 00:00:00", - ] - coords_points = [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]] - cubes = self.simple_1d_time_cubes(reftimes, coords_points) - unify_time_units(cubes) - result = concatenate(cubes) - self.assertEqual(len(result), 1) - self.assertEqual(result[0].shape, (10,)) - - -class Test_cubes_with_aux_coord(tests.IrisTest): - def create_cube(self): - data = np.arange(4).reshape(2, 2) - - lat = iris.coords.DimCoord( - [0, 30], standard_name="latitude", units="degrees" - ) - lon = iris.coords.DimCoord( - [0, 15], standard_name="longitude", units="degrees" - ) - height = iris.coords.AuxCoord([1.5], standard_name="height", units="m") - t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="standard" - ) - time = iris.coords.DimCoord([0, 6], standard_name="time", units=t_unit) - - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - cube.add_dim_coord(time, 0) - cube.add_dim_coord(lat, 1) - cube.add_aux_coord(lon, 1) - cube.add_aux_coord(height) - return cube - - def test_diff_aux_coord(self): - cube_a = self.create_cube() - cube_b = cube_a.copy() - cube_b.coord("time").points = [12, 18] - cube_b.coord("longitude").points = [120, 150] - - result = concatenate([cube_a, cube_b]) - self.assertEqual(len(result), 2) - - def test_ignore_diff_aux_coord(self): - cube_a = self.create_cube() - cube_b = cube_a.copy() - cube_b.coord("time").points = [12, 18] - cube_b.coord("longitude").points = [120, 150] - - result = concatenate([cube_a, cube_b], check_aux_coords=False) - self.assertEqual(len(result), 1) - self.assertEqual(result[0].shape, (4, 2)) - - -class Test_cubes_with_cell_measure(tests.IrisTest): - def create_cube(self): - data = np.arange(4).reshape(2, 2) - - lat = iris.coords.DimCoord( - [0, 30], standard_name="latitude", units="degrees" - ) - volume = iris.coords.CellMeasure( - [0, 15], measure="volume", long_name="volume" - ) - area = iris.coords.CellMeasure( - [1.5], standard_name="height", units="m" - ) - t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="standard" - ) - time = iris.coords.DimCoord([0, 6], standard_name="time", units=t_unit) - - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - cube.add_dim_coord(time, 0) - cube.add_dim_coord(lat, 1) - cube.add_cell_measure(volume, 1) - cube.add_cell_measure(area) - return cube - - def test_diff_cell_measure(self): - cube_a = self.create_cube() - cube_b = cube_a.copy() - cube_b.coord("time").points = [12, 18] - cube_b.cell_measure("volume").data = [120, 150] - - result = concatenate([cube_a, cube_b]) - self.assertEqual(len(result), 2) - - def test_ignore_diff_cell_measure(self): - cube_a = self.create_cube() - cube_b = cube_a.copy() - cube_b.coord("time").points = [12, 18] - cube_b.cell_measure("volume").data = [120, 150] - - result = concatenate([cube_a, cube_b], check_cell_measures=False) - self.assertEqual(len(result), 1) - self.assertEqual(result[0].shape, (4, 2)) - - -class Test_cubes_with_ancillary_variables(tests.IrisTest): - def create_cube(self): - data = np.arange(4).reshape(2, 2) - - lat = iris.coords.DimCoord( - [0, 30], standard_name="latitude", units="degrees" - ) - quality = iris.coords.AncillaryVariable([0, 15], long_name="quality") - height = iris.coords.AncillaryVariable( - [1.5], standard_name="height", units="m" - ) - t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="standard" - ) - time = iris.coords.DimCoord([0, 6], standard_name="time", units=t_unit) - - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - cube.add_dim_coord(time, 0) - cube.add_dim_coord(lat, 1) - cube.add_ancillary_variable(quality, 1) - cube.add_ancillary_variable(height) - return cube - - def test_diff_ancillary_variables(self): - cube_a = self.create_cube() - cube_b = cube_a.copy() - cube_b.coord("time").points = [12, 18] - cube_b.ancillary_variable("quality").data = [120, 150] - - result = concatenate([cube_a, cube_b]) - self.assertEqual(len(result), 2) - - def test_ignore_diff_ancillary_variables(self): - cube_a = self.create_cube() - cube_b = cube_a.copy() - cube_b.coord("time").points = [12, 18] - cube_b.ancillary_variable("quality").data = [120, 150] - - result = concatenate([cube_a, cube_b], check_ancils=False) - self.assertEqual(len(result), 1) - self.assertEqual(result[0].shape, (4, 2)) - - -class Test_anonymous_dims(tests.IrisTest): - def setUp(self): - data = np.arange(12).reshape(2, 3, 2) - self.cube = iris.cube.Cube( - data, standard_name="air_temperature", units="K" - ) - - # Time coord - t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="standard" - ) - t_coord = iris.coords.DimCoord( - [0, 6], standard_name="time", units=t_unit - ) - self.cube.add_dim_coord(t_coord, 0) - - # Lats and lons - self.x_coord = iris.coords.DimCoord( - [15, 30], standard_name="longitude", units="degrees" - ) - self.y_coord = iris.coords.DimCoord( - [0, 30, 60], standard_name="latitude", units="degrees" - ) - self.x_coord_2D = iris.coords.AuxCoord( - [[0, 15], [30, 45], [60, 75]], - standard_name="longitude", - units="degrees", - ) - self.y_coord_non_monotonic = iris.coords.AuxCoord( - [0, 30, 15], standard_name="latitude", units="degrees" - ) - - def test_matching_2d_longitudes(self): - cube1 = self.cube - cube1.add_dim_coord(self.y_coord, 1) - cube1.add_aux_coord(self.x_coord_2D, (1, 2)) - - cube2 = cube1.copy() - cube2.coord("time").points = [12, 18] - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 1) - - def test_differing_2d_longitudes(self): - cube1 = self.cube - cube1.add_aux_coord(self.y_coord, 1) - cube1.add_aux_coord(self.x_coord_2D, (1, 2)) - - cube2 = cube1.copy() - cube2.coord("time").points = [12, 18] - cube2.coord("longitude").points = [[-30, -15], [0, 15], [30, 45]] - - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 2) - - def test_matching_non_monotonic_latitudes(self): - cube1 = self.cube - cube1.add_aux_coord(self.y_coord_non_monotonic, 1) - cube1.add_aux_coord(self.x_coord, 2) - - cube2 = cube1.copy() - cube2.coord("time").points = [12, 18] - - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 1) - - def test_differing_non_monotonic_latitudes(self): - cube1 = self.cube - cube1.add_aux_coord(self.y_coord_non_monotonic, 1) - cube1.add_aux_coord(self.x_coord, 2) - - cube2 = cube1.copy() - cube2.coord("time").points = [12, 18] - cube2.coord("latitude").points = [30, 0, 15] - - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 2) - - def test_concatenate_along_anon_dim(self): - cube1 = self.cube - cube1.add_aux_coord(self.y_coord_non_monotonic, 1) - cube1.add_aux_coord(self.x_coord, 2) - - cube2 = cube1.copy() - cube2.coord("latitude").points = [30, 0, 15] - - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 2) - - -class Test_anonymous_dims_alternate_mapping(tests.IrisTest): - # Ensure that anonymous concatenation is not sensitive to dimension mapping - # of the anonymous dimension. - def setUp(self): - self.cube = stock.simple_3d() - coord = self.cube.coord("wibble") - self.cube.remove_coord(coord) - self.cube.add_aux_coord(coord, 0) - - def test_concatenate_anom_1st_dim(self): - # Check that concatenation along a non anonymous dimension is - # insensitive to the dimension which is anonymous. - # Concatenate along longitude. - # DIM: cube(--, lat, lon) & cube(--, lat, lon') - # AUX: cube(wibble, --, --) & cube(wibble, --, --) - cube1 = self.cube[..., :2] - cube2 = self.cube[..., 2:] - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 1) - - def test_concatenate_anom_2nd_dim(self): - # Check that concatenation along a non anonymous dimension is - # insensitive to the dimension which is anonymous. - # Concatenate along longitude. - # DIM: cube(lon, --, lat) & cube(lon', ---, lat) - # AUX: cube(--, wibble, --) & cube(--, wibble, --) - cube1 = self.cube[..., :2] - cube2 = self.cube[..., 2:] - cube1.transpose((2, 0, 1)) - cube2.transpose((2, 0, 1)) - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 1) - - def test_concatenate_anom_3rd_dim(self): - # Check that concatenation along a non anonymous dimension is - # insensitive to the dimension which is anonymous. - # Concatenate along longitude. - # DIM: cube(lat, lon, --) & cube(lat, lon', --) - # AUX: cube(--, --, wibble) & cube(--, --, wibble) - cube1 = self.cube[..., :2] - cube2 = self.cube[..., 2:] - cube1.transpose((1, 2, 0)) - cube2.transpose((1, 2, 0)) - result = concatenate([cube1, cube2]) - self.assertEqual(len(result), 1) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/experimental/__init__.py b/lib/iris/tests/integration/experimental/__init__.py deleted file mode 100644 index 269cf3dd9a..0000000000 --- a/lib/iris/tests/integration/experimental/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for the :mod:`iris.experimental` package.""" diff --git a/lib/iris/tests/integration/experimental/test_CubeRepresentation.py b/lib/iris/tests/integration/experimental/test_CubeRepresentation.py deleted file mode 100644 index 48a3e51b52..0000000000 --- a/lib/iris/tests/integration/experimental/test_CubeRepresentation.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for cube html representation.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from html import escape - -import numpy as np - -from iris.cube import Cube -from iris.experimental.representation import CubeRepresentation -import iris.tests.stock as stock - - -@tests.skip_data -class TestNoMetadata(tests.IrisTest): - # Test the situation where we have a cube with no metadata at all. - def setUp(self): - self.shape = (2, 3, 4) - self.cube = Cube(np.arange(24).reshape(self.shape)) - self.representer = CubeRepresentation(self.cube) - self.representer.repr_html() - - def test_cube_name(self): - expected = "Unknown" # This cube has no metadata. - result = self.representer.name - self.assertEqual(expected, result) - - def test_cube_units(self): - expected = "unknown" # This cube has no metadata. - result = self.representer.units - self.assertEqual(expected, result) - - def test_dim_names(self): - expected = ["--"] * len(self.shape) - result = self.representer.names - self.assertEqual(expected, result) - - def test_shape(self): - result = self.representer.shapes - self.assertEqual(result, self.shape) - - -@tests.skip_data -class TestMissingMetadata(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_3d() - - def test_no_coords(self): - all_coords = [coord.name() for coord in self.cube.coords()] - for coord in all_coords: - self.cube.remove_coord(coord) - representer = CubeRepresentation(self.cube) - result = representer.repr_html().lower() - self.assertNotIn("dimension coordinates", result) - self.assertNotIn("auxiliary coordinates", result) - self.assertNotIn("scalar coordinates", result) - self.assertIn("attributes", result) - - def test_no_dim_coords(self): - dim_coords = [c.name() for c in self.cube.coords(dim_coords=True)] - for coord in dim_coords: - self.cube.remove_coord(coord) - representer = CubeRepresentation(self.cube) - result = representer.repr_html().lower() - self.assertNotIn("dimension coordinates", result) - self.assertIn("auxiliary coordinates", result) - self.assertIn("scalar coordinates", result) - self.assertIn("attributes", result) - - def test_no_aux_coords(self): - aux_coords = ["forecast_period"] - for coord in aux_coords: - self.cube.remove_coord(coord) - representer = CubeRepresentation(self.cube) - result = representer.repr_html().lower() - self.assertIn("dimension coordinates", result) - self.assertNotIn("auxiliary coordinates", result) - self.assertIn("scalar coordinates", result) - self.assertIn("attributes", result) - - def test_no_scalar_coords(self): - aux_coords = ["air_pressure"] - for coord in aux_coords: - self.cube.remove_coord(coord) - representer = CubeRepresentation(self.cube) - result = representer.repr_html().lower() - self.assertIn("dimension coordinates", result) - self.assertIn("auxiliary coordinates", result) - self.assertNotIn("scalar coordinates", result) - self.assertIn("attributes", result) - - def test_no_attrs(self): - self.cube.attributes = {} - representer = CubeRepresentation(self.cube) - result = representer.repr_html().lower() - self.assertIn("dimension coordinates", result) - self.assertIn("auxiliary coordinates", result) - self.assertIn("scalar coordinates", result) - self.assertNotIn("attributes", result) - - def test_no_cell_methods(self): - representer = CubeRepresentation(self.cube) - result = representer.repr_html().lower() - self.assertNotIn("cell methods", result) - - -@tests.skip_data -class TestScalarCube(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_3d()[0, 0, 0] - self.representer = CubeRepresentation(self.cube) - self.representer.repr_html() - - def test_identfication(self): - # Is this scalar cube accurately identified? - self.assertTrue(self.representer.scalar_cube) - - def test_header__name(self): - header = self.representer._make_header() - expected_name = escape(self.cube.name().title().replace("_", " ")) - self.assertIn(expected_name, header) - - def test_header__units(self): - header = self.representer._make_header() - expected_units = escape(self.cube.units.symbol) - self.assertIn(expected_units, header) - - def test_header__scalar_str(self): - # Check that 'scalar cube' is placed in the header. - header = self.representer._make_header() - expected_str = "(scalar cube)" - self.assertIn(expected_str, header) - - def test_content__scalars(self): - # Check an element "Scalar coordinates" is present in the main content. - content = self.representer._make_content() - expected_str = "Scalar coordinates" - self.assertIn(expected_str, content) - - def test_content__specific_scalar_coord(self): - # Check a specific scalar coord is present in the main content. - content = self.representer._make_content() - expected_coord = self.cube.coords()[0] - expected_coord_name = escape(expected_coord.name()) - self.assertIn(expected_coord_name, content) - expected_coord_val = escape(str(expected_coord.points[0])) - self.assertIn(expected_coord_val, content) - - def test_content__attributes(self): - # Check an element "attributes" is present in the main content. - content = self.representer._make_content() - expected_str = "Attributes" - self.assertIn(expected_str, content) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py b/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py deleted file mode 100644 index 742adc8c15..0000000000 --- a/lib/iris/tests/integration/experimental/test_regrid_ProjectedUnstructured.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for experimental regridding.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest - -import cartopy.crs as ccrs -from cf_units import Unit -import numpy as np - -import iris -import iris.aux_factory -from iris.coord_systems import GeogCS -from iris.experimental.regrid import ( - ProjectedUnstructuredLinear, - ProjectedUnstructuredNearest, -) -from iris.tests.stock import global_pp - - -@tests.skip_data -class TestProjectedUnstructured(tests.IrisTest): - def setUp(self): - path = tests.get_data_path( - ("NetCDF", "unstructured_grid", "theta_nodal_xios.nc") - ) - self.src = iris.load_cube(path, "Potential Temperature") - - src_lat = self.src.coord("latitude") - src_lon = self.src.coord("longitude") - src_lat.coord_system = src_lon.coord_system = GeogCS(6370000) - src_lat.convert_units(Unit("degrees")) - src_lon.convert_units(Unit("degrees")) - - self.global_grid = global_pp() - - def test_nearest(self): - res = self.src.regrid(self.global_grid, ProjectedUnstructuredNearest()) - self.assertArrayShapeStats( - res, (1, 6, 73, 96), 315.8913582, 11.00063922733, rtol=1e-8 - ) - self.assertArrayShapeStats( - res[:, 0], (1, 73, 96), 299.99993826, 3.9226378869e-5 - ) - - def test_nearest_sinusoidal(self): - crs = ccrs.Sinusoidal() - res = self.src.regrid( - self.global_grid, ProjectedUnstructuredNearest(crs) - ) - self.assertArrayShapeStats( - res, (1, 6, 73, 96), 315.891358296, 11.000639227, rtol=1e-8 - ) - self.assertArrayShapeStats( - res[:, 0], (1, 73, 96), 299.99993826, 3.9223839688e-5 - ) - - @unittest.skip( - "Deprecated API and provenance of reference numbers unknown." - ) - def test_nearest_gnomonic_uk_domain(self): - crs = ccrs.Gnomonic(central_latitude=60.0) - uk_grid = self.global_grid.intersection( - longitude=(-20, 20), latitude=(40, 80) - ) - res = self.src.regrid(uk_grid, ProjectedUnstructuredNearest(crs)) - - self.assertArrayShapeStats( - res, - (1, 6, 17, 11), - 315.8854720963427, - 11.000539210625737, - rtol=1e-8, - ) - self.assertArrayShapeStats( - res[:, 0], - (1, 17, 11), - 299.9999985207442, - 3.53574517015874e-05, - ) - expected_subset = np.array( - [ - [318.92881733, 318.92881733, 318.92881733], - [318.92881733, 318.92881733, 318.92881733], - [318.92881733, 318.92881733, 318.92881733], - ] - ) - self.assertArrayAlmostEqual( - expected_subset, res.data[0, 3, 5:8, 4:7].data - ) - - def test_nearest_aux_factories(self): - src = self.src - - (xy_dim_len,) = src.coord(axis="X").shape - (z_dim_len,) = src.coord("levels").shape - - src.add_aux_coord( - iris.coords.AuxCoord( - np.arange(z_dim_len) + 40, long_name="level_height", units="m" - ), - 1, - ) - src.add_aux_coord( - iris.coords.AuxCoord( - np.arange(z_dim_len) + 50, long_name="sigma", units="1" - ), - 1, - ) - src.add_aux_coord( - iris.coords.AuxCoord( - np.arange(xy_dim_len) + 100, - long_name="surface_altitude", - units="m", - ), - 2, - ) - src.add_aux_factory( - iris.aux_factory.HybridHeightFactory( - delta=src.coord("level_height"), - sigma=src.coord("sigma"), - orography=src.coord("surface_altitude"), - ) - ) - res = src.regrid(self.global_grid, ProjectedUnstructuredNearest()) - - self.assertArrayShapeStats( - res, (1, 6, 73, 96), 315.8913582, 11.000639227334, rtol=1e-8 - ) - self.assertArrayShapeStats( - res[:, 0], (1, 73, 96), 299.99993826, 3.9226378869e-5 - ) - self.assertEqual(res.coord("altitude").shape, (6, 73, 96)) - - def test_linear_sinusoidal(self): - res = self.src.regrid(self.global_grid, ProjectedUnstructuredLinear()) - self.assertArrayShapeStats( - res, (1, 6, 73, 96), 315.8914839, 11.0006338412, rtol=1e-8 - ) - self.assertArrayShapeStats( - res[:, 0], (1, 73, 96), 299.99993826, 3.775024069e-5 - ) - expected_subset = np.array( - [ - [299.999987, 299.999996, 299.999999], - [299.999984, 299.999986, 299.999988], - [299.999973, 299.999977, 299.999982], - ] - ) - self.assertArrayAlmostEqual( - expected_subset, res.data[0, 0, 20:23, 40:43].data - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/experimental/test_ugrid_load.py b/lib/iris/tests/integration/experimental/test_ugrid_load.py deleted file mode 100644 index af97458ded..0000000000 --- a/lib/iris/tests/integration/experimental/test_ugrid_load.py +++ /dev/null @@ -1,247 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Integration tests for NetCDF-UGRID file loading. - -todo: fold these tests into netcdf tests when experimental.ugrid is folded into - standard behaviour. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from collections.abc import Iterable - -from iris import Constraint, load -from iris.experimental.ugrid import logger -from iris.experimental.ugrid.load import ( - PARSE_UGRID_ON_LOAD, - load_mesh, - load_meshes, -) -from iris.experimental.ugrid.mesh import Mesh -from iris.tests.stock.netcdf import ( - _file_from_cdl_template as create_file_from_cdl_template, -) -from iris.tests.unit.tests.stock.test_netcdf import XIOSFileMixin - - -def ugrid_load(uris, constraints=None, callback=None): - # TODO: remove constraint once files no longer have orphan connectivities. - orphan_connectivities = ( - "Mesh2d_half_levels_edge_face_links", - "Mesh2d_half_levels_face_links", - "Mesh2d_half_levels_face_edges", - "Mesh2d_full_levels_edge_face_links", - "Mesh2d_full_levels_face_links", - "Mesh2d_full_levels_face_edges", - ) - filter_orphan_connectivities = Constraint( - cube_func=lambda cube: cube.var_name not in orphan_connectivities - ) - if constraints is None: - constraints = filter_orphan_connectivities - else: - if not isinstance(constraints, Iterable): - constraints = [constraints] - constraints.append(filter_orphan_connectivities) - - with PARSE_UGRID_ON_LOAD.context(): - return load(uris, constraints, callback) - - -@tests.skip_data -class TestBasic(tests.IrisTest): - def common_test(self, load_filename, assert_filename): - cube_list = ugrid_load( - tests.get_data_path( - ["NetCDF", "unstructured_grid", load_filename] - ), - ) - self.assertEqual(1, len(cube_list)) - cube = cube_list[0] - self.assertCML(cube, ["experimental", "ugrid", assert_filename]) - - def test_2D_1t_face_half_levels(self): - self.common_test( - "lfric_ngvat_2D_1t_face_half_levels_main_conv_rain.nc", - "2D_1t_face_half_levels.cml", - ) - - def test_3D_1t_face_half_levels(self): - self.common_test( - "lfric_ngvat_3D_1t_half_level_face_grid_derived_theta_in_w3.nc", - "3D_1t_face_half_levels.cml", - ) - - def test_3D_1t_face_full_levels(self): - self.common_test( - "lfric_ngvat_3D_1t_full_level_face_grid_main_area_fraction_unit1.nc", - "3D_1t_face_full_levels.cml", - ) - - def test_2D_72t_face_half_levels(self): - self.common_test( - "lfric_ngvat_2D_72t_face_half_levels_main_conv_rain.nc", - "2D_72t_face_half_levels.cml", - ) - - def test_3D_snow_pseudo_levels(self): - self.common_test( - "lfric_ngvat_3D_snow_pseudo_levels_1t_face_half_levels_main_snow_layer_temp.nc", - "3D_snow_pseudo_levels.cml", - ) - - def test_3D_soil_pseudo_levels(self): - self.common_test( - "lfric_ngvat_3D_soil_pseudo_levels_1t_face_half_levels_main_soil_temperature.nc", - "3D_soil_pseudo_levels.cml", - ) - - def test_3D_tile_pseudo_levels(self): - self.common_test( - "lfric_ngvat_3D_tile_pseudo_levels_1t_face_half_levels_main_sw_up_tile.nc", - "3D_tile_pseudo_levels.cml", - ) - - def test_3D_veg_pseudo_levels(self): - self.common_test( - "lfric_ngvat_3D_veg_pseudo_levels_1t_face_half_levels_main_snowpack_density.nc", - "3D_veg_pseudo_levels.cml", - ) - - def test_no_mesh(self): - with PARSE_UGRID_ON_LOAD.context(): - cube_list = load( - tests.get_data_path( - ["NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc"] - ) - ) - self.assertTrue(all([cube.mesh is None for cube in cube_list])) - - -@tests.skip_data -class TestMultiplePhenomena(tests.IrisTest): - def test_multiple_phenomena(self): - cube_list = ugrid_load( - tests.get_data_path( - ["NetCDF", "unstructured_grid", "lfric_surface_mean.nc"] - ), - ) - self.assertCML( - cube_list, ("experimental", "ugrid", "surface_mean.cml") - ) - - -class TestTolerantLoading(XIOSFileMixin): - # N.B. using parts of the XIOS-like file integration testing, to make - # temporary netcdf files from stored CDL templates. - @classmethod - def setUpClass(cls): - super().setUpClass() # create cls.temp_dir = dir for test files - - @classmethod - def tearDownClass(cls): - super().setUpClass() # destroy temp dir - - # Create a testfile according to testcase-specific arguments. - # NOTE: with this, parent "create_synthetic_test_cube" can load a cube. - def create_synthetic_file(self, **create_kwargs): - template_name = create_kwargs["template"] # required kwarg - testfile_name = "tmp_netcdf" - template_subs = dict( - NUM_NODES=7, NUM_FACES=3, DATASET_NAME=testfile_name - ) - kwarg_subs = create_kwargs.get("subs", {}) # optional kwarg - template_subs.update(kwarg_subs) - filepath = create_file_from_cdl_template( - temp_file_dir=self.temp_dir, - dataset_name=testfile_name, - dataset_type=template_name, - template_subs=template_subs, - ) - return str(filepath) # N.B. Path object not usable in iris.load - - def test_mesh_bad_topology_dimension(self): - # Check that the load generates a suitable warning. - log_regex = r"topology_dimension.* ignoring" - with self.assertLogs(logger, level="WARNING", msg_regex=log_regex): - template = "minimal_bad_topology_dim" - dim_line = "mesh_var:topology_dimension = 1 ;" # which is wrong ! - cube = self.create_synthetic_test_cube( - template=template, subs=dict(TOPOLOGY_DIM_DEFINITION=dim_line) - ) - - # Check that the result has topology-dimension of 2 (not 1). - self.assertEqual(cube.mesh.topology_dimension, 2) - - def test_mesh_no_topology_dimension(self): - # Check that the load generates a suitable warning. - log_regex = r"Mesh variable.* has no 'topology_dimension'" - with self.assertLogs(logger, level="WARNING", msg_regex=log_regex): - template = "minimal_bad_topology_dim" - dim_line = "" # don't create ANY topology_dimension property - cube = self.create_synthetic_test_cube( - template=template, subs=dict(TOPOLOGY_DIM_DEFINITION=dim_line) - ) - - # Check that the result has the correct topology-dimension value. - self.assertEqual(cube.mesh.topology_dimension, 2) - - def test_mesh_bad_cf_role(self): - # Check that the load generates a suitable warning. - log_regex = r"inappropriate cf_role" - with self.assertLogs(logger, level="WARNING", msg_regex=log_regex): - template = "minimal_bad_mesh_cf_role" - dim_line = 'mesh_var:cf_role = "foo" ;' - _ = self.create_synthetic_test_cube( - template=template, subs=dict(CF_ROLE_DEFINITION=dim_line) - ) - - def test_mesh_no_cf_role(self): - # Check that the load generates a suitable warning. - log_regex = r"no cf_role attribute" - with self.assertLogs(logger, level="WARNING", msg_regex=log_regex): - template = "minimal_bad_mesh_cf_role" - dim_line = "" - _ = self.create_synthetic_test_cube( - template=template, subs=dict(CF_ROLE_DEFINITION=dim_line) - ) - - -@tests.skip_data -class Test_load_mesh(tests.IrisTest): - def common_test(self, file_name, mesh_var_name): - with PARSE_UGRID_ON_LOAD.context(): - mesh = load_mesh( - tests.get_data_path(["NetCDF", "unstructured_grid", file_name]) - ) - # NOTE: cannot use CML tests as this isn't supported for non-Cubes. - self.assertIsInstance(mesh, Mesh) - self.assertEqual(mesh.var_name, mesh_var_name) - - def test_full_file(self): - self.common_test( - "lfric_ngvat_2D_1t_face_half_levels_main_conv_rain.nc", - "Mesh2d_half_levels", - ) - - def test_mesh_file(self): - self.common_test("mesh_C12.nc", "dynamics") - - def test_no_mesh(self): - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes( - tests.get_data_path( - ["NetCDF", "unstructured_grid", "theta_nodal_not_ugrid.nc"] - ) - ) - self.assertDictEqual({}, meshes) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/experimental/test_ugrid_save.py b/lib/iris/tests/integration/experimental/test_ugrid_save.py deleted file mode 100644 index 803ac71caa..0000000000 --- a/lib/iris/tests/integration/experimental/test_ugrid_save.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Integration tests for NetCDF-UGRID file saving. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import glob -from pathlib import Path -import shutil -import tempfile - -import iris -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD -import iris.fileformats.netcdf -from iris.tests.stock.netcdf import _add_standard_data, ncgen_from_cdl - - -class TestBasicSave(tests.IrisTest): - @classmethod - def setUpClass(cls): - cls.temp_dir = Path(tempfile.mkdtemp()) - cls.examples_dir = ( - Path(__file__).absolute().parent / "ugrid_conventions_examples" - ) - example_paths = glob.glob(str(cls.examples_dir / "*ex*.cdl")) - example_names = [ - str(Path(filepath).name).split("_")[1] # = "ex" - for filepath in example_paths - ] - cls.example_names_paths = { - name: path for name, path in zip(example_names, example_paths) - } - - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.temp_dir) - - def test_example_result_cdls(self): - # Snapshot the result of saving the example cases. - for ex_name, cdl_path in self.example_names_paths.items(): - # Create a test netcdf file. - target_ncfile_path = str(self.temp_dir / f"{ex_name}.nc") - ncgen_from_cdl( - cdl_str=None, cdl_path=cdl_path, nc_path=target_ncfile_path - ) - # Fill in blank data-variables. - _add_standard_data(target_ncfile_path) - # Load as Iris data - with PARSE_UGRID_ON_LOAD.context(): - cubes = iris.load(target_ncfile_path) - # Re-save, to check the save behaviour. - resave_ncfile_path = str(self.temp_dir / f"{ex_name}_resaved.nc") - iris.save(cubes, resave_ncfile_path) - # Check the output against a CDL snapshot. - refdir_relpath = ( - "integration/experimental/ugrid_save/TestBasicSave/" - ) - reffile_name = str(Path(cdl_path).name).replace(".nc", ".cdl") - reffile_path = refdir_relpath + reffile_name - self.assertCDL(resave_ncfile_path, reference_filename=reffile_path) - - def test_example_roundtrips(self): - # Check that save-and-loadback leaves Iris data unchanged, - # for data derived from each UGRID example CDL. - for ex_name, cdl_path in self.example_names_paths.items(): - # Create a test netcdf file. - target_ncfile_path = str(self.temp_dir / f"{ex_name}.nc") - ncgen_from_cdl( - cdl_str=None, cdl_path=cdl_path, nc_path=target_ncfile_path - ) - # Fill in blank data-variables. - _add_standard_data(target_ncfile_path) - # Load the original as Iris data - with PARSE_UGRID_ON_LOAD.context(): - orig_cubes = iris.load(target_ncfile_path) - - if "ex4" in ex_name: - # Discard the extra formula terms component cubes - # Saving these does not do what you expect - orig_cubes = orig_cubes.extract("datavar") - - # Save-and-load-back to compare the Iris saved result. - resave_ncfile_path = str(self.temp_dir / f"{ex_name}_resaved.nc") - iris.save(orig_cubes, resave_ncfile_path) - with PARSE_UGRID_ON_LOAD.context(): - savedloaded_cubes = iris.load(resave_ncfile_path) - - # This should match the original exactly - # ..EXCEPT for our inability to compare meshes. - for orig, reloaded in zip(orig_cubes, savedloaded_cubes): - for cube in (orig, reloaded): - # Remove conventions attributes, which may differ. - cube.attributes.pop("Conventions", None) - # Remove var-names, which may differ. - cube.var_name = None - - # Compare the mesh contents (as we can't compare actual meshes) - self.assertEqual(orig.location, reloaded.location) - orig_mesh = orig.mesh - reloaded_mesh = reloaded.mesh - self.assertEqual( - orig_mesh.all_coords, reloaded_mesh.all_coords - ) - self.assertEqual( - orig_mesh.all_connectivities, - reloaded_mesh.all_connectivities, - ) - # Index the cubes to replace meshes with meshcoord-derived aux coords. - # This needs [:0] on the mesh dim, so do that on all dims. - keys = tuple([slice(0, None)] * orig.ndim) - orig = orig[keys] - reloaded = reloaded[keys] - # Resulting cubes, with collapsed mesh, should be IDENTICAL. - self.assertEqual(orig, reloaded) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/README.txt b/lib/iris/tests/integration/experimental/ugrid_conventions_examples/README.txt deleted file mode 100644 index 2a9b5bde35..0000000000 --- a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/README.txt +++ /dev/null @@ -1,16 +0,0 @@ -Examples generated from CDL example sections in UGRID conventions v1.0 - ( see webpage: https://ugrid-conventions.github.io/ugrid-conventions/ ) - -CHANGES: - * added a data-var to all examples, for ease of iris-roundtripping - * EX4 : - - had a couple of missing ";"s at lineends - - the formula terms (depth+surface) should map to 'Mesh2_layers', and not to the mesh at all. - - use Mesh2d_layers dim, and have no 'mesh' or 'location' - * "EX4a" -- possibly (future) closer mix of hybrid-vertical and mesh dimensions - - *don't* think we can have a hybrid coord ON the mesh dimension - - mesh being a vertical location (only) seems to make no sense - - .. and implies that the mesh is 1d and ordered, which is not really unstructured at all - - *could* have hybrid-height with the _orography_ mapping to the mesh - - doesn't match the UGRID examples, but see : iris.tests.unit.fileformats.netcdf.test_Saver__ugrid.TestSaveUgrid__cube.test_nonmesh_hybrid_dim - diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl b/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl deleted file mode 100644 index d022fedc61..0000000000 --- a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex1_1d_mesh.cdl +++ /dev/null @@ -1,55 +0,0 @@ -netcdf ex1_1d_mesh { -dimensions: -nMesh1_node = 5 ; // nNodes -nMesh1_edge = 4 ; // nEdges - -Two = 2; - -variables: -// Mesh topology -integer Mesh1 ; -Mesh1:cf_role = "mesh_topology" ; -Mesh1:long_name = "Topology data of 1D network" ; -Mesh1:topology_dimension = 1 ; -Mesh1:node_coordinates = "Mesh1_node_x Mesh1_node_y" ; -Mesh1:edge_node_connectivity = "Mesh1_edge_nodes" ; -Mesh1:edge_coordinates = "Mesh1_edge_x Mesh1_edge_y" ; // optional attribute -integer Mesh1_edge_nodes(nMesh1_edge, Two) ; -Mesh1_edge_nodes:cf_role = "edge_node_connectivity" ; -Mesh1_edge_nodes:long_name = "Maps every edge/link to the two nodes that it connects." ; -Mesh1_edge_nodes:start_index = 1 ; - -// Mesh node coordinates -double Mesh1_node_x(nMesh1_node) ; -Mesh1_node_x:standard_name = "longitude" ; -Mesh1_node_x:long_name = "Longitude of 1D network nodes." ; -Mesh1_node_x:units = "degrees_east" ; -double Mesh1_node_y(nMesh1_node) ; -Mesh1_node_y:standard_name = "latitude" ; -Mesh1_node_y:long_name = "Latitude of 1D network nodes." ; -Mesh1_node_y:units = "degrees_north" ; - -// Optional mesh edge coordinate variables -double Mesh1_edge_x(nMesh1_edge) ; -Mesh1_edge_x:standard_name = "longitude" ; -Mesh1_edge_x:long_name = "Characteristic longitude of 1D network edge (e.g. midpoint of the edge)." ; -Mesh1_edge_x:units = "degrees_east" ; -Mesh1_edge_x:bounds = "Mesh1_edge_xbnds" ; -double Mesh1_edge_y(nMesh1_edge) ; -Mesh1_edge_y:standard_name = "latitude" ; -Mesh1_edge_y:long_name = "Characteristic latitude of 1D network edge (e.g. midpoint of the edge)." ; -Mesh1_edge_y:units = "degrees_north" ; -Mesh1_edge_y:bounds = "Mesh1_edge_ybnds" ; -double Mesh1_edge_xbnds(nMesh1_edge,Two) ; -Mesh1_edge_xbnds:standard_name = "longitude" ; -Mesh1_edge_xbnds:long_name = "Longitude bounds of 1D network edge (i.e. begin and end longitude)." ; -Mesh1_edge_xbnds:units = "degrees_east" ; -double Mesh1_edge_ybnds(nMesh1_edge,Two) ; -Mesh1_edge_ybnds:standard_name = "latitude" ; -Mesh1_edge_ybnds:long_name = "Latitude bounds of 1D network edge (i.e. begin and end latitude)." ; -Mesh1_edge_ybnds:units = "degrees_north" ; - -float datavar(nMesh1_edge) ; - datavar:mesh = "Mesh1" ; - datavar:location = "edge" ; -} diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl b/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl deleted file mode 100644 index 1e4e483826..0000000000 --- a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex2_2d_triangular.cdl +++ /dev/null @@ -1,84 +0,0 @@ -netcdf ex2_2d_triangular { -dimensions: -nMesh2_node = 4 ; // nNodes -nMesh2_edge = 5 ; // nEdges -nMesh2_face = 2 ; // nFaces - -Two = 2 ; -Three = 3 ; - -variables: -// Mesh topology -integer Mesh2 ; -Mesh2:cf_role = "mesh_topology" ; -Mesh2:long_name = "Topology data of 2D unstructured mesh" ; -Mesh2:topology_dimension = 2 ; -Mesh2:node_coordinates = "Mesh2_node_x Mesh2_node_y" ; -Mesh2:face_node_connectivity = "Mesh2_face_nodes" ; -Mesh2:face_dimension = "nMesh2_face" ; -Mesh2:edge_node_connectivity = "Mesh2_edge_nodes" ; // attribute required if variables will be defined on edges -Mesh2:edge_dimension = "nMesh2_edge" ; -Mesh2:edge_coordinates = "Mesh2_edge_x Mesh2_edge_y" ; // optional attribute (requires edge_node_connectivity) -Mesh2:face_coordinates = "Mesh2_face_x Mesh2_face_y" ; // optional attribute -Mesh2:face_edge_connectivity = "Mesh2_face_edges" ; // optional attribute (requires edge_node_connectivity) -Mesh2:face_face_connectivity = "Mesh2_face_links" ; // optional attribute -Mesh2:edge_face_connectivity = "Mesh2_edge_face_links" ; // optional attribute (requires edge_node_connectivity) -integer Mesh2_face_nodes(nMesh2_face, Three) ; -Mesh2_face_nodes:cf_role = "face_node_connectivity" ; -Mesh2_face_nodes:long_name = "Maps every triangular face to its three corner nodes." ; -Mesh2_face_nodes:start_index = 1 ; -integer Mesh2_edge_nodes(nMesh2_edge, Two) ; -Mesh2_edge_nodes:cf_role = "edge_node_connectivity" ; -Mesh2_edge_nodes:long_name = "Maps every edge to the two nodes that it connects." ; -Mesh2_edge_nodes:start_index = 1 ; - -// Optional mesh topology variables -integer Mesh2_face_edges(nMesh2_face, Three) ; -Mesh2_face_edges:cf_role = "face_edge_connectivity" ; -Mesh2_face_edges:long_name = "Maps every triangular face to its three edges." ; -Mesh2_face_edges:start_index = 1 ; -integer Mesh2_face_links(nMesh2_face, Three) ; -Mesh2_face_links:cf_role = "face_face_connectivity" ; -Mesh2_face_links:long_name = "neighbor faces for faces" ; -Mesh2_face_links:start_index = 1 ; -Mesh2_face_links:_FillValue = -999 ; -Mesh2_face_links:comment = "missing neighbor faces are indicated using _FillValue" ; -integer Mesh2_edge_face_links(nMesh2_edge, Two) ; -Mesh2_edge_face_links:cf_role = "edge_face_connectivity" ; -Mesh2_edge_face_links:long_name = "neighbor faces for edges" ; -Mesh2_edge_face_links:start_index = 1 ; -Mesh2_edge_face_links:_FillValue = -999 ; -Mesh2_edge_face_links:comment = "missing neighbor faces are indicated using _FillValue" ; - -// Mesh node coordinates -double Mesh2_node_x(nMesh2_node) ; -Mesh2_node_x:standard_name = "longitude" ; -Mesh2_node_x:long_name = "Longitude of 2D mesh nodes." ; -Mesh2_node_x:units = "degrees_east" ; -double Mesh2_node_y(nMesh2_node) ; -Mesh2_node_y:standard_name = "latitude" ; -Mesh2_node_y:long_name = "Latitude of 2D mesh nodes." ; -Mesh2_node_y:units = "degrees_north" ; - -// Optional mesh face and edge coordinate variables -double Mesh2_face_x(nMesh2_face) ; -Mesh2_face_x:standard_name = "longitude" ; -Mesh2_face_x:long_name = "Characteristics longitude of 2D mesh triangle (e.g. circumcenter coordinate)." ; -Mesh2_face_x:units = "degrees_east" ; -double Mesh2_face_y(nMesh2_face) ; -Mesh2_face_y:standard_name = "latitude" ; -Mesh2_face_y:long_name = "Characteristics latitude of 2D mesh triangle (e.g. circumcenter coordinate)." ; -Mesh2_face_y:units = "degrees_north" ; -double Mesh2_edge_x(nMesh2_edge) ; -Mesh2_edge_x:standard_name = "longitude" ; -Mesh2_edge_x:long_name = "Characteristic longitude of 2D mesh edge (e.g. midpoint of the edge)." ; -Mesh2_edge_x:units = "degrees_east" ; -double Mesh2_edge_y(nMesh2_edge) ; -Mesh2_edge_y:standard_name = "latitude" ; -Mesh2_edge_y:long_name = "Characteristic latitude of 2D mesh edge (e.g. midpoint of the edge)." ; -Mesh2_edge_y:units = "degrees_north" ; - -float datavar(nMesh2_face) ; - datavar:mesh = "Mesh2" ; - datavar:location = "face" ; -} diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl b/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl deleted file mode 100644 index 2fa077d152..0000000000 --- a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex3_2d_flexible.cdl +++ /dev/null @@ -1,99 +0,0 @@ -netcdf ex3_2d_flexible { -dimensions: -nMesh2_node = 5 ; // nNodes -nMesh2_edge = 6 ; // nEdges -nMesh2_face = 2 ; // nFaces -nMaxMesh2_face_nodes = 4 ; // MaxNumNodesPerFace - -Two = 2 ; - -variables: -// Mesh topology -integer Mesh2 ; -Mesh2:cf_role = "mesh_topology" ; -Mesh2:long_name = "Topology data of 2D unstructured mesh" ; -Mesh2:topology_dimension = 2 ; -Mesh2:node_coordinates = "Mesh2_node_x Mesh2_node_y" ; -Mesh2:face_node_connectivity = "Mesh2_face_nodes" ; -Mesh2:face_dimension = "nMesh2_face" ; -Mesh2:edge_node_connectivity = "Mesh2_edge_nodes" ; // attribute required if variables will be defined on edges -Mesh2:edge_dimension = "nMesh2_edge" ; -Mesh2:edge_coordinates = "Mesh2_edge_x Mesh2_edge_y" ; // optional attribute (requires edge_node_connectivity) -Mesh2:face_coordinates = "Mesh2_face_x Mesh2_face_y" ; // optional attribute -Mesh2:face_edge_connectivity = "Mesh2_face_edges" ; // optional attribute (requires edge_node_connectivity) -Mesh2:face_face_connectivity = "Mesh2_face_links" ; // optional attribute -Mesh2:edge_face_connectivity = "Mesh2_edge_face_links" ; // optional attribute (requires edge_node_connectivity) -integer Mesh2_face_nodes(nMesh2_face, nMaxMesh2_face_nodes) ; -Mesh2_face_nodes:cf_role = "face_node_connectivity" ; -Mesh2_face_nodes:long_name = "Maps every face to its corner nodes." ; -Mesh2_face_nodes:_FillValue = 999999 ; -Mesh2_face_nodes:start_index = 1 ; -integer Mesh2_edge_nodes(nMesh2_edge, Two) ; -Mesh2_edge_nodes:cf_role = "edge_node_connectivity" ; -Mesh2_edge_nodes:long_name = "Maps every edge to the two nodes that it connects." ; -Mesh2_edge_nodes:start_index = 1 ; - -// Optional mesh topology variables -integer Mesh2_face_edges(nMesh2_face, nMaxMesh2_face_nodes) ; -Mesh2_face_edges:cf_role = "face_edge_connectivity" ; -Mesh2_face_edges:long_name = "Maps every face to its edges." ; -Mesh2_face_edges:_FillValue = 999999 ; -Mesh2_face_edges:start_index = 1 ; -integer Mesh2_face_links(nMesh2_face, nMaxMesh2_face_nodes) ; -Mesh2_face_links:cf_role = "face_face_connectivity" ; -Mesh2_face_links:long_name = "neighbor faces for faces" ; -Mesh2_face_links:start_index = 1 ; -Mesh2_face_links:_FillValue = -999 ; -Mesh2_face_links:comment = "missing edges as well as missing neighbor faces are indicated using _FillValue" ; -integer Mesh2_edge_face_links(nMesh2_edge, Two) ; -Mesh2_edge_face_links:cf_role = "edge_face_connectivity" ; -Mesh2_edge_face_links:long_name = "neighbor faces for edges" ; -Mesh2_edge_face_links:start_index = 1 ; -Mesh2_edge_face_links:_FillValue = -999 ; -Mesh2_edge_face_links:comment = "missing neighbor faces are indicated using _FillValue" ; - -// Mesh node coordinates -double Mesh2_node_x(nMesh2_node) ; -Mesh2_node_x:standard_name = "longitude" ; -Mesh2_node_x:long_name = "Longitude of 2D mesh nodes." ; -Mesh2_node_x:units = "degrees_east" ; -double Mesh2_node_y(nMesh2_node) ; -Mesh2_node_y:standard_name = "latitude" ; -Mesh2_node_y:long_name = "Latitude of 2D mesh nodes." ; -Mesh2_node_y:units = "degrees_north" ; - -// Optional mesh face and edge coordinate variables -double Mesh2_face_x(nMesh2_face) ; -Mesh2_face_x:standard_name = "longitude" ; -Mesh2_face_x:long_name = "Characteristics longitude of 2D mesh face." ; -Mesh2_face_x:units = "degrees_east" ; -Mesh2_face_x:bounds = "Mesh2_face_xbnds" ; -double Mesh2_face_y(nMesh2_face) ; -Mesh2_face_y:standard_name = "latitude" ; -Mesh2_face_y:long_name = "Characteristics latitude of 2D mesh face." ; -Mesh2_face_y:units = "degrees_north" ; -Mesh2_face_y:bounds = "Mesh2_face_ybnds" ; -double Mesh2_face_xbnds(nMesh2_face,nMaxMesh2_face_nodes) ; -Mesh2_face_xbnds:standard_name = "longitude" ; -Mesh2_face_xbnds:long_name = "Longitude bounds of 2D mesh face (i.e. corner coordinates)." ; -Mesh2_face_xbnds:units = "degrees_east" ; -Mesh2_face_xbnds:_FillValue = 9.9692099683868690E36; -double Mesh2_face_ybnds(nMesh2_face,nMaxMesh2_face_nodes) ; -Mesh2_face_ybnds:standard_name = "latitude" ; -Mesh2_face_ybnds:long_name = "Latitude bounds of 2D mesh face (i.e. corner coordinates)." ; -Mesh2_face_ybnds:units = "degrees_north" ; -Mesh2_face_ybnds:_FillValue = 9.9692099683868690E36; -double Mesh2_edge_x(nMesh2_edge) ; -Mesh2_edge_x:standard_name = "longitude" ; -Mesh2_edge_x:long_name = "Characteristic longitude of 2D mesh edge (e.g. midpoint of the edge)." ; -Mesh2_edge_x:units = "degrees_east" ; -double Mesh2_edge_y(nMesh2_edge) ; -Mesh2_edge_y:standard_name = "latitude" ; -Mesh2_edge_y:long_name = "Characteristic latitude of 2D mesh edge (e.g. midpoint of the edge)." ; -Mesh2_edge_y:units = "degrees_north" ; -// bounds variables for edges skipped - -float datavar(nMesh2_face) ; - datavar:mesh = "Mesh2" ; - datavar:location = "face" ; -} diff --git a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl b/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl deleted file mode 100644 index d154502018..0000000000 --- a/lib/iris/tests/integration/experimental/ugrid_conventions_examples/ugrid_ex4_3d_layered.cdl +++ /dev/null @@ -1,120 +0,0 @@ -netcdf ex4_3d_layered { -dimensions: -nMesh2_node = 6 ; // nNodes -nMesh2_edge = 7 ; // nEdges -nMesh2_face = 2 ; // nFaces -nMaxMesh2_face_nodes = 4 ; // MaxNumNodesPerFace -Mesh2_layers = 10 ; - -Two = 2 ; - -variables: -// Mesh topology -integer Mesh2 ; -Mesh2:cf_role = "mesh_topology" ; -Mesh2:long_name = "Topology data of 2D unstructured mesh" ; -Mesh2:topology_dimension = 2 ; -Mesh2:node_coordinates = "Mesh2_node_x Mesh2_node_y" ; -Mesh2:face_node_connectivity = "Mesh2_face_nodes" ; -Mesh2:face_dimension = "nMesh2_face" ; -Mesh2:edge_node_connectivity = "Mesh2_edge_nodes" ; // attribute required if variables will be defined on edges -Mesh2:edge_dimension = "nMesh2_edge" ; -Mesh2:edge_coordinates = "Mesh2_edge_x Mesh2_edge_y" ; // optional attribute (requires edge_node_connectivity) -Mesh2:face_coordinates = "Mesh2_face_x Mesh2_face_y" ; // optional attribute -Mesh2:face_edge_connectivity = "Mesh2_face_edges" ; // optional attribute (requires edge_node_connectivity) -Mesh2:face_face_connectivity = "Mesh2_face_links" ; // optional attribute -Mesh2:edge_face_connectivity = "Mesh2_edge_face_links" ; // optional attribute (requires edge_node_connectivity) -integer Mesh2_face_nodes(nMesh2_face, nMaxMesh2_face_nodes) ; -Mesh2_face_nodes:cf_role = "face_node_connectivity" ; -Mesh2_face_nodes:long_name = "Maps every face to its corner nodes." ; -Mesh2_face_nodes:_FillValue = 999999 ; -Mesh2_face_nodes:start_index = 1 ; -integer Mesh2_edge_nodes(nMesh2_edge, Two) ; -Mesh2_edge_nodes:cf_role = "edge_node_connectivity" ; -Mesh2_edge_nodes:long_name = "Maps every edge to the two nodes that it connects." ; -Mesh2_edge_nodes:start_index = 1 ; - -// Optional mesh topology variables -integer Mesh2_face_edges(nMesh2_face, nMaxMesh2_face_nodes) ; -Mesh2_face_edges:cf_role = "face_edge_connectivity" ; -Mesh2_face_edges:long_name = "Maps every face to its edges." ; -Mesh2_face_edges:_FillValue = 999999 ; -Mesh2_face_edges:start_index = 1 ; -integer Mesh2_face_links(nMesh2_face, nMaxMesh2_face_nodes) ; -Mesh2_face_links:cf_role = "face_face_connectivity" ; -Mesh2_face_links:long_name = "neighbor faces for faces" ; -Mesh2_face_links:start_index = 1 ; -Mesh2_face_links:_FillValue = -999 ; -Mesh2_face_links:comment = "missing edges as well as missing neighbor faces are indicated using _FillValue" ; -integer Mesh2_edge_face_links(nMesh2_edge, Two) ; -Mesh2_edge_face_links:cf_role = "edge_face_connectivity" ; -Mesh2_edge_face_links:long_name = "neighbor faces for edges" ; -Mesh2_edge_face_links:start_index = 1 ; -Mesh2_edge_face_links:_FillValue = -999 ; -Mesh2_edge_face_links:comment = "missing neighbor faces are indicated using _FillValue" ; - -// Mesh node coordinates -double Mesh2_node_x(nMesh2_node) ; -Mesh2_node_x:standard_name = "longitude" ; -Mesh2_node_x:long_name = "Longitude of 2D mesh nodes." ; -Mesh2_node_x:units = "degrees_east" ; -double Mesh2_node_y(nMesh2_node) ; -Mesh2_node_y:standard_name = "latitude" ; -Mesh2_node_y:long_name = "Latitude of 2D mesh nodes." ; -Mesh2_node_y:units = "degrees_north" ; - -// Optional mesh face and edge coordinate variables -double Mesh2_face_x(nMesh2_face) ; -Mesh2_face_x:standard_name = "longitude" ; -Mesh2_face_x:long_name = "Characteristics longitude of 2D mesh face." ; -Mesh2_face_x:units = "degrees_east" ; -Mesh2_face_x:bounds = "Mesh2_face_xbnds" ; -double Mesh2_face_y(nMesh2_face) ; -Mesh2_face_y:standard_name = "latitude" ; -Mesh2_face_y:long_name = "Characteristics latitude of 2D mesh face." ; -Mesh2_face_y:units = "degrees_north" ; -Mesh2_face_y:bounds = "Mesh2_face_ybnds" ; -double Mesh2_face_xbnds(nMesh2_face,nMaxMesh2_face_nodes) ; -Mesh2_face_xbnds:standard_name = "longitude" ; -Mesh2_face_xbnds:long_name = "Longitude bounds of 2D mesh face (i.e. corner coordinates)." ; -Mesh2_face_xbnds:units = "degrees_east" ; -Mesh2_face_xbnds:_FillValue = 9.9692099683868690E36; -double Mesh2_face_ybnds(nMesh2_face,nMaxMesh2_face_nodes) ; -Mesh2_face_ybnds:standard_name = "latitude" ; -Mesh2_face_ybnds:long_name = "Latitude bounds of 2D mesh face (i.e. corner coordinates)." ; -Mesh2_face_ybnds:units = "degrees_north" ; -Mesh2_face_ybnds:_FillValue = 9.9692099683868690E36; -double Mesh2_edge_x(nMesh2_edge) ; -Mesh2_edge_x:standard_name = "longitude" ; -Mesh2_edge_x:long_name = "Characteristic longitude of 2D mesh edge (e.g. midpoint of the edge)." ; -Mesh2_edge_x:units = "degrees_east" ; -double Mesh2_edge_y(nMesh2_edge) ; -Mesh2_edge_y:standard_name = "latitude" ; -Mesh2_edge_y:long_name = "Characteristic latitude of 2D mesh edge (e.g. midpoint of the edge)." ; -Mesh2_edge_y:units = "degrees_north" ; -// bounds variables for edges skipped - -// Vertical coordinate -double Mesh2_layers(Mesh2_layers) ; -Mesh2_layers:standard_name = "ocean_sigma_coordinate" ; -Mesh2_layers:long_name = "sigma at layer midpoints" ; -Mesh2_layers:positive = "up" ; -Mesh2_layers:formula_terms = "sigma: Mesh2_layers eta: Mesh2_surface depth: Mesh2_depth" ; -double Mesh2_depth(Mesh2_layers) ; -Mesh2_depth:standard_name = "sea_floor_depth_below_geoid" ; -Mesh2_depth:units = "m" ; -Mesh2_depth:positive = "down" ; -Mesh2_depth:coordinates = "Mesh2_node_x Mesh2_node_y" ; -double Mesh2_surface(Mesh2_layers) ; -Mesh2_surface:standard_name = "sea_surface_height_above_geoid" ; -Mesh2_surface:units = "m" ; -Mesh2_surface:coordinates = "Mesh2_face_x Mesh2_face_y" ; - -float datavar(Mesh2_layers, nMesh2_face) ; - datavar:mesh = "Mesh2" ; - datavar:location = "face" ; - -data: -Mesh2_layers = 0., 1., 2., 3., 4., 5., 6., 7., 8., 9. ; - -} diff --git a/lib/iris/tests/integration/fast_load/__init__.py b/lib/iris/tests/integration/fast_load/__init__.py deleted file mode 100644 index a94785ca58..0000000000 --- a/lib/iris/tests/integration/fast_load/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :mod:`iris.fileformats.um` fast load functions.""" diff --git a/lib/iris/tests/integration/fast_load/test_fast_load.py b/lib/iris/tests/integration/fast_load/test_fast_load.py deleted file mode 100644 index a510ef7257..0000000000 --- a/lib/iris/tests/integration/fast_load/test_fast_load.py +++ /dev/null @@ -1,690 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for fast-loading FF and PP files.""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -from collections.abc import Iterable -import shutil -import tempfile - -import numpy as np - -import iris -from iris.coord_systems import GeogCS -import iris.coords -from iris.coords import AuxCoord, CellMethod, DimCoord -from iris.cube import Cube, CubeList -from iris.exceptions import IgnoreCubeException -from iris.fileformats.pp import EARTH_RADIUS, STASH -from iris.fileformats.um._fast_load import STRUCTURED_LOAD_CONTROLS - - -class Mixin_FieldTest: - # A mixin providing common facilities for fast-load testing : - # * create 'raw' cubes to produce the desired PP fields in a test file. - # * save 'raw' cubes to temporary PP files that get deleted afterwards. - # * control whether tests run with 'normal' or 'fast' loading. - - def setUp(self): - # Create a private temporary directory. - self.temp_dir_path = tempfile.mkdtemp() - # Initialise temporary filename generation. - self.tempfile_count = 0 - self.tempfile_path_fmt = ( - "{dir_path}/tempfile_{prefix}_{file_number:06d}{suffix}" - ) - # Enable fast loading, if the inheritor enables it. - # N.B. *requires* the user to define "self.do_fast_loads" (no default). - if self.do_fast_loads: - # Enter a 'structured load' context. - self.load_context = STRUCTURED_LOAD_CONTROLS.context( - loads_use_structured=True - ) - # N.B. we can't use a 'with', so issue separate 'enter' and 'exit' - # calls instead. - self.load_context.__enter__() - - def tearDown(self): - # Delete temporary directory. - shutil.rmtree(self.temp_dir_path) - if self.do_fast_loads: - # End the 'fast loading' context. - self.load_context.__exit__(None, None, None) - - def _temp_filepath(self, user_name="", suffix=".pp"): - # Return the filepath for a new temporary file. - self.tempfile_count += 1 - file_path = self.tempfile_path_fmt.format( - dir_path=self.temp_dir_path, - prefix=user_name, - file_number=self.tempfile_count, - suffix=suffix, - ) - return file_path - - def save_fieldcubes(self, cubes, basename=""): - # Save cubes to a temporary file, and return its filepath. - file_path = self._temp_filepath(user_name=basename, suffix=".pp") - iris.save(cubes, file_path) - return file_path - - def fields( - self, - c_t=None, - cft=None, - ctp=None, - c_h=None, - c_p=None, - phn=0, - mmm=None, - pse=None, - ): - # Return a list of 2d cubes representing raw PPFields, from args - # specifying sequences of (scalar) coordinate values. - # TODO? : add bounds somehow ? - # - # Arguments 'c' are either a single int value, making a scalar - # coord, or a string of characters : '0'-'9' (index) or '-' (missing). - # The indexes select point values from fixed list of possibles. - # - # Argument 'c_h' and 'c_p' represent height or pressure values, so - # ought to be mutually exclusive -- these control LBVC. - # - # Argument 'phn' indexes phenomenon types. - # - # Argument 'mmm' denotes existence (or not) of a cell method of type - # 'average' or 'min' or 'max' (values '012' respectively), applying to - # the time values -- ultimately, this controls LBTIM. - # - # Argument 'pse' denotes pseudo-level numbers. - # These translate into 'LBUSER5' values. - - # Get the number of result cubes, defined by the 'longest' arg. - def arglen(arg): - # Get the 'length' of a control argument. - if arg is None: - result = 0 - elif isinstance(arg, str): - result = len(arg) - else: - result = 1 - return result - - n_flds = max(arglen(x) for x in (c_t, cft, ctp, c_h, c_p, mmm)) - - # Make basic anonymous test cubes. - ny, nx = 3, 5 - data = np.arange(n_flds * ny * nx, dtype=np.float32) - data = data.reshape((n_flds, ny, nx)) - cubes = [Cube(data[i]) for i in range(n_flds)] - - # Define test point values for making coordinates. - time_unit = "hours since 1970-01-01" - period_unit = "hours" - height_unit = "m" - pressure_unit = "hPa" - time_values = 24.0 * np.arange(10) - height_values = 100.0 * np.arange(1, 11) - pressure_values = [ - 100.0, - 150.0, - 200.0, - 250.0, - 300.0, - 500.0, - 850.0, - 1000.0, - ] - pseudolevel_values = range(1, 11) # A valid value is >= 1. - - # Test phenomenon details. - # NOTE: in order to write/readback as identical, these also contain a - # canonical unit and matching STASH attribute. - # Those could in principle be looked up, but it's a bit awkward. - phenomenon_values = [ - ("air_temperature", "K", "m01s01i004"), - ("x_wind", "m s-1", "m01s00i002"), - ("y_wind", "m s-1", "m01s00i003"), - ("specific_humidity", "kg kg-1", "m01s00i010"), - ] - - # Test cell-methods. - # NOTE: if you add an *interval* to any of these cell-methods, it is - # not saved into the PP file (?? or maybe not loaded back again ??). - # This could be a PP save/load bug, or maybe just because no bounds ? - cell_method_values = [ - CellMethod("mean", "time"), - CellMethod("maximum", "time"), - CellMethod("minimum", "time"), - ] - - # Define helper to decode an argument as a list of test values. - def arg_vals(arg, vals): - # Decode an argument to a list of 'n_flds' coordinate point values. - # (or 'None' where missing) - - # First get a list of value indices from the argument. - # Can be: a single index value; a list of indices; or a string. - if isinstance(arg, Iterable) and not isinstance(arg, str): - # Can also just pass a simple iterable of values. - inds = [int(val) for val in arg] - else: - n_vals = arglen(arg) - if n_vals == 0: - inds = [None] * n_flds - elif n_vals == 1: - inds = [int(arg)] * n_flds - else: - assert isinstance(arg, str) - inds = [None if char == "-" else int(char) for char in arg] - - # Convert indices to selected point values. - values = [None if ind is None else vals[int(ind)] for ind in inds] - - return values - - # Apply phenomenon_values definitions. - phenomena = arg_vals(phn, phenomenon_values) - for cube, (name, units, stash) in zip(cubes, phenomena): - cube.rename(name) - # NOTE: in order to get a cube that will write+readback the same, - # the units must be the canonical one. - cube.units = units - # NOTE: in order to get a cube that will write+readback the same, - # we must include a STASH attribute. - cube.attributes["STASH"] = STASH.from_msi(stash) - cube.fill_value = np.float32(-1e30) - - # Add x and y coords. - cs = GeogCS(EARTH_RADIUS) - xvals = np.linspace(0.0, 180.0, nx) - co_x = DimCoord( - np.array(xvals, dtype=np.float32), - standard_name="longitude", - units="degrees", - coord_system=cs, - ) - yvals = np.linspace(-45.0, 45.0, ny) - co_y = DimCoord( - np.array(yvals, dtype=np.float32), - standard_name="latitude", - units="degrees", - coord_system=cs, - ) - for cube in cubes: - cube.add_dim_coord(co_y, 0) - cube.add_dim_coord(co_x, 1) - - # Add multiple scalar coordinates as defined by the arguments. - def arg_coords(arg, name, unit, vals=None): - # Decode an argument to a list of scalar coordinates. - if vals is None: - vals = np.arange(n_flds + 2) # Note allowance - vals = arg_vals(arg, vals) - coords = [ - None if val is None else DimCoord([val], units=unit) - for val in vals - ] - # Apply names separately, as 'pressure' is not a standard name. - for coord in coords: - if coord: - coord.rename(name) - # Also fix heights to match what comes from a PP file. - if name == "height": - coord.attributes["positive"] = "up" - return coords - - def add_arg_coords(arg, name, unit, vals=None): - # Add scalar coordinates to each cube, for one argument. - coords = arg_coords(arg, name, unit, vals) - for cube, coord in zip(cubes, coords): - if coord: - cube.add_aux_coord(coord) - - add_arg_coords(c_t, "time", time_unit, time_values) - add_arg_coords(cft, "forecast_reference_time", time_unit) - add_arg_coords(ctp, "forecast_period", period_unit, time_values) - add_arg_coords(c_h, "height", height_unit, height_values) - add_arg_coords(c_p, "pressure", pressure_unit, pressure_values) - add_arg_coords(pse, "pseudo_level", "1", pseudolevel_values) - - # Add cell methods as required. - methods = arg_vals(mmm, cell_method_values) - for cube, method in zip(cubes, methods): - if method: - cube.add_cell_method(method) - - return cubes - - -class MixinBasic: - # A mixin of tests that can be applied to *either* standard or fast load. - # The "real" test classes must inherit this, and Mixin_FieldTest, - # and define 'self.do_fast_loads' as True or False. - # - # Basic functional tests. - - def test_basic(self): - # Show that basic load merging works. - flds = self.fields(c_t="123", cft="000", ctp="123", c_p=0) - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - self.assertEqual(results, expected) - - def test_phenomena(self): - # Show that different phenomena are merged into distinct cubes. - flds = self.fields(c_t="1122", phn="0101") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - self.assertEqual(results, expected) - - def test_cross_file_concatenate(self): - # Combine vector dimensions (i.e. concatenate) across multiple files. - fldset_1 = self.fields(c_t="12") - fldset_2 = self.fields(c_t="34") - file_1 = self.save_fieldcubes(fldset_1) - file_2 = self.save_fieldcubes(fldset_2) - results = iris.load((file_1, file_2)) - expected = CubeList(fldset_1 + fldset_2).merge() - self.assertEqual(results, expected) - - def test_cell_method(self): - # Check that cell methods (i.e. LBPROC values) produce distinct - # phenomena. - flds = self.fields(c_t="000111222", mmm="-01-01-01") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList( - CubeList(flds[i_start::3]).merge_cube() for i_start in range(3) - ) - self.assertEqual(results, expected) - - -class MixinCallDetails: - # A mixin of tests that can be applied to *either* standard or fast load. - # The "real" test classes must inherit this, and Mixin_FieldTest, - # and define 'self.do_fast_loads' as True or False. - # - # Tests for different load calls and load-call arguments. - - def test_stash_constraint(self): - # Check that an attribute constraint functions correctly. - # Note: this is a special case in "fileformats.pp". - flds = self.fields(c_t="1122", phn="0101") - file = self.save_fieldcubes(flds) - airtemp_flds = [fld for fld in flds if fld.name() == "air_temperature"] - stash_attribute = airtemp_flds[0].attributes["STASH"] - results = iris.load( - file, iris.AttributeConstraint(STASH=stash_attribute) - ) - expected = CubeList(airtemp_flds).merge() - self.assertEqual(results, expected) - - def test_ordinary_constraint(self): - # Check that a 'normal' constraint functions correctly. - # Note: *should* be independent of structured loading. - flds = self.fields(c_h="0123") - file = self.save_fieldcubes(flds) - height_constraint = iris.Constraint(height=lambda h: 150.0 < h < 350.0) - results = iris.load(file, height_constraint) - expected = CubeList(flds[1:3]).merge() - self.assertEqual(results, expected) - - def test_callback(self): - # Use 2 timesteps each of (air-temp on height) and (rh on pressure). - flds = self.fields(c_t="0011", phn="0303", c_h="0-1-", c_p="-2-3") - file = self.save_fieldcubes(flds) - - if not self.do_fast_loads: - - def callback(cube, field, filename): - self.assertEqual(filename, file) - lbvc = field.lbvc - if lbvc == 1: - # reject the height level data (accept only pressure). - raise IgnoreCubeException() - else: - # Record the LBVC value. - cube.attributes["LBVC"] = lbvc - - else: - - def callback(cube, collation, filename): - self.assertEqual(filename, file) - lbvcs = [fld.lbvc for fld in collation.fields] - lbvc0 = lbvcs[0] - if not np.all(lbvcs == lbvc0): - msg = "Fields have different LBVCs : {}" - raise ValueError(msg.format(set(lbvcs))) - if lbvc0 == 1: - # reject the height level data (accept only pressure). - raise IgnoreCubeException() - else: - # Record the LBVC values. - cube.attributes["A_LBVC"] = lbvcs - - results = iris.load(file, callback=callback) - - # Make an 'expected' from selected fields, with the expected attribute. - expected = CubeList([flds[1], flds[3]]).merge() - if not self.do_fast_loads: - # This is actually a NumPy int32, so honour that here. - expected[0].attributes["LBVC"] = np.int32(8) - else: - expected[0].attributes["A_LBVC"] = [8, 8] - - self.assertEqual(results, expected) - - def test_load_cube(self): - flds = self.fields(c_t="123", cft="000", ctp="123", c_p=0) - file = self.save_fieldcubes(flds) - results = iris.load_cube(file) - expected = CubeList(flds).merge_cube() - self.assertEqual(results, expected) - - def test_load_cubes(self): - flds = self.fields(c_h="0123") - file = self.save_fieldcubes(flds) - height_constraints = [ - iris.Constraint(height=300.0), - iris.Constraint(height=lambda h: 150.0 < h < 350.0), - iris.Constraint("air_temperature"), - ] - results = iris.load_cubes(file, height_constraints) - expected = CubeList( - [ - flds[2], - CubeList(flds[1:3]).merge_cube(), - CubeList(flds).merge_cube(), - ] - ) - self.assertEqual(results, expected) - - def test_load_raw(self): - fldset_1 = self.fields(c_t="015", phn="001") - fldset_2 = self.fields(c_t="234") - file_1 = self.save_fieldcubes(fldset_1) - file_2 = self.save_fieldcubes(fldset_2) - results = iris.load_raw((file_1, file_2)) - if not self.do_fast_loads: - # Each 'raw' cube is just one field. - expected = CubeList(fldset_1 + fldset_2) - else: - # 'Raw' cubes have combined (vector) times within each file. - # The 'other' phenomenon appears seperately. - expected = CubeList( - [ - CubeList(fldset_1[:2]).merge_cube(), - CubeList(fldset_2).merge_cube(), - fldset_1[2], - ] - ) - - # Again here, the order of these results is not stable : - # It varies with random characters in the temporary filepath. - # - # ***************************************************************** - # *** Here, this is clearly ALSO the case for "standard" loads. *** - # ***************************************************************** - # - # E.G. run "test_fast_load.py -v TestCallDetails__Iris.test_load_raw" : - # If you remove the sort operations, this fails "sometimes". - # - # To fix this, sort both expected and results by (first) timepoint - # - for which purpose we made all the time values different. - - def timeorder(cube): - return cube.coord("time").points[0] - - expected = sorted(expected, key=timeorder) - results = sorted(results, key=timeorder) - - self.assertEqual(results, expected) - - -class MixinDimsAndOrdering: - # A mixin of tests that can be applied to *either* standard or fast load. - # The "real" test classes must inherit this, and Mixin_FieldTest, - # and define 'self.do_fast_loads' as True or False. - # - # Tests for multidimensional results and dimension orderings. - - def test_multidim(self): - # Check that a full 2-phenom * 2d structure all works properly. - flds = self.fields(c_t="00001111", c_h="00110011", phn="01010101") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - self.assertEqual(results, expected) - - def test_odd_order(self): - # Show that an erratic interleaving of phenomena fields still works. - # N.B. field sequences *within* each phenomenon are properly ordered. - flds = self.fields(c_t="00010111", c_h="00101101", phn="01001011") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - self.assertEqual(results, expected) - - def test_v_t_order(self): - # With height varying faster than time, first dimension is time, - # which matches the 'normal' load behaviour. - flds = self.fields(c_t="000111", c_h="012012") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - # Order is (t, h, y, x), which is "standard". - self.assertEqual(expected[0].coord_dims("time"), (0,)) - self.assertEqual(expected[0].coord_dims("height"), (1,)) - self.assertEqual(results, expected) - - def test_t_v_order(self): - # With time varying faster than height, first dimension is height, - # which does not match the 'normal' load. - flds = self.fields(c_t="010101", c_h="001122") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - if not self.do_fast_loads: - # Order is (t, h, y, x), which is "standard". - self.assertEqual(results[0].coord_dims("time"), (0,)) - self.assertEqual(results[0].coord_dims("height"), (1,)) - else: - # Order is (h, t, y, x), which is *not* "standard". - self.assertEqual(results[0].coord_dims("time"), (1,)) - self.assertEqual(results[0].coord_dims("height"), (0,)) - expected[0].transpose((1, 0, 2, 3)) - self.assertEqual(results, expected) - - def test_missing_combination(self): - # A case where one field is 'missing' to make a 2d result. - flds = self.fields(c_t="00011", c_h="01202") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - self.assertEqual(expected[0].coord_dims("time"), (0,)) - self.assertEqual(expected[0].coord_dims("height"), (0,)) - if self.do_fast_loads: - # Something a bit weird happens to the 'height' coordinate in this - # case (and not for standard load). - for cube in expected: - cube.coord("height").points = np.array( - cube.coord("height").points, dtype=np.float32 - ) - cube.coord("height").attributes = {} - self.assertEqual(results, expected) - - -class MixinProblemCases: - def test_FAIL_scalar_vector_concatenate(self): - # Structured load can produce a scalar coordinate from one file, and a - # matching vector one from another file, but these won't "combine". - # We'd really like to fix this one... - (single_timepoint_fld,) = self.fields(c_t="1") - multi_timepoint_flds = self.fields(c_t="23") - file_single = self.save_fieldcubes( - [single_timepoint_fld], basename="single" - ) - file_multi = self.save_fieldcubes( - multi_timepoint_flds, basename="multi" - ) - - results = iris.load((file_single, file_multi)) - if not self.do_fast_loads: - # This is what we'd LIKE to get (what iris.load gives). - expected = CubeList( - multi_timepoint_flds + [single_timepoint_fld] - ).merge() - else: - # This is what we ACTUALLY get at present. - # It can't combine the scalar and vector time coords. - expected = CubeList( - [ - CubeList(multi_timepoint_flds).merge_cube(), - single_timepoint_fld, - ] - ) - # NOTE: in this case, we need to sort the results to ensure a - # repeatable ordering, because ??somehow?? the random temporary - # directory name affects the ordering of the cubes in the result ! - results = CubeList(sorted(results, key=lambda cube: cube.shape)) - self.assertEqual(results, expected) - - def test_FAIL_phenomena_nostash(self): - # If we remove the 'STASH' attributes, certain phenomena can still be - # successfully encoded+decoded by standard load using LBFC values. - # Structured loading gets this wrong, because it does not use LBFC in - # characterising phenomena. - flds = self.fields(c_t="1122", phn="0101") - for fld in flds: - del fld.attributes["STASH"] - file = self.save_fieldcubes(flds) - results = iris.load(file) - if not self.do_fast_loads: - # This is what we'd LIKE to get (what iris.load gives). - expected = CubeList(flds).merge() - else: - # At present, we get a cube incorrectly combined together over all - # 4 timepoints, with the same phenomenon for all (!wrong!). - # It's a bit tricky to arrange the existing data like that. - # Do it by hacking the time values to allow merge, and then fixing - # up the time - old_t1, old_t2 = ( - fld.coord("time").points[0] for fld in (flds[0], flds[2]) - ) - for i_fld, fld in enumerate(flds): - # Hack the phenomena to all look like the first one. - fld.rename("air_temperature") - fld.units = "K" - # Hack the time points so the 4 cube can merge into one. - fld.coord("time").points = [old_t1 + i_fld] - one_cube = CubeList(flds).merge_cube() - # Replace time dim with an anonymous dim. - co_t_fake = one_cube.coord("time") - one_cube.remove_coord(co_t_fake) - # Reconstruct + add back the expected auxiliary time coord. - co_t_new = AuxCoord( - [old_t1, old_t1, old_t2, old_t2], - standard_name="time", - units=co_t_fake.units, - ) - one_cube.add_aux_coord(co_t_new, 0) - expected = [one_cube] - self.assertEqual(results, expected) - - def test_FAIL_pseudo_levels(self): - # Show how pseudo levels are handled. - flds = self.fields(c_t="000111222", pse="123123123") - file = self.save_fieldcubes(flds) - results = iris.load(file) - expected = CubeList(flds).merge() - - # NOTE: this problem is now fixed : Structured load gives the same answer. - # - # if not self.do_fast_loads: - # expected = CubeList(flds).merge() - # else: - # # Structured loading doesn't understand pseudo-level. - # # The result is rather horrible... - # - # # First get a cube over 9 timepoints. - # flds = self.fields(c_t='012345678', - # pse=1) # result gets level==2, not clear why. - # - # # Replace the time coord with an AUX coord. - # nine_timepoints_cube = CubeList(flds).merge_cube() - # co_time = nine_timepoints_cube.coord('time') - # nine_timepoints_cube.remove_coord(co_time) - # nine_timepoints_cube.add_aux_coord(AuxCoord.from_coord(co_time), - # 0) - # # Set the expected timepoints equivalent to '000111222'. - # nine_timepoints_cube.coord('time').points = \ - # np.array([0.0, 0.0, 0.0, 24.0, 24.0, 24.0, 48.0, 48.0, 48.0]) - # # Make a cubelist with this single cube. - # expected = CubeList([nine_timepoints_cube]) - - self.assertEqual(results, expected) - - -class TestBasic__Iris(Mixin_FieldTest, MixinBasic, tests.IrisTest): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'basic' tests with *normal* loading. - do_fast_loads = False - - -class TestBasic__Fast(Mixin_FieldTest, MixinBasic, tests.IrisTest): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'basic' tests with *FAST* loading. - do_fast_loads = True - - -class TestCallDetails__Iris(Mixin_FieldTest, MixinCallDetails, tests.IrisTest): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'call details' tests with *normal* loading. - do_fast_loads = False - - -class TestCallDetails__Fast(Mixin_FieldTest, MixinCallDetails, tests.IrisTest): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'call details' tests with *FAST* loading. - do_fast_loads = True - - -class TestDimsAndOrdering__Iris( - Mixin_FieldTest, MixinDimsAndOrdering, tests.IrisTest -): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'dimensions and ordering' tests with *normal* loading. - do_fast_loads = False - - -class TestDimsAndOrdering__Fast( - Mixin_FieldTest, MixinDimsAndOrdering, tests.IrisTest -): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'dimensions and ordering' tests with *FAST* loading. - do_fast_loads = True - - -class TestProblems__Iris(Mixin_FieldTest, MixinProblemCases, tests.IrisTest): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'failure cases' tests with *normal* loading. - do_fast_loads = False - - -class TestProblems__Fast(Mixin_FieldTest, MixinProblemCases, tests.IrisTest): - # Finally, an actual test-class (unittest.TestCase) : - # run the 'failure cases' tests with *FAST* loading. - do_fast_loads = True - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/merge/__init__.py b/lib/iris/tests/integration/merge/__init__.py deleted file mode 100644 index 9374976532..0000000000 --- a/lib/iris/tests/integration/merge/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for the :mod:`iris._merge` package.""" diff --git a/lib/iris/tests/integration/merge/test_merge.py b/lib/iris/tests/integration/merge/test_merge.py deleted file mode 100644 index f5f92a7a7d..0000000000 --- a/lib/iris/tests/integration/merge/test_merge.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Integration tests for merging cubes. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -from iris.coords import DimCoord -from iris.cube import Cube, CubeList - - -class TestContiguous(tests.IrisTest): - def test_form_contiguous_dimcoord(self): - # Test that cube sliced up and remerged in the opposite order maintains - # contiguity. - cube1 = Cube([1, 2, 3], "air_temperature", units="K") - coord1 = DimCoord([3, 2, 1], long_name="spam") - coord1.guess_bounds() - cube1.add_dim_coord(coord1, 0) - cubes = CubeList(cube1.slices_over("spam")) - cube2 = cubes.merge_cube() - coord2 = cube2.coord("spam") - - self.assertTrue(coord2.is_contiguous()) - self.assertArrayEqual(coord2.points, [1, 2, 3]) - self.assertArrayEqual(coord2.bounds, coord1.bounds[::-1, ::-1]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/netcdf/__init__.py b/lib/iris/tests/integration/netcdf/__init__.py deleted file mode 100644 index f500b52520..0000000000 --- a/lib/iris/tests/integration/netcdf/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for loading and saving netcdf files.""" diff --git a/lib/iris/tests/integration/netcdf/test_attributes.py b/lib/iris/tests/integration/netcdf/test_attributes.py deleted file mode 100644 index a73d6c7d49..0000000000 --- a/lib/iris/tests/integration/netcdf/test_attributes.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for attribute-related loading and saving netcdf files.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from contextlib import contextmanager -from unittest import mock - -import iris -from iris.cube import Cube, CubeList -from iris.fileformats.netcdf import CF_CONVENTIONS_VERSION - - -class TestUmVersionAttribute(tests.IrisTest): - def test_single_saves_as_global(self): - cube = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"um_version": "4.3"}, - ) - with self.temp_filename(".nc") as nc_path: - iris.save(cube, nc_path) - self.assertCDL(nc_path) - - def test_multiple_same_saves_as_global(self): - cube_a = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"um_version": "4.3"}, - ) - cube_b = Cube( - [1.0], - standard_name="air_pressure", - units="hPa", - attributes={"um_version": "4.3"}, - ) - with self.temp_filename(".nc") as nc_path: - iris.save(CubeList([cube_a, cube_b]), nc_path) - self.assertCDL(nc_path) - - def test_multiple_different_saves_on_variables(self): - cube_a = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"um_version": "4.3"}, - ) - cube_b = Cube( - [1.0], - standard_name="air_pressure", - units="hPa", - attributes={"um_version": "4.4"}, - ) - with self.temp_filename(".nc") as nc_path: - iris.save(CubeList([cube_a, cube_b]), nc_path) - self.assertCDL(nc_path) - - -@contextmanager -def _patch_site_configuration(): - def cf_patch_conventions(conventions): - return ", ".join([conventions, "convention1, convention2"]) - - def update(config): - config["cf_profile"] = mock.Mock(name="cf_profile") - config["cf_patch"] = mock.Mock(name="cf_patch") - config["cf_patch_conventions"] = cf_patch_conventions - - orig_site_config = iris.site_configuration.copy() - update(iris.site_configuration) - yield - iris.site_configuration = orig_site_config - - -class TestConventionsAttributes(tests.IrisTest): - def test_patching_conventions_attribute(self): - # Ensure that user defined conventions are wiped and those which are - # saved patched through site_config can be loaded without an exception - # being raised. - cube = Cube( - [1.0], - standard_name="air_temperature", - units="K", - attributes={"Conventions": "some user defined conventions"}, - ) - - # Patch the site configuration dictionary. - with _patch_site_configuration(), self.temp_filename(".nc") as nc_path: - iris.save(cube, nc_path) - res = iris.load_cube(nc_path) - - self.assertEqual( - res.attributes["Conventions"], - "{}, {}, {}".format( - CF_CONVENTIONS_VERSION, "convention1", "convention2" - ), - ) - - -class TestStandardName(tests.IrisTest): - def test_standard_name_roundtrip(self): - standard_name = "air_temperature detection_minimum" - cube = iris.cube.Cube(1, standard_name=standard_name) - with self.temp_filename(suffix=".nc") as fout: - iris.save(cube, fout) - detection_limit_cube = iris.load_cube(fout) - self.assertEqual(detection_limit_cube.standard_name, standard_name) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_aux_factories.py b/lib/iris/tests/integration/netcdf/test_aux_factories.py deleted file mode 100644 index d89f275336..0000000000 --- a/lib/iris/tests/integration/netcdf/test_aux_factories.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for aux-factory-related loading and saving netcdf files.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris -from iris.tests import stock as stock - - -@tests.skip_data -class TestAtmosphereSigma(tests.IrisTest): - def setUp(self): - # Modify stock cube so it is suitable to have a atmosphere sigma - # factory added to it. - cube = stock.realistic_4d_no_derived() - cube.coord("surface_altitude").rename("surface_air_pressure") - cube.coord("surface_air_pressure").units = "Pa" - cube.coord("sigma").units = "1" - ptop_coord = iris.coords.AuxCoord(1000.0, var_name="ptop", units="Pa") - cube.add_aux_coord(ptop_coord, ()) - cube.remove_coord("level_height") - # Construct and add atmosphere sigma factory. - factory = iris.aux_factory.AtmosphereSigmaFactory( - cube.coord("ptop"), - cube.coord("sigma"), - cube.coord("surface_air_pressure"), - ) - cube.add_aux_factory(factory) - self.cube = cube - - def test_save(self): - with self.temp_filename(suffix=".nc") as filename: - iris.save(self.cube, filename) - self.assertCDL(filename) - - def test_save_load_loop(self): - # Ensure that the AtmosphereSigmaFactory is automatically loaded - # when loading the file. - with self.temp_filename(suffix=".nc") as filename: - iris.save(self.cube, filename) - cube = iris.load_cube(filename, "air_potential_temperature") - assert cube.coords("air_pressure") - - -@tests.skip_data -class TestHybridPressure(tests.IrisTest): - def setUp(self): - # Modify stock cube so it is suitable to have a - # hybrid pressure factory added to it. - cube = stock.realistic_4d_no_derived() - cube.coord("surface_altitude").rename("surface_air_pressure") - cube.coord("surface_air_pressure").units = "Pa" - cube.coord("level_height").rename("level_pressure") - cube.coord("level_pressure").units = "Pa" - # Construct and add hybrid pressure factory. - factory = iris.aux_factory.HybridPressureFactory( - cube.coord("level_pressure"), - cube.coord("sigma"), - cube.coord("surface_air_pressure"), - ) - cube.add_aux_factory(factory) - self.cube = cube - - def test_save(self): - with self.temp_filename(suffix=".nc") as filename: - iris.save(self.cube, filename) - self.assertCDL(filename) - - def test_save_load_loop(self): - # Tests an issue where the variable names in the formula - # terms changed to the standard_names instead of the variable names - # when loading a previously saved cube. - with self.temp_filename(suffix=".nc") as filename, self.temp_filename( - suffix=".nc" - ) as other_filename: - iris.save(self.cube, filename) - cube = iris.load_cube(filename, "air_potential_temperature") - iris.save(cube, other_filename) - other_cube = iris.load_cube( - other_filename, "air_potential_temperature" - ) - self.assertEqual(cube, other_cube) - - -@tests.skip_data -class TestSaveMultipleAuxFactories(tests.IrisTest): - def test_hybrid_height_and_pressure(self): - cube = stock.realistic_4d() - cube.add_aux_coord( - iris.coords.DimCoord( - 1200.0, long_name="level_pressure", units="hPa" - ) - ) - cube.add_aux_coord( - iris.coords.DimCoord(0.5, long_name="other sigma", units="1") - ) - cube.add_aux_coord( - iris.coords.DimCoord( - 1000.0, long_name="surface_air_pressure", units="hPa" - ) - ) - factory = iris.aux_factory.HybridPressureFactory( - cube.coord("level_pressure"), - cube.coord("other sigma"), - cube.coord("surface_air_pressure"), - ) - cube.add_aux_factory(factory) - with self.temp_filename(suffix=".nc") as filename: - iris.save(cube, filename) - self.assertCDL(filename) - - def test_shared_primary(self): - cube = stock.realistic_4d() - factory = iris.aux_factory.HybridHeightFactory( - cube.coord("level_height"), - cube.coord("sigma"), - cube.coord("surface_altitude"), - ) - factory.rename("another altitude") - cube.add_aux_factory(factory) - with self.temp_filename( - suffix=".nc" - ) as filename, self.assertRaisesRegex( - ValueError, "multiple aux factories" - ): - iris.save(cube, filename) - - def test_hybrid_height_cubes(self): - hh1 = stock.simple_4d_with_hybrid_height() - hh1.attributes["cube"] = "hh1" - hh2 = stock.simple_4d_with_hybrid_height() - hh2.attributes["cube"] = "hh2" - sa = hh2.coord("surface_altitude") - sa.points = sa.points * 10 - with self.temp_filename(".nc") as fname: - iris.save([hh1, hh2], fname) - cubes = iris.load(fname, "air_temperature") - cubes = sorted(cubes, key=lambda cube: cube.attributes["cube"]) - self.assertCML(cubes) - - def test_hybrid_height_cubes_on_dimension_coordinate(self): - hh1 = stock.hybrid_height() - hh2 = stock.hybrid_height() - sa = hh2.coord("surface_altitude") - sa.points = sa.points * 10 - emsg = "Unable to create dimensonless vertical coordinate." - with self.temp_filename(".nc") as fname, self.assertRaisesRegex( - ValueError, emsg - ): - iris.save([hh1, hh2], fname) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_coord_systems.py b/lib/iris/tests/integration/netcdf/test_coord_systems.py deleted file mode 100644 index 8576f5ffe8..0000000000 --- a/lib/iris/tests/integration/netcdf/test_coord_systems.py +++ /dev/null @@ -1,281 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for coord-system-related loading and saving netcdf files.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from os.path import join as path_join -import shutil -import tempfile - -import iris -from iris.coords import DimCoord -from iris.cube import Cube -from iris.tests import stock as stock -from iris.tests.stock.netcdf import ncgen_from_cdl -from iris.tests.unit.fileformats.netcdf import test_load_cubes as tlc - - -@tests.skip_data -class TestCoordSystem(tests.IrisTest): - def setUp(self): - tlc.setUpModule() - - def tearDown(self): - tlc.tearDownModule() - - def test_load_laea_grid(self): - cube = iris.load_cube( - tests.get_data_path( - ("NetCDF", "lambert_azimuthal_equal_area", "euro_air_temp.nc") - ) - ) - self.assertCML(cube, ("netcdf", "netcdf_laea.cml")) - - datum_cf_var_cdl = """ - netcdf output { - dimensions: - y = 4 ; - x = 3 ; - variables: - float data(y, x) ; - data :standard_name = "toa_brightness_temperature" ; - data :units = "K" ; - data :grid_mapping = "mercator" ; - int mercator ; - mercator:grid_mapping_name = "mercator" ; - mercator:longitude_of_prime_meridian = 0. ; - mercator:earth_radius = 6378169. ; - mercator:horizontal_datum_name = "OSGB36" ; - float y(y) ; - y:axis = "Y" ; - y:units = "m" ; - y:standard_name = "projection_y_coordinate" ; - float x(x) ; - x:axis = "X" ; - x:units = "m" ; - x:standard_name = "projection_x_coordinate" ; - - // global attributes: - :Conventions = "CF-1.7" ; - :standard_name_vocabulary = "CF Standard Name Table v27" ; - - data: - - data = - 0, 1, 2, - 3, 4, 5, - 6, 7, 8, - 9, 10, 11 ; - - mercator = _ ; - - y = 1, 2, 3, 5 ; - - x = -6, -4, -2 ; - - } - """ - - datum_wkt_cdl = """ -netcdf output5 { -dimensions: - y = 4 ; - x = 3 ; -variables: - float data(y, x) ; - data :standard_name = "toa_brightness_temperature" ; - data :units = "K" ; - data :grid_mapping = "mercator" ; - int mercator ; - mercator:grid_mapping_name = "mercator" ; - mercator:longitude_of_prime_meridian = 0. ; - mercator:earth_radius = 6378169. ; - mercator:longitude_of_projection_origin = 0. ; - mercator:false_easting = 0. ; - mercator:false_northing = 0. ; - mercator:scale_factor_at_projection_origin = 1. ; - mercator:crs_wkt = "PROJCRS[\\"unknown\\",BASEGEOGCRS[\\"unknown\\",DATUM[\\"OSGB36\\",ELLIPSOID[\\"unknown\\",6378169,0,LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]],PRIMEM[\\"Greenwich\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8901]]],CONVERSION[\\"unknown\\",METHOD[\\"Mercator (variant B)\\",ID[\\"EPSG\\",9805]],PARAMETER[\\"Latitude of 1st standard parallel\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8823]],PARAMETER[\\"Longitude of natural origin\\",0,ANGLEUNIT[\\"degree\\",0.0174532925199433],ID[\\"EPSG\\",8802]],PARAMETER[\\"False easting\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8806]],PARAMETER[\\"False northing\\",0,LENGTHUNIT[\\"metre\\",1],ID[\\"EPSG\\",8807]]],CS[Cartesian,2],AXIS[\\"(E)\\",east,ORDER[1],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]],AXIS[\\"(N)\\",north,ORDER[2],LENGTHUNIT[\\"metre\\",1,ID[\\"EPSG\\",9001]]]]" ; - float y(y) ; - y:axis = "Y" ; - y:units = "m" ; - y:standard_name = "projection_y_coordinate" ; - float x(x) ; - x:axis = "X" ; - x:units = "m" ; - x:standard_name = "projection_x_coordinate" ; - -// global attributes: - :standard_name_vocabulary = "CF Standard Name Table v27" ; - :Conventions = "CF-1.7" ; -data: - - data = - 0, 1, 2, - 3, 4, 5, - 6, 7, 8, - 9, 10, 11 ; - - mercator = _ ; - - y = 1, 2, 3, 5 ; - - x = -6, -4, -2 ; -} - """ - - def test_load_datum_wkt(self): - expected = "OSGB 1936" - nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) - with iris.FUTURE.context(datum_support=True): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(expected, actual) - - def test_no_load_datum_wkt(self): - nc_path = tlc.cdl_to_nc(self.datum_wkt_cdl) - with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(actual, "unknown") - - def test_load_datum_cf_var(self): - expected = "OSGB 1936" - nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) - with iris.FUTURE.context(datum_support=True): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(expected, actual) - - def test_no_load_datum_cf_var(self): - nc_path = tlc.cdl_to_nc(self.datum_cf_var_cdl) - with self.assertWarnsRegex(FutureWarning, "iris.FUTURE.datum_support"): - cube = iris.load_cube(nc_path) - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(actual, "unknown") - - def test_save_datum(self): - expected = "OSGB 1936" - saved_crs = iris.coord_systems.Mercator( - ellipsoid=iris.coord_systems.GeogCS.from_datum("OSGB36") - ) - - base_cube = stock.realistic_3d() - base_lat_coord = base_cube.coord("grid_latitude") - test_lat_coord = DimCoord( - base_lat_coord.points, - standard_name="projection_y_coordinate", - coord_system=saved_crs, - ) - base_lon_coord = base_cube.coord("grid_longitude") - test_lon_coord = DimCoord( - base_lon_coord.points, - standard_name="projection_x_coordinate", - coord_system=saved_crs, - ) - test_cube = Cube( - base_cube.data, - standard_name=base_cube.standard_name, - units=base_cube.units, - dim_coords_and_dims=( - (base_cube.coord("time"), 0), - (test_lat_coord, 1), - (test_lon_coord, 2), - ), - ) - - with self.temp_filename(suffix=".nc") as filename: - iris.save(test_cube, filename) - with iris.FUTURE.context(datum_support=True): - cube = iris.load_cube(filename) - - test_crs = cube.coord("projection_y_coordinate").coord_system - actual = str(test_crs.as_cartopy_crs().datum) - self.assertMultiLineEqual(expected, actual) - - -class TestLoadMinimalGeostationary(tests.IrisTest): - """ - Check we can load data with a geostationary grid-mapping, even when the - 'false-easting' and 'false_northing' properties are missing. - - """ - - _geostationary_problem_cdl = """ -netcdf geostationary_problem_case { -dimensions: - y = 2 ; - x = 3 ; -variables: - short radiance(y, x) ; - radiance:standard_name = "toa_outgoing_radiance_per_unit_wavelength" ; - radiance:units = "W m-2 sr-1 um-1" ; - radiance:coordinates = "y x" ; - radiance:grid_mapping = "imager_grid_mapping" ; - short y(y) ; - y:units = "rad" ; - y:axis = "Y" ; - y:long_name = "fixed grid projection y-coordinate" ; - y:standard_name = "projection_y_coordinate" ; - short x(x) ; - x:units = "rad" ; - x:axis = "X" ; - x:long_name = "fixed grid projection x-coordinate" ; - x:standard_name = "projection_x_coordinate" ; - int imager_grid_mapping ; - imager_grid_mapping:grid_mapping_name = "geostationary" ; - imager_grid_mapping:perspective_point_height = 35786023. ; - imager_grid_mapping:semi_major_axis = 6378137. ; - imager_grid_mapping:semi_minor_axis = 6356752.31414 ; - imager_grid_mapping:latitude_of_projection_origin = 0. ; - imager_grid_mapping:longitude_of_projection_origin = -75. ; - imager_grid_mapping:sweep_angle_axis = "x" ; - -data: - - // coord values, just so these can be dim-coords - y = 0, 1 ; - x = 0, 1, 2 ; - -} -""" - - @classmethod - def setUpClass(cls): - # Create a temp directory for transient test files. - cls.temp_dir = tempfile.mkdtemp() - cls.path_test_cdl = path_join(cls.temp_dir, "geos_problem.cdl") - cls.path_test_nc = path_join(cls.temp_dir, "geos_problem.nc") - # Create reference CDL and netcdf files from the CDL text. - ncgen_from_cdl( - cdl_str=cls._geostationary_problem_cdl, - cdl_path=cls.path_test_cdl, - nc_path=cls.path_test_nc, - ) - - @classmethod - def tearDownClass(cls): - # Destroy the temp directory. - shutil.rmtree(cls.temp_dir) - - def test_geostationary_no_false_offsets(self): - # Check we can load the test data and coordinate system properties are correct. - cube = iris.load_cube(self.path_test_nc) - # Check the coordinate system properties has the correct default properties. - cs = cube.coord_system() - self.assertIsInstance(cs, iris.coord_systems.Geostationary) - self.assertEqual(cs.false_easting, 0.0) - self.assertEqual(cs.false_northing, 0.0) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_general.py b/lib/iris/tests/integration/netcdf/test_general.py deleted file mode 100644 index 63b977674d..0000000000 --- a/lib/iris/tests/integration/netcdf/test_general.py +++ /dev/null @@ -1,360 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for loading and saving netcdf files.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from itertools import repeat -import os.path -import shutil -import tempfile -import warnings - -import numpy as np -import numpy.ma as ma -import pytest - -import iris -import iris.coord_systems -from iris.coords import CellMethod -from iris.cube import Cube, CubeList -import iris.exceptions -from iris.fileformats.netcdf import Saver, UnknownCellMethodWarning -from iris.tests.stock.netcdf import ncgen_from_cdl - - -class TestLazySave(tests.IrisTest): - @tests.skip_data - def test_lazy_preserved_save(self): - fpath = tests.get_data_path( - ("NetCDF", "label_and_climate", "small_FC_167_mon_19601101.nc") - ) - acube = iris.load_cube(fpath, "air_temperature") - self.assertTrue(acube.has_lazy_data()) - # Also check a coord with lazy points + bounds. - self.assertTrue(acube.coord("forecast_period").has_lazy_points()) - self.assertTrue(acube.coord("forecast_period").has_lazy_bounds()) - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(acube) - # Check that cube data is not realised, also coord points + bounds. - self.assertTrue(acube.has_lazy_data()) - self.assertTrue(acube.coord("forecast_period").has_lazy_points()) - self.assertTrue(acube.coord("forecast_period").has_lazy_bounds()) - - -@tests.skip_data -class TestCellMeasures(tests.IrisTest): - def setUp(self): - self.fname = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) - - def test_load_raw(self): - (cube,) = iris.load_raw(self.fname) - self.assertEqual(len(cube.cell_measures()), 1) - self.assertEqual(cube.cell_measures()[0].measure, "area") - - def test_load(self): - cube = iris.load_cube(self.fname) - self.assertEqual(len(cube.cell_measures()), 1) - self.assertEqual(cube.cell_measures()[0].measure, "area") - - def test_merge_cell_measure_aware(self): - (cube1,) = iris.load_raw(self.fname) - (cube2,) = iris.load_raw(self.fname) - cube2._cell_measures_and_dims[0][0].var_name = "not_areat" - cubes = CubeList([cube1, cube2]).merge() - self.assertEqual(len(cubes), 2) - - def test_concatenate_cell_measure_aware(self): - (cube1,) = iris.load_raw(self.fname) - cube1 = cube1[:, :, 0, 0] - cm_and_dims = cube1._cell_measures_and_dims - (cube2,) = iris.load_raw(self.fname) - cube2 = cube2[:, :, 0, 0] - cube2._cell_measures_and_dims[0][0].var_name = "not_areat" - cube2.coord("time").points = cube2.coord("time").points + 1 - cubes = CubeList([cube1, cube2]).concatenate() - self.assertEqual(cubes[0]._cell_measures_and_dims, cm_and_dims) - self.assertEqual(len(cubes), 2) - - def test_concatenate_cell_measure_match(self): - (cube1,) = iris.load_raw(self.fname) - cube1 = cube1[:, :, 0, 0] - cm_and_dims = cube1._cell_measures_and_dims - (cube2,) = iris.load_raw(self.fname) - cube2 = cube2[:, :, 0, 0] - cube2.coord("time").points = cube2.coord("time").points + 1 - cubes = CubeList([cube1, cube2]).concatenate() - self.assertEqual(cubes[0]._cell_measures_and_dims, cm_and_dims) - self.assertEqual(len(cubes), 1) - - def test_round_trip(self): - (cube,) = iris.load(self.fname) - with self.temp_filename(suffix=".nc") as filename: - iris.save(cube, filename, unlimited_dimensions=[]) - (round_cube,) = iris.load_raw(filename) - self.assertEqual(len(round_cube.cell_measures()), 1) - self.assertEqual(round_cube.cell_measures()[0].measure, "area") - - def test_print(self): - cube = iris.load_cube(self.fname) - printed = cube.__str__() - self.assertIn( - ( - "Cell measures:\n" - " cell_area - - " - " x x" - ), - printed, - ) - - -class TestCellMethod_unknown(tests.IrisTest): - def test_unknown_method(self): - cube = Cube([1, 2], long_name="odd_phenomenon") - cube.add_cell_method(CellMethod(method="oddity", coords=("x",))) - temp_dirpath = tempfile.mkdtemp() - try: - temp_filepath = os.path.join(temp_dirpath, "tmp.nc") - iris.save(cube, temp_filepath) - with warnings.catch_warnings(record=True) as warning_records: - iris.load(temp_filepath) - # Filter to get the warning we are interested in. - warning_messages = [record.message for record in warning_records] - warning_messages = [ - warn - for warn in warning_messages - if isinstance(warn, UnknownCellMethodWarning) - ] - self.assertEqual(len(warning_messages), 1) - message = warning_messages[0].args[0] - msg = ( - "NetCDF variable 'odd_phenomenon' contains unknown cell " - "method 'oddity'" - ) - self.assertIn(msg, message) - finally: - shutil.rmtree(temp_dirpath) - - -def _get_scale_factor_add_offset(cube, datatype): - """Utility function used by netCDF data packing tests.""" - if isinstance(datatype, dict): - dt = np.dtype(datatype["dtype"]) - else: - dt = np.dtype(datatype) - cmax = cube.data.max() - cmin = cube.data.min() - n = dt.itemsize * 8 - if ma.isMaskedArray(cube.data): - masked = True - else: - masked = False - if masked: - scale_factor = (cmax - cmin) / (2**n - 2) - else: - scale_factor = (cmax - cmin) / (2**n - 1) - if dt.kind == "u": - add_offset = cmin - elif dt.kind == "i": - if masked: - add_offset = (cmax + cmin) / 2 - else: - add_offset = cmin + 2 ** (n - 1) * scale_factor - return (scale_factor, add_offset) - - -@tests.skip_data -class TestPackedData(tests.IrisTest): - def _single_test(self, datatype, CDLfilename, manual=False): - # Read PP input file. - file_in = tests.get_data_path( - ( - "PP", - "cf_processing", - "000003000000.03.236.000128.1990.12.01.00.00.b.pp", - ) - ) - cube = iris.load_cube(file_in) - scale_factor, offset = _get_scale_factor_add_offset(cube, datatype) - if manual: - packspec = dict( - dtype=datatype, scale_factor=scale_factor, add_offset=offset - ) - else: - packspec = datatype - # Write Cube to netCDF file. - with self.temp_filename(suffix=".nc") as file_out: - iris.save(cube, file_out, packing=packspec) - decimal = int(-np.log10(scale_factor)) - packedcube = iris.load_cube(file_out) - # Check that packed cube is accurate to expected precision - self.assertArrayAlmostEqual( - cube.data, packedcube.data, decimal=decimal - ) - # Check the netCDF file against CDL expected output. - self.assertCDL( - file_out, - ( - "integration", - "netcdf", - "general", - "TestPackedData", - CDLfilename, - ), - ) - - def test_single_packed_signed(self): - """Test saving a single CF-netCDF file with packing.""" - self._single_test("i2", "single_packed_signed.cdl") - - def test_single_packed_unsigned(self): - """Test saving a single CF-netCDF file with packing into unsigned.""" - self._single_test("u1", "single_packed_unsigned.cdl") - - def test_single_packed_manual_scale(self): - """Test saving a single CF-netCDF file with packing with scale - factor and add_offset set manually.""" - self._single_test("i2", "single_packed_manual.cdl", manual=True) - - def _multi_test(self, CDLfilename, multi_dtype=False): - """Test saving multiple packed cubes with pack_dtype list.""" - # Read PP input file. - file_in = tests.get_data_path( - ("PP", "cf_processing", "abcza_pa19591997_daily_29.b.pp") - ) - cubes = iris.load(file_in) - # ensure cube order is the same: - cubes.sort(key=lambda cube: cube.cell_methods[0].method) - datatype = "i2" - scale_factor, offset = _get_scale_factor_add_offset(cubes[0], datatype) - if multi_dtype: - packdict = dict( - dtype=datatype, scale_factor=scale_factor, add_offset=offset - ) - packspec = [packdict, None, "u2"] - dtypes = packspec - else: - packspec = datatype - dtypes = repeat(packspec) - - # Write Cube to netCDF file. - with self.temp_filename(suffix=".nc") as file_out: - iris.save(cubes, file_out, packing=packspec) - # Check the netCDF file against CDL expected output. - self.assertCDL( - file_out, - ( - "integration", - "netcdf", - "general", - "TestPackedData", - CDLfilename, - ), - ) - packedcubes = iris.load(file_out) - packedcubes.sort(key=lambda cube: cube.cell_methods[0].method) - for cube, packedcube, dtype in zip(cubes, packedcubes, dtypes): - if dtype: - sf, ao = _get_scale_factor_add_offset(cube, dtype) - decimal = int(-np.log10(sf)) - # Check that packed cube is accurate to expected precision - self.assertArrayAlmostEqual( - cube.data, packedcube.data, decimal=decimal - ) - else: - self.assertArrayEqual(cube.data, packedcube.data) - - def test_multi_packed_single_dtype(self): - """Test saving multiple packed cubes with the same pack_dtype.""" - # Read PP input file. - self._multi_test("multi_packed_single_dtype.cdl") - - def test_multi_packed_multi_dtype(self): - """Test saving multiple packed cubes with pack_dtype list.""" - # Read PP input file. - self._multi_test("multi_packed_multi_dtype.cdl", multi_dtype=True) - - -class TestScalarCube(tests.IrisTest): - def test_scalar_cube_save_load(self): - cube = iris.cube.Cube(1, long_name="scalar_cube") - with self.temp_filename(suffix=".nc") as fout: - iris.save(cube, fout) - scalar_cube = iris.load_cube(fout) - self.assertEqual(scalar_cube.name(), "scalar_cube") - - -@tests.skip_data -class TestConstrainedLoad(tests.IrisTest): - filename = tests.get_data_path( - ("NetCDF", "label_and_climate", "A1B-99999a-river-sep-2070-2099.nc") - ) - - def test_netcdf_with_NameConstraint(self): - constr = iris.NameConstraint(var_name="cdf_temp_dmax_tmean_abs") - cubes = iris.load(self.filename, constr) - self.assertEqual(len(cubes), 1) - self.assertEqual(cubes[0].var_name, "cdf_temp_dmax_tmean_abs") - - def test_netcdf_with_no_constraint(self): - cubes = iris.load(self.filename) - self.assertEqual(len(cubes), 3) - - -class TestSkippedCoord: - # If a coord/cell measure/etcetera cannot be added to the loaded Cube, a - # Warning is raised and the coord is skipped. - # This 'catching' is generic to all CannotAddErrors, but currently the only - # such problem that can exist in a NetCDF file is a mismatch of dimensions - # between phenomenon and coord. - - cdl_core = """ -dimensions: - length_scale = 1 ; - lat = 3 ; -variables: - float lat(lat) ; - lat:standard_name = "latitude" ; - lat:units = "degrees_north" ; - short lst_unc_sys(length_scale) ; - lst_unc_sys:long_name = "uncertainty from large-scale systematic - errors" ; - lst_unc_sys:units = "kelvin" ; - lst_unc_sys:coordinates = "lat" ; - -data: - lat = 0, 1, 2; - """ - - @pytest.fixture(autouse=True) - def create_nc_file(self, tmp_path): - file_name = "dim_mismatch" - cdl = f"netcdf {file_name}" + "{\n" + self.cdl_core + "\n}" - self.nc_path = (tmp_path / file_name).with_suffix(".nc") - ncgen_from_cdl( - cdl_str=cdl, - cdl_path=None, - nc_path=str(self.nc_path), - ) - yield - self.nc_path.unlink() - - def test_lat_not_loaded(self): - # iris#5068 includes discussion of possible retention of the skipped - # coords in the future. - with pytest.warns( - match="Missing data dimensions for multi-valued DimCoord" - ): - cube = iris.load_cube(self.nc_path) - with pytest.raises(iris.exceptions.CoordinateNotFoundError): - _ = cube.coord("lat") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_self_referencing.py b/lib/iris/tests/integration/netcdf/test_self_referencing.py deleted file mode 100644 index 3395296e11..0000000000 --- a/lib/iris/tests/integration/netcdf/test_self_referencing.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for iris#3367 - loading a self-referencing NetCDF file.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import os -import tempfile -from unittest import mock - -import numpy as np - -import iris -from iris.fileformats.netcdf import _thread_safe_nc - - -@tests.skip_data -class TestCMIP6VolcelloLoad(tests.IrisTest): - def setUp(self): - self.fname = tests.get_data_path( - ( - "NetCDF", - "volcello", - "volcello_Ofx_CESM2_deforest-globe_r1i1p1f1_gn.nc", - ) - ) - - def test_cmip6_volcello_load_issue_3367(self): - # Ensure that reading a file which references itself in - # `cell_measures` can be read. At the same time, ensure that we - # still receive a warning about other variables mentioned in - # `cell_measures` i.e. a warning should be raised about missing - # areacello. - areacello_str = "areacello" - volcello_str = "volcello" - expected_msg = ( - "Missing CF-netCDF measure variable %r, " - "referenced by netCDF variable %r" % (areacello_str, volcello_str) - ) - - with mock.patch("warnings.warn") as warn: - # ensure file loads without failure - cube = iris.load_cube(self.fname) - warn.assert_has_calls([mock.call(expected_msg)]) - - # extra check to ensure correct variable was found - assert cube.standard_name == "ocean_volume" - - -class TestSelfReferencingVarLoad(tests.IrisTest): - def setUp(self): - self.temp_dir_path = os.path.join( - tempfile.mkdtemp(), "issue_3367_volcello_test_file.nc" - ) - dataset = _thread_safe_nc.DatasetWrapper(self.temp_dir_path, "w") - - dataset.createDimension("lat", 4) - dataset.createDimension("lon", 5) - dataset.createDimension("lev", 3) - - latitudes = dataset.createVariable("lat", np.float64, ("lat",)) - longitudes = dataset.createVariable("lon", np.float64, ("lon",)) - levels = dataset.createVariable("lev", np.float64, ("lev",)) - volcello = dataset.createVariable( - "volcello", np.float32, ("lat", "lon", "lev") - ) - - latitudes.standard_name = "latitude" - latitudes.units = "degrees_north" - latitudes.axis = "Y" - latitudes[:] = np.linspace(-90, 90, 4) - - longitudes.standard_name = "longitude" - longitudes.units = "degrees_east" - longitudes.axis = "X" - longitudes[:] = np.linspace(0, 360, 5) - - levels.standard_name = "olevel" - levels.units = "centimeters" - levels.positive = "down" - levels.axis = "Z" - levels[:] = np.linspace(0, 10**5, 3) - - volcello.id = "volcello" - volcello.out_name = "volcello" - volcello.standard_name = "ocean_volume" - volcello.units = "m3" - volcello.realm = "ocean" - volcello.frequency = "fx" - volcello.cell_measures = "area: areacello volume: volcello" - volcello = np.arange(4 * 5 * 3).reshape((4, 5, 3)) - - dataset.close() - - def test_self_referencing_load_issue_3367(self): - # Ensure that reading a file which references itself in - # `cell_measures` can be read. At the same time, ensure that we - # still receive a warning about other variables mentioned in - # `cell_measures` i.e. a warning should be raised about missing - # areacello. - areacello_str = "areacello" - volcello_str = "volcello" - expected_msg = ( - "Missing CF-netCDF measure variable %r, " - "referenced by netCDF variable %r" % (areacello_str, volcello_str) - ) - - with mock.patch("warnings.warn") as warn: - # ensure file loads without failure - cube = iris.load_cube(self.temp_dir_path) - warn.assert_called_with(expected_msg) - - # extra check to ensure correct variable was found - assert cube.standard_name == "ocean_volume" - - def tearDown(self): - os.remove(self.temp_dir_path) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/netcdf/test_thread_safety.py b/lib/iris/tests/integration/netcdf/test_thread_safety.py deleted file mode 100644 index 280e0f8418..0000000000 --- a/lib/iris/tests/integration/netcdf/test_thread_safety.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Integration tests covering thread safety during loading/saving netcdf files. - -These tests are intended to catch non-thread-safe behaviour by producing CI -'irregularities' that are noticed and investigated. They cannot reliably -produce standard pytest failures, since the tools for 'correctly' -testing non-thread-safe behaviour are not available at the Python layer. -Thread safety problems can be either produce errors (like a normal test) OR -segfaults (test doesn't complete, pytest-xdiff starts a new group worker, the -end exit code is still non-0), and some problems do not occur in every test -run. - -Token assertions are included after the line that is expected to reveal -a thread safety problem, as this seems to be good testing practice. - -""" -from pathlib import Path - -import dask -from dask import array as da -import numpy as np -import pytest - -import iris -from iris.cube import Cube, CubeList -from iris.tests import get_data_path - - -@pytest.fixture -def tiny_chunks(): - """Guarantee that Dask will use >1 thread by guaranteeing >1 chunk.""" - - def _check_tiny_loaded_chunks(cube: Cube): - assert cube.has_lazy_data() - cube_lazy_data = cube.core_data() - assert np.product(cube_lazy_data.chunksize) < cube_lazy_data.size - - with dask.config.set({"array.chunk-size": "1KiB"}): - yield _check_tiny_loaded_chunks - - -@pytest.fixture -def save_common(tmp_path): - save_path = tmp_path / "tmp.nc" - - def _func(cube: Cube): - assert not save_path.exists() - iris.save(cube, save_path) - assert save_path.exists() - - yield _func - - -@pytest.fixture -def get_cubes_from_netcdf(): - load_dir_path = Path(get_data_path(["NetCDF", "global", "xyt"])) - loaded = iris.load(load_dir_path.glob("*"), "tcco2") - smaller = CubeList([c[0] for c in loaded]) - yield smaller - - -def test_realise_data(tiny_chunks, get_cubes_from_netcdf): - cube = get_cubes_from_netcdf[0] - tiny_chunks(cube) - _ = cube.data # Any problems are expected here. - assert not cube.has_lazy_data() - - -def test_realise_data_multisource(get_cubes_from_netcdf): - """Load from multiple sources to force Dask to use multiple threads.""" - cubes = get_cubes_from_netcdf - final_cube = sum(cubes) - _ = final_cube.data # Any problems are expected here. - assert not final_cube.has_lazy_data() - - -def test_save(tiny_chunks, save_common): - cube = Cube(da.ones(10000)) - tiny_chunks(cube) - save_common(cube) # Any problems are expected here. - - -def test_stream(tiny_chunks, get_cubes_from_netcdf, save_common): - cube = get_cubes_from_netcdf[0] - tiny_chunks(cube) - save_common(cube) # Any problems are expected here. - - -def test_stream_multisource(get_cubes_from_netcdf, save_common): - """Load from multiple sources to force Dask to use multiple threads.""" - cubes = get_cubes_from_netcdf - final_cube = sum(cubes) - save_common(final_cube) # Any problems are expected here. - - -def test_comparison(get_cubes_from_netcdf): - """ - Comparing multiple loaded files forces co-realisation. - - See :func:`iris._lazy_data._co_realise_lazy_arrays` . - """ - cubes = get_cubes_from_netcdf - _ = cubes[:-1] == cubes[1:] # Any problems are expected here. - assert all([c.has_lazy_data() for c in cubes]) diff --git a/lib/iris/tests/integration/plot/__init__.py b/lib/iris/tests/integration/plot/__init__.py deleted file mode 100644 index aafa488e2d..0000000000 --- a/lib/iris/tests/integration/plot/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for the :mod:`iris.plot` package.""" diff --git a/lib/iris/tests/integration/plot/test_animate.py b/lib/iris/tests/integration/plot/test_animate.py deleted file mode 100644 index ef19dbb108..0000000000 --- a/lib/iris/tests/integration/plot/test_animate.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Integration tests for :func:`iris.plot.animate`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np - -import iris -from iris.coord_systems import GeogCS - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class IntegrationTest(tests.GraphicsTest): - def setUp(self): - super().setUp() - cube = iris.cube.Cube(np.arange(36, dtype=np.int32).reshape((3, 3, 4))) - cs = GeogCS(6371229) - - coord = iris.coords.DimCoord( - points=np.array([1, 2, 3], dtype=np.int32), long_name="time" - ) - cube.add_dim_coord(coord, 0) - - coord = iris.coords.DimCoord( - points=np.array([-1, 0, 1], dtype=np.int32), - standard_name="latitude", - units="degrees", - coord_system=cs, - ) - cube.add_dim_coord(coord, 1) - coord = iris.coords.DimCoord( - points=np.array([-1, 0, 1, 2], dtype=np.int32), - standard_name="longitude", - units="degrees", - coord_system=cs, - ) - cube.add_dim_coord(coord, 2) - self.cube = cube - - def test_cube_animation(self): - # This follows :meth:`~matplotlib.animation.FuncAnimation.save` - # to ensure that each frame corresponds to known accepted frames for - # the animation. - cube_iter = self.cube.slices(("latitude", "longitude")) - - ani = iplt.animate(cube_iter, iplt.contourf) - - # Disconnect the first draw callback to stop the animation. - ani._fig.canvas.mpl_disconnect(ani._first_draw_id) - # Update flag to indicate drawing happens. Without this, a warning is - # thrown when the ani object is destroyed, and this warning sometimes - # interferes with unrelated tests (#4330). - ani._draw_was_started = True - - ani = [ani] - # Extract frame data - for data in zip(*[a.new_saved_frame_seq() for a in ani]): - # Draw each frame - for anim, d in zip(ani, data): - anim._draw_next_frame(d, blit=False) - self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/plot/test_colorbar.py b/lib/iris/tests/integration/plot/test_colorbar.py deleted file mode 100644 index a306e6c82f..0000000000 --- a/lib/iris/tests/integration/plot/test_colorbar.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test interaction between :mod:`iris.plot` and -:func:`matplotlib.pyplot.colorbar` - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.coords import AuxCoord -import iris.tests.stock - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import matplotlib.pyplot as plt - - from iris.plot import ( - contour, - contourf, - pcolor, - pcolormesh, - points, - scatter, - ) - - -@tests.skip_plot -class TestColorBarCreation(tests.GraphicsTest): - def setUp(self): - super().setUp() - self.draw_functions = (contour, contourf, pcolormesh, pcolor) - self.cube = iris.tests.stock.lat_lon_cube() - self.cube.coord("longitude").guess_bounds() - self.cube.coord("latitude").guess_bounds() - self.traj_lon = AuxCoord( - np.linspace(-180, 180, 50), - standard_name="longitude", - units="degrees", - ) - self.traj_lat = AuxCoord( - np.sin(np.deg2rad(self.traj_lon.points)) * 30.0, - standard_name="latitude", - units="degrees", - ) - - def test_common_draw_functions(self): - for draw_function in self.draw_functions: - mappable = draw_function(self.cube) - cbar = plt.colorbar() - self.assertIs( - cbar.mappable, - mappable, - msg="Problem with draw function iris.plot.{}".format( - draw_function.__name__ - ), - ) - - def test_common_draw_functions_specified_mappable(self): - for draw_function in self.draw_functions: - mappable_initial = draw_function(self.cube, cmap="cool") - _ = draw_function(self.cube) - cbar = plt.colorbar(mappable_initial) - self.assertIs( - cbar.mappable, - mappable_initial, - msg="Problem with draw function iris.plot.{}".format( - draw_function.__name__ - ), - ) - - def test_points_with_c_kwarg(self): - mappable = points(self.cube, c=self.cube.data) - cbar = plt.colorbar() - self.assertIs(cbar.mappable, mappable) - - def test_points_with_c_kwarg_specified_mappable(self): - mappable_initial = points(self.cube, c=self.cube.data, cmap="cool") - _ = points(self.cube, c=self.cube.data) - cbar = plt.colorbar(mappable_initial) - self.assertIs(cbar.mappable, mappable_initial) - - def test_scatter_with_c_kwarg(self): - mappable = scatter( - self.traj_lon, self.traj_lat, c=self.traj_lon.points - ) - cbar = plt.colorbar() - self.assertIs(cbar.mappable, mappable) - - def test_scatter_with_c_kwarg_specified_mappable(self): - mappable_initial = scatter( - self.traj_lon, self.traj_lat, c=self.traj_lon.points - ) - _ = scatter( - self.traj_lon, self.traj_lat, c=self.traj_lon.points, cmap="cool" - ) - cbar = plt.colorbar(mappable_initial) - self.assertIs(cbar.mappable, mappable_initial) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/plot/test_netcdftime.py b/lib/iris/tests/integration/plot/test_netcdftime.py deleted file mode 100644 index d438c09bd5..0000000000 --- a/lib/iris/tests/integration/plot/test_netcdftime.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test plot of time coord with non-standard calendar. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from cf_units import Unit -import cftime -import numpy as np - -from iris.coords import AuxCoord - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_nc_time_axis -@tests.skip_plot -class Test(tests.GraphicsTest): - def test_360_day_calendar(self): - n = 360 - calendar = "360_day" - time_unit = Unit("days since 1970-01-01 00:00", calendar=calendar) - time_coord = AuxCoord(np.arange(n), "time", units=time_unit) - times = [time_unit.num2date(point) for point in time_coord.points] - times = [ - cftime.datetime( - atime.year, - atime.month, - atime.day, - atime.hour, - atime.minute, - atime.second, - calendar=calendar, - ) - for atime in times - ] - - expected_ydata = times - (line1,) = iplt.plot(time_coord) - result_ydata = line1.get_ydata() - self.assertArrayEqual(expected_ydata, result_ydata) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/plot/test_nzdateline.py b/lib/iris/tests/integration/plot/test_nzdateline.py deleted file mode 100644 index 0051549794..0000000000 --- a/lib/iris/tests/integration/plot/test_nzdateline.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test set up of limited area map extents which bridge the date line. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import iris - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import matplotlib.pyplot as plt - - from iris.plot import pcolormesh - - -@tests.skip_plot -@tests.skip_data -class TestExtent(tests.IrisTest): - def test_dateline(self): - dpath = tests.get_data_path(["PP", "nzgust.pp"]) - cube = iris.load_cube(dpath) - pcolormesh(cube) - # Ensure that the limited area expected for NZ is set. - # This is set in longitudes with the datum set to the - # International Date Line. - self.assertTrue( - -10 < plt.gca().get_xlim()[0] < -5 - and 5 < plt.gca().get_xlim()[1] < 10 - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/plot/test_plot_2d_coords.py b/lib/iris/tests/integration/plot/test_plot_2d_coords.py deleted file mode 100644 index 1b95899803..0000000000 --- a/lib/iris/tests/integration/plot/test_plot_2d_coords.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test plots with two dimensional coordinates. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs -import matplotlib.pyplot as plt -import numpy as np - -import iris -from iris.analysis.cartography import unrotate_pole -from iris.coords import AuxCoord -from iris.cube import Cube - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_data -def simple_cube_w_2d_coords(): - path = tests.get_data_path(("NetCDF", "ORCA2", "votemper.nc")) - cube = iris.load_cube(path) - return cube - - -@tests.skip_plot -@tests.skip_data -class Test(tests.GraphicsTest): - def test_2d_coord_bounds_platecarree(self): - # To avoid a problem with Cartopy smearing the data where the - # longitude wraps, we set the central_longitude. - # SciTools/cartopy#1421 - cube = simple_cube_w_2d_coords()[0, 0] - ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180)) - qplt.pcolormesh(cube) - - # Cartopy can't reliably set y-limits with curvilinear plotting. - # SciTools/cartopy#2121 - y_lims = [m(cube.coord("latitude").points) for m in (np.min, np.max)] - ax.set_ylim(*y_lims) - - ax.coastlines(resolution="110m", color="red") - self.check_graphic() - - def test_2d_coord_bounds_northpolarstereo(self): - cube = simple_cube_w_2d_coords()[0, 0] - ax = plt.axes(projection=ccrs.NorthPolarStereo()) - qplt.pcolormesh(cube) - ax.coastlines(resolution="110m", color="red") - self.check_graphic() - - -@tests.skip_plot -class Test2dContour(tests.GraphicsTest): - def test_2d_coords_contour(self): - ny, nx = 4, 6 - x1 = np.linspace(-20, 70, nx) - y1 = np.linspace(10, 60, ny) - data = np.zeros((ny, nx)) - data.flat[:] = np.arange(nx * ny) % 7 - cube = Cube(data, long_name="Odd data") - x2, y2 = np.meshgrid(x1, y1) - true_lons, true_lats = unrotate_pole(x2, y2, -130.0, 77.0) - co_x = AuxCoord(true_lons, standard_name="longitude", units="degrees") - co_y = AuxCoord(true_lats, standard_name="latitude", units="degrees") - cube.add_aux_coord(co_y, (0, 1)) - cube.add_aux_coord(co_x, (0, 1)) - ax = plt.axes(projection=ccrs.PlateCarree()) - qplt.contourf(cube) - ax.coastlines(resolution="110m", color="red") - ax.gridlines(draw_labels=True) - ax.set_extent((0, 180, 0, 90)) - self.check_graphic() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/plot/test_vector_plots.py b/lib/iris/tests/integration/plot/test_vector_plots.py deleted file mode 100644 index 37f506bd17..0000000000 --- a/lib/iris/tests/integration/plot/test_vector_plots.py +++ /dev/null @@ -1,233 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test some key usages of :func:`iris.plot.quiver`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs -import numpy as np - -from iris.coord_systems import Mercator -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube -from iris.tests.stock import sample_2d_latlons - -# Run tests in no graphics mode if matplotlib is not available. -if tests.MPL_AVAILABLE: - import matplotlib.pyplot as plt - - from iris.plot import barbs, quiver - - -@tests.skip_plot -class MixinVectorPlotCases: - """ - Test examples mixin, used by separate barb, quiver + streamplot classes. - - NOTE: at present for barb and quiver only, as streamplot does not support - arbitrary coordinates. - - """ - - def plot(self, plotname, *args, **kwargs): - plot_function = self.plot_function_to_test() - plot_function(*args, **kwargs) - plt.suptitle(plotname) - - @staticmethod - def _nonlatlon_xyuv(): - # Create common x, y, u, v arrays for quiver/streamplot testing. - x = np.array([0.0, 2, 3, 5]) - y = np.array([0.0, 2.5, 4]) - uv = np.array( - [ - [(0.0, 0), (0, 1), (0, -1), (2, 1)], - [(-1, 0), (-1, -1), (-1, 1), (-2, 1)], - [(1.0, 0), (1, -1), (1, 1), (-2, 2)], - ] - ) - uv = np.array(uv) - u, v = uv[..., 0], uv[..., 1] - return x, y, u, v - - @staticmethod - def _nonlatlon_uv_cubes(x, y, u, v): - # Create u and v test cubes from x, y, u, v arrays. - coord_cls = DimCoord if x.ndim == 1 else AuxCoord - x_coord = coord_cls(x, long_name="x") - y_coord = coord_cls(y, long_name="y") - u_cube = Cube(u, long_name="u", units="ms-1") - if x.ndim == 1: - u_cube.add_dim_coord(y_coord, 0) - u_cube.add_dim_coord(x_coord, 1) - else: - u_cube.add_aux_coord(y_coord, (0, 1)) - u_cube.add_aux_coord(x_coord, (0, 1)) - v_cube = u_cube.copy() - v_cube.rename("v") - v_cube.data = v - return u_cube, v_cube - - def test_non_latlon_1d_coords(self): - # Plot against simple 1D x and y coords. - x, y, u, v = self._nonlatlon_xyuv() - u_cube, v_cube = self._nonlatlon_uv_cubes(x, y, u, v) - self.plot("nonlatlon, 1-d coords", u_cube, v_cube) - plt.xlim(x.min() - 1, x.max() + 2) - plt.ylim(y.min() - 1, y.max() + 2) - self.check_graphic() - - def test_non_latlon_2d_coords(self): - # Plot against expanded 2D x and y coords. - x, y, u, v = self._nonlatlon_xyuv() - x, y = np.meshgrid(x, y) - u_cube, v_cube = self._nonlatlon_uv_cubes(x, y, u, v) - # Call plot : N.B. default gives wrong coords order. - self.plot("nonlatlon_2d", u_cube, v_cube, coords=("x", "y")) - plt.xlim(x.min() - 1, x.max() + 2) - plt.ylim(y.min() - 1, y.max() + 2) - self.check_graphic() - - @staticmethod - def _latlon_uv_cubes(grid_cube): - # Make a sample grid into u and v data for quiver/streamplot testing. - u_cube = grid_cube.copy() - u_cube.rename("dx") - u_cube.units = "ms-1" - v_cube = u_cube.copy() - v_cube.rename("dy") - ny, nx = u_cube.shape - nn = nx * ny - angles = np.arange(nn).reshape((ny, nx)) - angles = (angles * 360.0 / 5.5) % 360.0 - scale = np.arange(nn) % 5 - scale = (scale + 4) / 4 - scale = scale.reshape((ny, nx)) - u_cube.data = scale * np.cos(np.deg2rad(angles)) - v_cube.data = scale * np.sin(np.deg2rad(angles)) - return u_cube, v_cube - - def test_2d_plain_latlon(self): - # Test 2d vector plotting with implicit (PlateCarree) coord system. - u_cube, v_cube = self._latlon_uv_cubes(sample_2d_latlons()) - ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180)) - self.plot( - "latlon_2d", u_cube, v_cube, coords=("longitude", "latitude") - ) - ax.coastlines(resolution="110m", color="red") - ax.set_global() - self.check_graphic() - - def test_2d_plain_latlon_on_polar_map(self): - # Test 2d vector plotting onto a different projection. - u_cube, v_cube = self._latlon_uv_cubes(sample_2d_latlons()) - ax = plt.axes(projection=ccrs.NorthPolarStereo()) - self.plot( - "latlon_2d_polar", u_cube, v_cube, coords=("longitude", "latitude") - ) - ax.coastlines(resolution="110m", color="red") - self.check_graphic() - - def test_2d_rotated_latlon(self): - # Test plotting vectors in a rotated latlon coord system. - u_cube, v_cube = self._latlon_uv_cubes(sample_2d_latlons(rotated=True)) - ax = plt.axes(projection=ccrs.PlateCarree(central_longitude=180)) - self.plot( - "2d_rotated", u_cube, v_cube, coords=("longitude", "latitude") - ) - ax.coastlines(resolution="110m", color="red") - ax.set_global() - self.check_graphic() - - def test_fail_unsupported_coord_system(self): - # Test plotting vectors in a rotated latlon coord system. - u_cube, v_cube = self._latlon_uv_cubes(sample_2d_latlons()) - patch_coord_system = Mercator() - for cube in u_cube, v_cube: - for coord in cube.coords(): - coord.coord_system = patch_coord_system - re_msg = ( - r"Can only plot .* lat-lon projection, .* " - r"This .* translates as Cartopy \+proj=merc .*" - ) - with self.assertRaisesRegex(ValueError, re_msg): - self.plot( - "2d_rotated", u_cube, v_cube, coords=("longitude", "latitude") - ) - - def test_circular_longitude(self): - # Test circular longitude does not cause a crash. - res = 5 - lat = DimCoord( - np.arange(-90, 91, res), "latitude", units="degrees_north" - ) - lon = DimCoord( - np.arange(0, 360, res), - "longitude", - units="degrees_east", - circular=True, - ) - nlat = len(lat.points) - nlon = len(lon.points) - u_arr = np.ones((nlat, nlon)) - v_arr = np.ones((nlat, nlon)) - u_cube = Cube( - u_arr, - dim_coords_and_dims=[(lat, 0), (lon, 1)], - standard_name="eastward_wind", - ) - v_cube = Cube( - v_arr, - dim_coords_and_dims=[(lat, 0), (lon, 1)], - standard_name="northward_wind", - ) - - self.plot("circular", u_cube, v_cube, coords=("longitude", "latitude")) - - -class TestBarbs(MixinVectorPlotCases, tests.GraphicsTest): - def setUp(self): - super().setUp() - - @staticmethod - def _nonlatlon_xyuv(): - # Increase the range of wind speeds used in the barbs test to test more - # barbs shapes than just circles - x, y, u, v = MixinVectorPlotCases._nonlatlon_xyuv() - scale_factor = 50 - u *= scale_factor - v *= scale_factor - return x, y, u, v - - @staticmethod - def _latlon_uv_cubes(grid_cube): - # Increase the range of wind speeds used in the barbs test to test all - # barbs shapes - u_cube, v_cube = MixinVectorPlotCases._latlon_uv_cubes(grid_cube) - scale_factor = 30 - u_cube.data *= scale_factor - v_cube.data *= scale_factor - return u_cube, v_cube - - def plot_function_to_test(self): - return barbs - - -class TestQuiver(MixinVectorPlotCases, tests.GraphicsTest): - def setUp(self): - super().setUp() - - def plot_function_to_test(self): - return quiver - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_Datums.py b/lib/iris/tests/integration/test_Datums.py deleted file mode 100755 index 6953534f2d..0000000000 --- a/lib/iris/tests/integration/test_Datums.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :class:`iris.coord_systems` datum suppport.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs -import numpy as np - -from iris.coord_systems import GeogCS, LambertConformal - - -class TestDatumTransformation(tests.IrisTest): - def setUp(self): - self.x_points = np.array([-1.5]) - self.y_points = np.array([50.5]) - - self.start_crs = ccrs.OSGB(False) - - def test_transform_points_datum(self): - # Iris version - wgs84 = GeogCS.from_datum("WGS84") - iris_cs = LambertConformal( - central_lat=54, - central_lon=-4, - secant_latitudes=[52, 56], - ellipsoid=wgs84, - ) - iris_cs_as_cartopy = iris_cs.as_cartopy_crs() - - # Cartopy equivalent - cartopy_cs = ccrs.LambertConformal( - central_latitude=54, - central_longitude=-4, - standard_parallels=[52, 56], - globe=ccrs.Globe("WGS84"), - ) - - expected = cartopy_cs.transform_points( - self.start_crs, self.x_points, self.y_points - ) - - actual = iris_cs_as_cartopy.transform_points( - self.start_crs, self.x_points, self.y_points - ) - - self.assertArrayEqual(expected, actual) diff --git a/lib/iris/tests/integration/test_PartialDateTime.py b/lib/iris/tests/integration/test_PartialDateTime.py deleted file mode 100644 index 563af1035c..0000000000 --- a/lib/iris/tests/integration/test_PartialDateTime.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :class:`iris.time.PartialDateTime`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris -from iris.time import PartialDateTime - - -class Test(tests.IrisTest): - @tests.skip_data - def test_cftime_interface(self): - # The `netcdf4` Python module introduced new calendar classes by v1.2.7 - # This test is primarily of this interface, so the - # final test assertion is simple. - filename = tests.get_data_path(("PP", "structured", "small.pp")) - cube = iris.load_cube(filename) - pdt = PartialDateTime(year=1992, month=10, day=1, hour=2) - time_constraint = iris.Constraint(time=lambda cell: cell < pdt) - sub_cube = cube.extract(time_constraint) - self.assertEqual(sub_cube.coord("time").points.shape, (1,)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_climatology.py b/lib/iris/tests/integration/test_climatology.py deleted file mode 100644 index 54d43858fb..0000000000 --- a/lib/iris/tests/integration/test_climatology.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for loading and saving netcdf files.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from os.path import dirname -from os.path import join as path_join -from os.path import sep as os_sep -import shutil -import tempfile - -import iris -from iris.tests import stock -from iris.tests.stock.netcdf import ncgen_from_cdl - - -class TestClimatology(tests.IrisTest): - reference_cdl_path = os_sep.join( - [ - dirname(tests.__file__), - ( - "results/integration/climatology/TestClimatology/" - "reference_simpledata.cdl" - ), - ] - ) - - @classmethod - def _simple_cdl_string(cls): - with open(cls.reference_cdl_path, "r") as f: - cdl_content = f.read() - # Add the expected CDL first line since this is removed from the - # stored results file. - cdl_content = "netcdf {\n" + cdl_content - - return cdl_content - - @staticmethod - def _load_sanitised_cube(filepath): - cube = iris.load_cube(filepath) - # Remove attributes convention, if any. - cube.attributes.pop("Conventions", None) - # Remove any var-names. - for coord in cube.coords(): - coord.var_name = None - cube.var_name = None - return cube - - @classmethod - def setUpClass(cls): - # Create a temp directory for temp files. - cls.temp_dir = tempfile.mkdtemp() - cls.path_ref_cdl = path_join(cls.temp_dir, "standard.cdl") - cls.path_ref_nc = path_join(cls.temp_dir, "standard.nc") - # Create reference CDL and netcdf files (with ncgen). - ncgen_from_cdl( - cdl_str=cls._simple_cdl_string(), - cdl_path=cls.path_ref_cdl, - nc_path=cls.path_ref_nc, - ) - - cls.path_temp_nc = path_join(cls.temp_dir, "tmp.nc") - - # Create reference cube. - cls.cube_ref = stock.climatology_3d() - - @classmethod - def tearDownClass(cls): - # Destroy a temp directory for temp files. - shutil.rmtree(cls.temp_dir) - - ############################################################################### - # Round-trip tests - - def test_cube_to_cube(self): - # Save reference cube to file, load cube from same file, test against - # reference cube. - iris.save(self.cube_ref, self.path_temp_nc) - cube = self._load_sanitised_cube(self.path_temp_nc) - self.assertEqual(cube, self.cube_ref) - - def test_file_to_file(self): - # Load cube from reference file, save same cube to file, test against - # reference CDL. - cube = iris.load_cube(self.path_ref_nc) - iris.save(cube, self.path_temp_nc) - self.assertCDL( - self.path_temp_nc, - reference_filename=self.reference_cdl_path, - flags="", - ) - - # NOTE: - # The saving half of the round-trip tests is tested in the - # appropriate dedicated test class: - # unit.fileformats.netcdf.test_Saver.Test_write.test_with_climatology . - # The loading half has no equivalent dedicated location, so is tested - # here as test_load_from_file. - - def test_load_from_file(self): - # Create cube from file, test against reference cube. - cube = self._load_sanitised_cube(self.path_ref_nc) - self.assertEqual(cube, self.cube_ref) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_cube.py b/lib/iris/tests/integration/test_cube.py deleted file mode 100644 index 996362f594..0000000000 --- a/lib/iris/tests/integration/test_cube.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :class:`iris.cube.Cube`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -import iris -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.analysis import MEAN -from iris.cube import Cube - - -class Test_aggregated_by(tests.IrisTest): - @tests.skip_data - def test_agg_by_aux_coord(self): - problem_test_file = tests.get_data_path( - ("NetCDF", "testing", "small_theta_colpex.nc") - ) - cube = iris.load_cube(problem_test_file, "air_potential_temperature") - - # Test aggregating by aux coord, notably the `forecast_period` aux - # coord on `cube`, whose `_points` attribute is a lazy array. - # This test then ensures that aggregating using `points` instead is - # successful. - - # First confirm we've got a lazy array. - # NB. This checks the merge process in `load_cube()` hasn't - # triggered the load of the coordinate's data. - forecast_period_coord = cube.coord("forecast_period") - - self.assertTrue(is_lazy_data(forecast_period_coord.core_points())) - - # Now confirm we can aggregate along this coord. - res_cube = cube.aggregated_by("forecast_period", MEAN) - res_cell_methods = res_cube.cell_methods[0] - self.assertEqual(res_cell_methods.coord_names, ("forecast_period",)) - self.assertEqual(res_cell_methods.method, "mean") - - -class TestDataFillValue(tests.IrisTest): - def test_real(self): - data = np.ma.masked_array([1, 2, 3], [0, 1, 0], fill_value=10) - cube = Cube(data) - cube.data.fill_value = 20 - self.assertEqual(cube.data.fill_value, 20) - - def test_lazy(self): - data = np.ma.masked_array([1, 2, 3], [0, 1, 0], fill_value=10) - data = as_lazy_data(data) - cube = Cube(data) - cube.data.fill_value = 20 - self.assertEqual(cube.data.fill_value, 20) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_ff.py b/lib/iris/tests/integration/test_ff.py deleted file mode 100644 index 0b0ccf4c5c..0000000000 --- a/lib/iris/tests/integration/test_ff.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for loading LBC fieldsfiles.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -import iris - - -@tests.skip_data -class TestLBC(tests.IrisTest): - def setUp(self): - # Load multiple cubes from a test file. - file_path = tests.get_data_path(("FF", "lbc", "small_lbc")) - self.all_cubes = iris.load(file_path) - # Select the second cube for detailed checks (the first is orography). - self.test_cube = self.all_cubes[1] - - def test_various_cubes_shapes(self): - # Check a few aspects of the loaded cubes. - cubes = self.all_cubes - self.assertEqual(len(cubes), 10) - self.assertEqual(cubes[0].shape, (16, 16)) - self.assertEqual(cubes[1].shape, (2, 4, 16, 16)) - self.assertEqual(cubes[3].shape, (2, 5, 16, 16)) - - def test_cube_coords(self): - # Check coordinates of one cube. - cube = self.test_cube - self.assertEqual(len(cube.coords()), 8) - for name, shape in [ - ("forecast_reference_time", (1,)), - ("time", (2,)), - ("forecast_period", (2,)), - ("model_level_number", (4,)), - ("level_height", (1,)), - ("sigma", (1,)), - ("grid_latitude", (16,)), - ("grid_longitude", (16,)), - ]: - coords = cube.coords(name) - self.assertEqual( - len(coords), - 1, - "expected one {!r} coord, found {}".format(name, len(coords)), - ) - (coord,) = coords - self.assertEqual( - coord.shape, - shape, - "coord {!r} shape is {} instead of {!r}.".format( - name, coord.shape, shape - ), - ) - - def test_cube_data(self): - # Check just a few points of the data. - cube = self.test_cube - self.assertArrayAllClose( - cube.data[:, ::2, 6, 13], - np.array([[4.218922, 10.074577], [4.626897, 6.520156]]), - atol=1.0e-6, - ) - - def test_cube_mask(self): - # Check the data mask : should be just the centre 6x2 section. - cube = self.test_cube - mask = np.zeros((2, 4, 16, 16), dtype=bool) - mask[:, :, 7:9, 5:11] = True - self.assertArrayEqual(cube.data.mask, mask) - - -@tests.skip_data -class TestSkipField(tests.IrisTest): - def test_missing_lbrel(self): - infile = tests.get_data_path(("FF", "lbrel_missing")) - with mock.patch("warnings.warn") as warn_fn: - fields = iris.load(infile) - self.assertIn( - "Input field skipped as PPField creation failed : " - "error = 'Unsupported header release number: -32768'", - warn_fn.call_args[0][0], - ) - self.assertEqual(len(fields), 2) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_new_axis.py b/lib/iris/tests/integration/test_new_axis.py deleted file mode 100644 index 876eccbb63..0000000000 --- a/lib/iris/tests/integration/test_new_axis.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :func:`iris.util.new_axis`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris -from iris.util import new_axis - - -class Test(tests.IrisTest): - @tests.skip_data - def test_lazy_data(self): - filename = tests.get_data_path(("PP", "globClim1", "theta.pp")) - cube = iris.load_cube(filename) - new_cube = new_axis(cube, "time") - self.assertTrue(cube.has_lazy_data()) - self.assertTrue(new_cube.has_lazy_data()) - self.assertEqual(new_cube.shape, (1,) + cube.shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_pickle.py b/lib/iris/tests/integration/test_pickle.py deleted file mode 100644 index fa5ddbd73e..0000000000 --- a/lib/iris/tests/integration/test_pickle.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for pickling things.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import pickle - -import iris - - -class Common: - def pickle_cube(self, protocol): - # Ensure that data proxies are pickleable. - cube = iris.load(self.path)[0] - with self.temp_filename(".pkl") as filename: - with open(filename, "wb") as f: - pickle.dump(cube, f, protocol) - with open(filename, "rb") as f: - ncube = pickle.load(f) - self.assertEqual(ncube, cube) - - def test_protocol_0(self): - self.pickle_cube(0) - - def test_protocol_1(self): - self.pickle_cube(1) - - def test_protocol_2(self): - self.pickle_cube(2) - - -@tests.skip_data -class test_netcdf(Common, tests.IrisTest): - def setUp(self): - self.path = tests.get_data_path( - ("NetCDF", "global", "xyt", "SMALL_hires_wind_u_for_ipcc4.nc") - ) - - -@tests.skip_data -class test_pp(Common, tests.IrisTest): - def setUp(self): - self.path = tests.get_data_path(("PP", "aPPglob1", "global.pp")) - - -@tests.skip_data -class test_ff(Common, tests.IrisTest): - def setUp(self): - self.path = tests.get_data_path(("FF", "n48_multi_field")) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_pp.py b/lib/iris/tests/integration/test_pp.py deleted file mode 100644 index e654694aa7..0000000000 --- a/lib/iris/tests/integration/test_pp.py +++ /dev/null @@ -1,808 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for loading and saving PP files.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import os -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris.aux_factory import HybridHeightFactory, HybridPressureFactory -from iris.coords import AuxCoord, CellMethod, DimCoord -from iris.cube import Cube -from iris.exceptions import IgnoreCubeException -import iris.fileformats.pp -from iris.fileformats.pp import load_pairs_from_fields -import iris.fileformats.pp_load_rules -from iris.fileformats.pp_save_rules import verify -import iris.util - - -class TestVertical(tests.IrisTest): - def _test_coord(self, cube, point, bounds=None, **kwargs): - coords = cube.coords(**kwargs) - self.assertEqual( - len(coords), - 1, - "failed to find exactly one coord" " using: {}".format(kwargs), - ) - self.assertEqual(coords[0].points, point) - if bounds is not None: - self.assertArrayEqual(coords[0].bounds, [bounds]) - - @staticmethod - def _mock_field(**kwargs): - mock_data = np.zeros(1) - mock_core_data = mock.MagicMock(return_value=mock_data) - field = mock.MagicMock( - lbuser=[0] * 7, - lbrsvd=[0] * 4, - brsvd=[0] * 4, - brlev=0, - t1=mock.MagicMock(year=1990, month=1, day=3), - t2=mock.MagicMock(year=1990, month=1, day=3), - core_data=mock_core_data, - realised_dtype=mock_data.dtype, - ) - field.configure_mock(**kwargs) - return field - - def test_soil_level_round_trip(self): - # Use pp.load_cubes() to convert a fake PPField into a Cube. - # NB. Use MagicMock so that SplittableInt header items, such as - # LBCODE, support len(). - soil_level = 1234 - field = self._mock_field( - lbvc=6, lblev=soil_level, stash=iris.fileformats.pp.STASH(1, 0, 9) - ) - load = mock.Mock(return_value=iter([field])) - with mock.patch("iris.fileformats.pp.load", new=load) as load: - cube = next(iris.fileformats.pp.load_cubes("DUMMY")) - - self.assertIn("soil", cube.standard_name) - self._test_coord(cube, soil_level, long_name="soil_model_level_number") - - # Now use the save rules to convert the Cube back into a PPField. - field = iris.fileformats.pp.PPField3() - field.lbfc = 0 - field.lbvc = 0 - field.brsvd = [None] * 4 - field.brlev = None - field = verify(cube, field) - - # Check the vertical coordinate is as originally specified. - self.assertEqual(field.lbvc, 6) - self.assertEqual(field.lblev, soil_level) - self.assertEqual(field.blev, soil_level) - self.assertEqual(field.brsvd[0], 0) - self.assertEqual(field.brlev, 0) - - def test_soil_depth_round_trip(self): - # Use pp.load_cubes() to convert a fake PPField into a Cube. - # NB. Use MagicMock so that SplittableInt header items, such as - # LBCODE, support len(). - lower, point, upper = 1.2, 3.4, 5.6 - brsvd = [lower, 0, 0, 0] - field = self._mock_field( - lbvc=6, - blev=point, - brsvd=brsvd, - brlev=upper, - stash=iris.fileformats.pp.STASH(1, 0, 9), - ) - load = mock.Mock(return_value=iter([field])) - with mock.patch("iris.fileformats.pp.load", new=load) as load: - cube = next(iris.fileformats.pp.load_cubes("DUMMY")) - - self.assertIn("soil", cube.standard_name) - self._test_coord( - cube, point, bounds=[lower, upper], standard_name="depth" - ) - - # Now use the save rules to convert the Cube back into a PPField. - field = iris.fileformats.pp.PPField3() - field.lbfc = 0 - field.lbvc = 0 - field.brlev = None - field.brsvd = [None] * 4 - field = verify(cube, field) - - # Check the vertical coordinate is as originally specified. - self.assertEqual(field.lbvc, 6) - self.assertEqual(field.blev, point) - self.assertEqual(field.brsvd[0], lower) - self.assertEqual(field.brlev, upper) - - def test_potential_temperature_level_round_trip(self): - # Check save+load for data on 'potential temperature' levels. - - # Use pp.load_cubes() to convert a fake PPField into a Cube. - # NB. Use MagicMock so that SplittableInt header items, such as - # LBCODE, support len(). - potm_value = 22.5 - field = self._mock_field(lbvc=19, blev=potm_value) - load = mock.Mock(return_value=iter([field])) - with mock.patch("iris.fileformats.pp.load", new=load): - cube = next(iris.fileformats.pp.load_cubes("DUMMY")) - - self._test_coord( - cube, potm_value, standard_name="air_potential_temperature" - ) - - # Now use the save rules to convert the Cube back into a PPField. - field = iris.fileformats.pp.PPField3() - field.lbfc = 0 - field.lbvc = 0 - field = verify(cube, field) - - # Check the vertical coordinate is as originally specified. - self.assertEqual(field.lbvc, 19) - self.assertEqual(field.blev, potm_value) - - @staticmethod - def _field_with_data(scale=1, **kwargs): - x, y = 40, 30 - mock_data = np.arange(1200).reshape(y, x) * scale - mock_core_data = mock.MagicMock(return_value=mock_data) - field = mock.MagicMock( - core_data=mock_core_data, - realised_dtype=mock_data.dtype, - lbcode=[1], - lbnpt=x, - lbrow=y, - bzx=350, - bdx=1.5, - bzy=40, - bdy=1.5, - lbuser=[0] * 7, - lbrsvd=[0] * 4, - t1=mock.MagicMock(year=1990, month=1, day=3), - t2=mock.MagicMock(year=1990, month=1, day=3), - ) - - field._x_coord_name = lambda: "longitude" - field._y_coord_name = lambda: "latitude" - field.coord_system = lambda: None - field.configure_mock(**kwargs) - return field - - def test_hybrid_pressure_round_trip(self): - # Use pp.load_cubes() to convert fake PPFields into Cubes. - # NB. Use MagicMock so that SplittableInt header items, such as - # LBCODE, support len(). - - # Make a fake reference surface field. - pressure_field = self._field_with_data( - 10, - stash=iris.fileformats.pp.STASH(1, 0, 409), - lbuser=[0, 0, 0, 409, 0, 0, 0], - ) - - # Make a fake data field which needs the reference surface. - model_level = 5678 - sigma_lower, sigma, sigma_upper = 0.85, 0.9, 0.95 - delta_lower, delta, delta_upper = 0.05, 0.1, 0.15 - data_field = self._field_with_data( - lbvc=9, - lblev=model_level, - bhlev=delta, - bhrlev=delta_lower, - blev=sigma, - brlev=sigma_lower, - brsvd=[sigma_upper, delta_upper], - ) - - # Convert both fields to cubes. - load = mock.Mock(return_value=iter([pressure_field, data_field])) - with mock.patch("iris.fileformats.pp.load", new=load) as load: - pressure_cube, data_cube = iris.fileformats.pp.load_cubes("DUMMY") - - # Check the reference surface cube looks OK. - self.assertEqual(pressure_cube.standard_name, "surface_air_pressure") - self.assertEqual(pressure_cube.units, "Pa") - - # Check the data cube is set up to use hybrid-pressure. - self._test_coord( - data_cube, model_level, standard_name="model_level_number" - ) - self._test_coord( - data_cube, - delta, - [delta_lower, delta_upper], - long_name="level_pressure", - ) - self._test_coord( - data_cube, sigma, [sigma_lower, sigma_upper], long_name="sigma" - ) - aux_factories = data_cube.aux_factories - self.assertEqual(len(aux_factories), 1) - surface_coord = aux_factories[0].dependencies["surface_air_pressure"] - self.assertArrayEqual( - surface_coord.points, np.arange(12000, step=10).reshape(30, 40) - ) - - # Now use the save rules to convert the Cubes back into PPFields. - pressure_field = iris.fileformats.pp.PPField3() - pressure_field.lbfc = 0 - pressure_field.lbvc = 0 - pressure_field.brsvd = [None, None] - pressure_field.lbuser = [None] * 7 - pressure_field = verify(pressure_cube, pressure_field) - - data_field = iris.fileformats.pp.PPField3() - data_field.lbfc = 0 - data_field.lbvc = 0 - data_field.brsvd = [None, None] - data_field.lbuser = [None] * 7 - data_field = verify(data_cube, data_field) - - # The reference surface field should have STASH=409 - self.assertArrayEqual( - pressure_field.lbuser, [None, None, None, 409, None, None, 1] - ) - - # Check the data field has the vertical coordinate as originally - # specified. - self.assertEqual(data_field.lbvc, 9) - self.assertEqual(data_field.lblev, model_level) - self.assertEqual(data_field.bhlev, delta) - self.assertEqual(data_field.bhrlev, delta_lower) - self.assertEqual(data_field.blev, sigma) - self.assertEqual(data_field.brlev, sigma_lower) - self.assertEqual(data_field.brsvd, [sigma_upper, delta_upper]) - - def test_hybrid_pressure_with_duplicate_references(self): - # Make a fake reference surface field. - pressure_field = self._field_with_data( - 10, - stash=iris.fileformats.pp.STASH(1, 0, 409), - lbuser=[0, 0, 0, 409, 0, 0, 0], - ) - - # Make a fake data field which needs the reference surface. - model_level = 5678 - sigma_lower, sigma, sigma_upper = 0.85, 0.9, 0.95 - delta_lower, delta, delta_upper = 0.05, 0.1, 0.15 - data_field = self._field_with_data( - lbvc=9, - lblev=model_level, - bhlev=delta, - bhrlev=delta_lower, - blev=sigma, - brlev=sigma_lower, - brsvd=[sigma_upper, delta_upper], - ) - - # Convert both fields to cubes. - load = mock.Mock( - return_value=iter([data_field, pressure_field, pressure_field]) - ) - msg = "Multiple reference cubes for surface_air_pressure" - with mock.patch( - "iris.fileformats.pp.load", new=load - ) as load, mock.patch("warnings.warn") as warn: - _, _, _ = iris.fileformats.pp.load_cubes("DUMMY") - warn.assert_called_with(msg) - - def test_hybrid_height_with_non_standard_coords(self): - # Check the save rules are using the AuxFactory to find the - # hybrid height coordinates and not relying on their names. - ny, nx = 30, 40 - sigma_lower, sigma, sigma_upper = 0.75, 0.8, 0.75 - delta_lower, delta, delta_upper = 150, 200, 250 - - cube = Cube(np.zeros((ny, nx)), "air_temperature") - level_coord = AuxCoord(0, "model_level_number", units="1") - cube.add_aux_coord(level_coord) - delta_coord = AuxCoord( - delta, - bounds=[[delta_lower, delta_upper]], - long_name="moog", - units="m", - ) - sigma_coord = AuxCoord( - sigma, - bounds=[[sigma_lower, sigma_upper]], - long_name="mavis", - units="1", - ) - surface_altitude_coord = AuxCoord( - np.zeros((ny, nx)), "surface_altitude", units="m" - ) - cube.add_aux_coord(delta_coord) - cube.add_aux_coord(sigma_coord) - cube.add_aux_coord(surface_altitude_coord, (0, 1)) - cube.add_aux_factory( - HybridHeightFactory( - delta_coord, sigma_coord, surface_altitude_coord - ) - ) - - field = iris.fileformats.pp.PPField3() - field.lbfc = 0 - field.lbvc = 0 - field.brsvd = [None, None] - field.lbuser = [None] * 7 - field = verify(cube, field) - - self.assertEqual(field.blev, delta) - self.assertEqual(field.brlev, delta_lower) - self.assertEqual(field.bhlev, sigma) - self.assertEqual(field.bhrlev, sigma_lower) - self.assertEqual(field.brsvd, [delta_upper, sigma_upper]) - - def test_hybrid_pressure_with_non_standard_coords(self): - # Check the save rules are using the AuxFactory to find the - # hybrid pressure coordinates and not relying on their names. - ny, nx = 30, 40 - sigma_lower, sigma, sigma_upper = 0.75, 0.8, 0.75 - delta_lower, delta, delta_upper = 0.15, 0.2, 0.25 - - cube = Cube(np.zeros((ny, nx)), "air_temperature") - level_coord = AuxCoord(0, "model_level_number", units="1") - cube.add_aux_coord(level_coord) - delta_coord = AuxCoord( - delta, - bounds=[[delta_lower, delta_upper]], - long_name="moog", - units="Pa", - ) - sigma_coord = AuxCoord( - sigma, - bounds=[[sigma_lower, sigma_upper]], - long_name="mavis", - units="1", - ) - surface_air_pressure_coord = AuxCoord( - np.zeros((ny, nx)), "surface_air_pressure", units="Pa" - ) - cube.add_aux_coord(delta_coord) - cube.add_aux_coord(sigma_coord) - cube.add_aux_coord(surface_air_pressure_coord, (0, 1)) - cube.add_aux_factory( - HybridPressureFactory( - delta_coord, sigma_coord, surface_air_pressure_coord - ) - ) - - field = iris.fileformats.pp.PPField3() - field.lbfc = 0 - field.lbvc = 0 - field.brsvd = [None, None] - field.lbuser = [None] * 7 - field = verify(cube, field) - - self.assertEqual(field.bhlev, delta) - self.assertEqual(field.bhrlev, delta_lower) - self.assertEqual(field.blev, sigma) - self.assertEqual(field.brlev, sigma_lower) - self.assertEqual(field.brsvd, [sigma_upper, delta_upper]) - - def test_hybrid_height_round_trip_no_reference(self): - # Use pp.load_cubes() to convert fake PPFields into Cubes. - # NB. Use MagicMock so that SplittableInt header items, such as - # LBCODE, support len(). - # Make a fake data field which needs the reference surface. - model_level = 5678 - sigma_lower, sigma, sigma_upper = 0.85, 0.9, 0.95 - delta_lower, delta, delta_upper = 0.05, 0.1, 0.15 - data_field = self._field_with_data( - lbvc=65, - lblev=model_level, - bhlev=sigma, - bhrlev=sigma_lower, - blev=delta, - brlev=delta_lower, - brsvd=[delta_upper, sigma_upper], - ) - - # Convert field to a cube. - load = mock.Mock(return_value=iter([data_field])) - with mock.patch( - "iris.fileformats.pp.load", new=load - ) as load, mock.patch("warnings.warn") as warn: - (data_cube,) = iris.fileformats.pp.load_cubes("DUMMY") - - msg = ( - "Unable to create instance of HybridHeightFactory. " - "The source data contains no field(s) for 'orography'." - ) - warn.assert_called_with(msg) - - # Check the data cube is set up to use hybrid height. - self._test_coord( - data_cube, model_level, standard_name="model_level_number" - ) - self._test_coord( - data_cube, - delta, - [delta_lower, delta_upper], - long_name="level_height", - ) - self._test_coord( - data_cube, sigma, [sigma_lower, sigma_upper], long_name="sigma" - ) - # Check that no aux factory is created (due to missing - # reference surface). - aux_factories = data_cube.aux_factories - self.assertEqual(len(aux_factories), 0) - - # Now use the save rules to convert the Cube back into a PPField. - data_field = iris.fileformats.pp.PPField3() - data_field.lbfc = 0 - data_field.lbvc = 0 - data_field.brsvd = [None, None] - data_field.lbuser = [None] * 7 - data_field = verify(data_cube, data_field) - - # Check the data field has the vertical coordinate as originally - # specified. - self.assertEqual(data_field.lbvc, 65) - self.assertEqual(data_field.lblev, model_level) - self.assertEqual(data_field.bhlev, sigma) - self.assertEqual(data_field.bhrlev, sigma_lower) - self.assertEqual(data_field.blev, delta) - self.assertEqual(data_field.brlev, delta_lower) - self.assertEqual(data_field.brsvd, [delta_upper, sigma_upper]) - - -class TestSaveLBFT(tests.IrisTest): - def setUp(self): - delta_start = 24 - delta_mid = 36 - self.delta_end = 369 * 24 - ref_offset = 10 * 24 - self.args = (delta_start, delta_mid, self.delta_end, ref_offset) - - def create_cube(self, fp_min, fp_mid, fp_max, ref_offset, season=None): - cube = Cube(np.zeros((3, 4))) - cube.add_aux_coord( - AuxCoord( - standard_name="forecast_period", - units="hours", - points=fp_mid, - bounds=[fp_min, fp_max], - ) - ) - cube.add_aux_coord( - AuxCoord( - standard_name="time", - units="hours since epoch", - points=ref_offset + fp_mid, - bounds=[ref_offset + fp_min, ref_offset + fp_max], - ) - ) - if season: - cube.add_aux_coord( - AuxCoord(long_name="clim_season", points=season) - ) - cube.add_cell_method(CellMethod("DUMMY", "clim_season")) - return cube - - def convert_cube_to_field(self, cube): - # Use the save rules to convert the Cube back into a PPField. - field = iris.fileformats.pp.PPField3() - field.lbfc = 0 - field.lbvc = 0 - field.lbtim = 0 - field = verify(cube, field) - return field - - def test_time_mean_from_forecast_period(self): - cube = self.create_cube(24, 36, 48, 72) - field = self.convert_cube_to_field(cube) - self.assertEqual(field.lbft, 48) - - def test_time_mean_from_forecast_reference_time(self): - cube = Cube(np.zeros((3, 4))) - cube.add_aux_coord( - AuxCoord( - standard_name="forecast_reference_time", - units="hours since epoch", - points=72, - ) - ) - cube.add_aux_coord( - AuxCoord( - standard_name="time", - units="hours since epoch", - points=72 + 36, - bounds=[72 + 24, 72 + 48], - ) - ) - field = self.convert_cube_to_field(cube) - self.assertEqual(field.lbft, 48) - - def test_climatological_mean_single_year(self): - cube = Cube(np.zeros((3, 4))) - cube.add_aux_coord( - AuxCoord( - standard_name="forecast_period", - units="hours", - points=36, - bounds=[24, 4 * 24], - ) - ) - cube.add_aux_coord( - AuxCoord( - standard_name="time", - units="hours since epoch", - points=240 + 36, - bounds=[240 + 24, 240 + 4 * 24], - ) - ) - cube.add_aux_coord(AuxCoord(long_name="clim_season", points="DUMMY")) - cube.add_cell_method(CellMethod("DUMMY", "clim_season")) - field = self.convert_cube_to_field(cube) - self.assertEqual(field.lbft, 4 * 24) - - def test_climatological_mean_multi_year_djf(self): - cube = self.create_cube(*self.args, season="djf") - field = self.convert_cube_to_field(cube) - self.assertEqual(field.lbft, self.delta_end) - - def test_climatological_mean_multi_year_mam(self): - cube = self.create_cube(*self.args, season="mam") - field = self.convert_cube_to_field(cube) - self.assertEqual(field.lbft, self.delta_end) - - def test_climatological_mean_multi_year_jja(self): - cube = self.create_cube(*self.args, season="jja") - field = self.convert_cube_to_field(cube) - self.assertEqual(field.lbft, self.delta_end) - - def test_climatological_mean_multi_year_son(self): - cube = self.create_cube(*self.args, season="son") - field = self.convert_cube_to_field(cube) - self.assertEqual(field.lbft, self.delta_end) - - -class TestCoordinateForms(tests.IrisTest): - def _common(self, x_coord): - nx = len(x_coord.points) - ny = 2 - data = np.zeros((ny, nx), dtype=np.float32) - test_cube = iris.cube.Cube(data) - y0 = np.float32(20.5) - dy = np.float32(3.72) - y_coord = iris.coords.DimCoord.from_regular( - zeroth=y0, - step=dy, - count=ny, - standard_name="latitude", - units="degrees_north", - ) - test_cube.add_dim_coord(x_coord, 1) - test_cube.add_dim_coord(y_coord, 0) - # Write to a temporary PP file and read it back as a PPField - with self.temp_filename(".pp") as pp_filepath: - iris.save(test_cube, pp_filepath) - pp_loader = iris.fileformats.pp.load(pp_filepath) - pp_field = next(pp_loader) - return pp_field - - def test_save_awkward_case_is_regular(self): - # Check that specific "awkward" values still save in a regular form. - nx = 3 - x0 = np.float32(355.626) - dx = np.float32(0.0135) - x_coord = iris.coords.DimCoord.from_regular( - zeroth=x0, - step=dx, - count=nx, - standard_name="longitude", - units="degrees_east", - ) - pp_field = self._common(x_coord) - # Check that the result has the regular coordinates as expected. - self.assertEqual(pp_field.bzx, x0) - self.assertEqual(pp_field.bdx, dx) - self.assertEqual(pp_field.lbnpt, nx) - - def test_save_irregular(self): - # Check that a non-regular coordinate saves as expected. - nx = 3 - x_values = [0.0, 1.1, 2.0] - x_coord = iris.coords.DimCoord( - x_values, standard_name="longitude", units="degrees_east" - ) - pp_field = self._common(x_coord) - # Check that the result has the regular/irregular Y and X as expected. - self.assertEqual(pp_field.bdx, 0.0) - self.assertArrayAllClose(pp_field.x, x_values) - self.assertEqual(pp_field.lbnpt, nx) - - -@tests.skip_data -class TestLoadLittleendian(tests.IrisTest): - def test_load_sample(self): - file_path = tests.get_data_path( - ("PP", "little_endian", "qrparm.orog.pp") - ) - # Ensure it just loads. - cube = iris.load_cube(file_path, "surface_altitude") - self.assertEqual(cube.shape, (110, 160)) - - # Check for sensible floating point numbers. - def check_minmax(array, expect_min, expect_max): - found = np.array([np.min(array), np.max(array)]) - expected = np.array([expect_min, expect_max]) - self.assertArrayAlmostEqual(found, expected, decimal=2) - - lons = cube.coord("grid_longitude").points - lats = cube.coord("grid_latitude").points - data = cube.data - check_minmax(lons, 342.0, 376.98) - check_minmax(lats, -10.48, 13.5) - check_minmax(data, -30.48, 6029.1) - - -@tests.skip_data -class TestAsCubes(tests.IrisTest): - def setUp(self): - dpath = tests.get_data_path( - ["PP", "meanMaxMin", "200806081200__qwpb.T24.pp"] - ) - self.ppfs = iris.fileformats.pp.load(dpath) - - def test_pseudo_level_filter(self): - chosen_ppfs = [] - for ppf in self.ppfs: - if ppf.lbuser[4] == 3: - chosen_ppfs.append(ppf) - cubes_fields = list(load_pairs_from_fields(chosen_ppfs)) - self.assertEqual(len(cubes_fields), 8) - - def test_pseudo_level_filter_none(self): - chosen_ppfs = [] - for ppf in self.ppfs: - if ppf.lbuser[4] == 30: - chosen_ppfs.append(ppf) - cubes = list(load_pairs_from_fields(chosen_ppfs)) - self.assertEqual(len(cubes), 0) - - def test_as_pairs(self): - cube_ppf_pairs = load_pairs_from_fields(self.ppfs) - cubes = [] - for cube, ppf in cube_ppf_pairs: - if ppf.lbuser[4] == 3: - cube.attributes["pseudo level"] = ppf.lbuser[4] - cubes.append(cube) - for cube in cubes: - self.assertEqual(cube.attributes["pseudo level"], 3) - - -class TestSaveLBPROC(tests.IrisTest): - def create_cube(self, longitude_coord="longitude"): - cube = Cube(np.zeros((2, 3, 4))) - tunit = Unit("days since epoch", calendar="standard") - tcoord = DimCoord(np.arange(2), standard_name="time", units=tunit) - xcoord = DimCoord( - np.arange(3), standard_name=longitude_coord, units="degrees" - ) - ycoord = DimCoord(points=np.arange(4)) - cube.add_dim_coord(tcoord, 0) - cube.add_dim_coord(xcoord, 1) - cube.add_dim_coord(ycoord, 2) - return cube - - def convert_cube_to_field(self, cube): - field = iris.fileformats.pp.PPField3() - field.lbvc = 0 - return verify(cube, field) - - def test_time_mean_only(self): - cube = self.create_cube() - cube.add_cell_method(CellMethod(method="mean", coords="time")) - field = self.convert_cube_to_field(cube) - self.assertEqual(int(field.lbproc), 128) - - def test_longitudinal_mean_only(self): - cube = self.create_cube() - cube.add_cell_method(CellMethod(method="mean", coords="longitude")) - field = self.convert_cube_to_field(cube) - self.assertEqual(int(field.lbproc), 64) - - def test_grid_longitudinal_mean_only(self): - cube = self.create_cube(longitude_coord="grid_longitude") - cube.add_cell_method( - CellMethod(method="mean", coords="grid_longitude") - ) - field = self.convert_cube_to_field(cube) - self.assertEqual(int(field.lbproc), 64) - - def test_time_mean_and_zonal_mean(self): - cube = self.create_cube() - cube.add_cell_method(CellMethod(method="mean", coords="time")) - cube.add_cell_method(CellMethod(method="mean", coords="longitude")) - field = self.convert_cube_to_field(cube) - self.assertEqual(int(field.lbproc), 192) - - -@tests.skip_data -class TestCallbackLoad(tests.IrisTest): - def setUp(self): - self.pass_name = "air_potential_temperature" - - def callback_wrapper(self): - # Wrap the `iris.exceptions.IgnoreCubeException`-calling callback. - def callback_ignore_cube_exception(cube, field, filename): - if cube.name() != self.pass_name: - raise IgnoreCubeException - - return callback_ignore_cube_exception - - def test_ignore_cube_callback(self): - test_dataset = tests.get_data_path( - ["PP", "globClim1", "dec_subset.pp"] - ) - exception_callback = self.callback_wrapper() - result_cubes = iris.load(test_dataset, callback=exception_callback) - n_result_cubes = len(result_cubes) - # We ignore all but one cube (the `air_potential_temperature` cube). - self.assertEqual(n_result_cubes, 1) - self.assertEqual(result_cubes[0].name(), self.pass_name) - - -@tests.skip_data -class TestZonalMeanBounds(tests.IrisTest): - def test_mulitple_longitude(self): - # test that bounds are set for a zonal mean file with many longitude - # values - orig_file = tests.get_data_path(("PP", "aPPglob1", "global.pp")) - - f = next(iris.fileformats.pp.load(orig_file)) - f.lbproc = 192 # time and zonal mean - - # Write out pp file - temp_filename = iris.util.create_temp_filename(".pp") - with open(temp_filename, "wb") as temp_fh: - f.save(temp_fh) - - # Load pp file - cube = iris.load_cube(temp_filename) - - self.assertTrue(cube.coord("longitude").has_bounds()) - - os.remove(temp_filename) - - def test_singular_longitude(self): - # test that bounds are set for a zonal mean file with a single - # longitude value - - pp_file = tests.get_data_path(("PP", "zonal_mean", "zonal_mean.pp")) - - # Load pp file - cube = iris.load_cube(pp_file) - - self.assertTrue(cube.coord("longitude").has_bounds()) - - -@tests.skip_data -class TestLoadPartialMask(tests.IrisTest): - def test_data(self): - # Ensure that fields merge correctly where one has a mask and one - # doesn't. - filename = tests.get_data_path(["PP", "simple_pp", "partial_mask.pp"]) - - expected_data = np.ma.masked_array( - [[[0, 1], [11, 12]], [[99, 100], [-1, -1]]], - [[[0, 0], [0, 0]], [[0, 0], [1, 1]]], - dtype=np.int32, - ) - cube = iris.load_cube(filename) - - self.assertEqual(expected_data.dtype, cube.data.dtype) - self.assertMaskedArrayEqual(expected_data, cube.data, strict=False) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_pp_constrained_load_cubes.py b/lib/iris/tests/integration/test_pp_constrained_load_cubes.py deleted file mode 100644 index 7ddf39b2ff..0000000000 --- a/lib/iris/tests/integration/test_pp_constrained_load_cubes.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :func:`iris.fileformats.rules.load_cubes`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris -from iris.fileformats import pp -from iris.fileformats.pp_load_rules import convert -from iris.fileformats.rules import load_cubes - - -class Test(tests.IrisTest): - @tests.skip_data - def test_pp_with_stash_constraint(self): - filenames = [tests.get_data_path(("PP", "globClim1", "dec_subset.pp"))] - stcon = iris.AttributeConstraint(STASH="m01s00i004") - pp_constraints = pp._convert_constraints(stcon) - pp_loader = iris.fileformats.rules.Loader(pp.load, {}, convert) - cubes = list(load_cubes(filenames, None, pp_loader, pp_constraints)) - self.assertEqual(len(cubes), 38) - - @tests.skip_data - def test_pp_with_stash_constraints(self): - filenames = [tests.get_data_path(("PP", "globClim1", "dec_subset.pp"))] - stcon1 = iris.AttributeConstraint(STASH="m01s00i004") - stcon2 = iris.AttributeConstraint(STASH="m01s00i010") - pp_constraints = pp._convert_constraints([stcon1, stcon2]) - pp_loader = iris.fileformats.rules.Loader(pp.load, {}, convert) - cubes = list(load_cubes(filenames, None, pp_loader, pp_constraints)) - self.assertEqual(len(cubes), 76) - - @tests.skip_data - def test_pp_no_constraint(self): - filenames = [tests.get_data_path(("PP", "globClim1", "dec_subset.pp"))] - pp_constraints = pp._convert_constraints(None) - pp_loader = iris.fileformats.rules.Loader(pp.load, {}, convert) - cubes = list(load_cubes(filenames, None, pp_loader, pp_constraints)) - self.assertEqual(len(cubes), 152) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_regrid_equivalence.py b/lib/iris/tests/integration/test_regrid_equivalence.py deleted file mode 100644 index 09b47072e0..0000000000 --- a/lib/iris/tests/integration/test_regrid_equivalence.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Tests to check the validity of replacing -"iris.analysis._interpolate.regrid`('nearest')" with -"iris.cube.Cube.regrid(scheme=iris.analysis.Nearest())". - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.analysis import Nearest -from iris.coords import DimCoord -from iris.cube import Cube - - -def grid_cube(xx, yy, data=None): - nx, ny = len(xx), len(yy) - if data is not None: - data = np.array(data).reshape((ny, nx)) - else: - data = np.zeros((ny, nx)) - cube = Cube(data) - y_coord = DimCoord(yy, standard_name="latitude", units="degrees") - x_coord = DimCoord(xx, standard_name="longitude", units="degrees") - cube.add_dim_coord(y_coord, 0) - cube.add_dim_coord(x_coord, 1) - return cube - - -ENABLE_DEBUG_OUTPUT = False - - -def _debug_data(cube, test_id): - if ENABLE_DEBUG_OUTPUT: - print - data = cube.data - print("CUBE: {}".format(test_id)) - print(" x={!r}".format(cube.coord("longitude").points)) - print(" y={!r}".format(cube.coord("latitude").points)) - print("data[{}]:".format(type(data))) - print(repr(data)) - - -class MixinCheckingCode: - def test_basic(self): - src_x = [30.0, 40.0, 50.0] - dst_x = [32.0, 42.0] - src_y = [-10.0, 0.0, 10.0] - dst_y = [-8.0, 2.0] - data = [[3.0, 4.0, 5.0], [23.0, 24.0, 25.0], [43.0, 44.0, 45.0]] - expected_result = [[3.0, 4.0], [23.0, 24.0]] - src_cube = grid_cube(src_x, src_y, data) - _debug_data(src_cube, "basic SOURCE") - dst_cube = grid_cube(dst_x, dst_y) - result_cube = self.regrid(src_cube, dst_cube) - _debug_data(result_cube, "basic RESULT") - self.assertArrayAllClose(result_cube.data, expected_result) - - def test_src_extrapolation(self): - src_x = [30.0, 40.0, 50.0] - dst_x = [0.0, 29.0, 39.0] - src_y = [-10.0, 0.0, 10.0] - dst_y = [-50.0, -9.0, -1.0] - data = [[3.0, 4.0, 5.0], [23.0, 24.0, 25.0], [43.0, 44.0, 45.0]] - expected_result = [ - [3.0, 3.0, 4.0], - [3.0, 3.0, 4.0], - [23.0, 23.0, 24.0], - ] - src_cube = grid_cube(src_x, src_y, data) - _debug_data(src_cube, "extrapolate SOURCE") - dst_cube = grid_cube(dst_x, dst_y) - result_cube = self.regrid(src_cube, dst_cube) - _debug_data(result_cube, "extrapolate RESULT") - self.assertArrayAllClose(result_cube.data, expected_result) - - def test_exact_matching_points(self): - src_x = [10.0, 20.0, 30.0] - src_y = [10.0, 20.0, 30.0] - dst_x = [14.9, 15.1, 20.0, 24.9, 25.1] - dst_y = [14.9, 15.1, 20.0, 24.9, 25.1] - data = [[3.0, 4.0, 5.0], [23.0, 24.0, 25.0], [43.0, 44.0, 45.0]] - expected_result = [ - [3.0, 4.0, 4.0, 4.0, 5.0], - [23.0, 24.0, 24.0, 24.0, 25.0], - [23.0, 24.0, 24.0, 24.0, 25.0], - [23.0, 24.0, 24.0, 24.0, 25.0], - [43.0, 44.0, 44.0, 44.0, 45.0], - ] - src_cube = grid_cube(src_x, src_y, data) - _debug_data(src_cube, "matching SOURCE") - dst_cube = grid_cube(dst_x, dst_y) - result_cube = self.regrid(src_cube, dst_cube) - _debug_data(result_cube, "matching RESULt") - self.assertArrayAllClose(result_cube.data, expected_result) - - def test_source_mask(self): - src_x = [40.0, 50.0, 60.0] - src_y = [40.0, 50.0, 60.0] - dst_x = [44.99, 45.01, 48.0, 50.0, 52.0, 54.99, 55.01] - dst_y = [44.99, 45.01, 48.0, 50.0, 52.0, 54.99, 55.01] - data = np.ma.masked_equal( - [[3.0, 4.0, 5.0], [23.0, 999, 25.0], [43.0, 44.0, 45.0]], 999 - ) - expected_result = np.ma.masked_equal( - [ - [3.0, 4.0, 4.0, 4.0, 4.0, 4.0, 5.0], - [23.0, 999, 999, 999, 999, 999, 25.0], - [23.0, 999, 999, 999, 999, 999, 25.0], - [23.0, 999, 999, 999, 999, 999, 25.0], - [23.0, 999, 999, 999, 999, 999, 25.0], - [23.0, 999, 999, 999, 999, 999, 25.0], - [43.0, 44.0, 44.0, 44.0, 44.0, 44.0, 45.0], - ], - 999, - ) - src_cube = grid_cube(src_x, src_y, data) - src_cube.data = np.ma.masked_array(src_cube.data) - src_cube.data[1, 1] = np.ma.masked - _debug_data(src_cube, "masked SOURCE") - dst_cube = grid_cube(dst_x, dst_y) - result_cube = self.regrid( - src_cube, dst_cube, translate_nans_to_mask=True - ) - _debug_data(result_cube, "masked RESULT") - self.assertMaskedArrayEqual(result_cube.data, expected_result) - - def test_wrapping_non_circular(self): - src_x = [-10.0, 0.0, 10.0] - dst_x = [-360.0, -170.0, -1.0, 1.0, 50.0, 170.0, 352.0, 720.0] - src_y = [0.0, 10.0] - dst_y = [0.0, 10.0] - data = [[3.0, 4.0, 5.0], [3.0, 4.0, 5.0]] - src_cube = grid_cube(src_x, src_y, data) - dst_cube = grid_cube(dst_x, dst_y) - expected_result = [ - [4.0, 3.0, 4.0, 4.0, 5.0, 5.0, 3.0, 4.0], - [4.0, 3.0, 4.0, 4.0, 5.0, 5.0, 3.0, 4.0], - ] - _debug_data(src_cube, "noncircular SOURCE") - result_cube = self.regrid(src_cube, dst_cube) - _debug_data(result_cube, "noncircular RESULT") - self.assertArrayAllClose(result_cube.data, expected_result) - - def test_wrapping_circular(self): - # When x-coord is "circular", the above distinction does not apply : - # results are the same for both calculations. - src_x = [-10.0, 0.0, 10.0] - dst_x = [-360.0, -170.0, -1.0, 1.0, 50.0, 170.0, 352.0, 720.0] - src_y = [0.0, 10.0] - dst_y = [0.0, 10.0] - data = [[3.0, 4.0, 5.0], [3.0, 4.0, 5.0]] - src_cube = grid_cube(src_x, src_y, data) - dst_cube = grid_cube(dst_x, dst_y) - src_cube.coord("longitude").circular = True - expected_result = [ - [4.0, 3.0, 4.0, 4.0, 5.0, 5.0, 3.0, 4.0], - [4.0, 3.0, 4.0, 4.0, 5.0, 5.0, 3.0, 4.0], - ] - _debug_data(src_cube, "circular SOURCE") - result_cube = self.regrid(src_cube, dst_cube) - _debug_data(result_cube, "circular RESULT") - self.assertArrayAllClose(result_cube.data, expected_result) - - def test_wrapping_non_angular(self): - src_x = [-10.0, 0.0, 10.0] - dst_x = [-360.0, -170.0, -1.0, 1.0, 50.0, 170.0, 352.0, 720.0] - src_y = [0.0, 10.0] - dst_y = [0.0, 10.0] - data = [[3.0, 4.0, 5.0], [3.0, 4.0, 5.0]] - src_cube = grid_cube(src_x, src_y, data) - dst_cube = grid_cube(dst_x, dst_y) - for co_name in ("longitude", "latitude"): - for cube in (src_cube, dst_cube): - coord = cube.coord(co_name) - coord.coord_system = None - coord.convert_units("1") - # interpolate.regrid --> Wrapping-free results (non-circular). - expected_result = [ - [3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 5.0], - [3.0, 3.0, 4.0, 4.0, 5.0, 5.0, 5.0, 5.0], - ] - _debug_data(src_cube, "non-angle-lons SOURCE") - result_cube = self.regrid(src_cube, dst_cube) - _debug_data(result_cube, "non-angle-lons RESULT") - self.assertArrayAllClose(result_cube.data, expected_result) - - def test_source_nan(self): - src_x = [40.0, 50.0, 60.0] - src_y = [40.0, 50.0, 60.0] - dst_x = [44.99, 45.01, 48.0, 50.0, 52.0, 54.99, 55.01] - dst_y = [44.99, 45.01, 48.0, 50.0, 52.0, 54.99, 55.01] - nan = np.nan - data = [[3.0, 4.0, 5.0], [23.0, nan, 25.0], [43.0, 44.0, 45.0]] - expected_result = [ - [3.0, 4.0, 4.0, 4.0, 4.0, 4.0, 5.0], - [23.0, nan, nan, nan, nan, nan, 25.0], - [23.0, nan, nan, nan, nan, nan, 25.0], - [23.0, nan, nan, nan, nan, nan, 25.0], - [23.0, nan, nan, nan, nan, nan, 25.0], - [23.0, nan, nan, nan, nan, nan, 25.0], - [43.0, 44.0, 44.0, 44.0, 44.0, 44.0, 45.0], - ] - src_cube = grid_cube(src_x, src_y, data) - _debug_data(src_cube, "nan SOURCE") - dst_cube = grid_cube(dst_x, dst_y) - result_cube = self.regrid(src_cube, dst_cube) - _debug_data(result_cube, "nan RESULT") - self.assertArrayEqual(result_cube.data, expected_result) - - -class TestCubeRegridNearest(MixinCheckingCode, tests.IrisTest): - scheme = Nearest(extrapolation_mode="extrapolate") - - def regrid(self, src_cube, dst_cube, **kwargs): - return src_cube.regrid(dst_cube, scheme=self.scheme) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_regridding.py b/lib/iris/tests/integration/test_regridding.py deleted file mode 100644 index 3e87a8d0aa..0000000000 --- a/lib/iris/tests/integration/test_regridding.py +++ /dev/null @@ -1,248 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for regridding.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -import iris -from iris.analysis import UnstructuredNearest -from iris.analysis._regrid import RectilinearRegridder as Regridder -from iris.coord_systems import GeogCS -from iris.coords import DimCoord -from iris.cube import Cube -from iris.tests.stock import global_pp, simple_3d - - -@tests.skip_data -class TestOSGBToLatLon(tests.IrisTest): - def setUp(self): - path = tests.get_data_path( - ( - "NIMROD", - "uk2km", - "WO0000000003452", - "201007020900_u1096_ng_ey00_visibility0180_screen_2km", - ) - ) - self.src = iris.load_cube(path)[0] - # Cast up to float64, to work around numpy<=1.8 bug with means of - # arrays of 32bit floats. - self.src.data = self.src.data.astype(np.float64) - self.grid = Cube(np.empty((73, 96))) - cs = GeogCS(6370000) - lat = DimCoord( - np.linspace(46, 65, 73), - "latitude", - units="degrees", - coord_system=cs, - ) - lon = DimCoord( - np.linspace(-14, 8, 96), - "longitude", - units="degrees", - coord_system=cs, - ) - self.grid.add_dim_coord(lat, 0) - self.grid.add_dim_coord(lon, 1) - - def _regrid(self, method): - regridder = Regridder(self.src, self.grid, method, "mask") - result = regridder(self.src) - return result - - def test_linear(self): - res = self._regrid("linear") - self.assertArrayShapeStats(res, (73, 96), 17799.296120, 11207.701323) - - def test_nearest(self): - res = self._regrid("nearest") - self.assertArrayShapeStats(res, (73, 96), 17808.068828, 11225.314310) - - -@tests.skip_data -class TestGlobalSubsample(tests.IrisTest): - def setUp(self): - self.src = global_pp() - _ = self.src.data - # Cast up to float64, to work around numpy<=1.8 bug with means of - # arrays of 32bit floats. - self.src.data = self.src.data.astype(np.float64) - # Subsample and shift the target grid so that we can see a visual - # difference between regridding scheme methods. - grid = self.src[1::2, 1::3] - grid.coord("latitude").points = grid.coord("latitude").points + 1 - grid.coord("longitude").points = grid.coord("longitude").points + 1 - self.grid = grid - - def _regrid(self, method): - regridder = Regridder(self.src, self.grid, method, "mask") - result = regridder(self.src) - return result - - def test_linear(self): - res = self._regrid("linear") - self.assertArrayShapeStats(res, (36, 32), 280.35907, 15.997223) - - def test_nearest(self): - res = self._regrid("nearest") - self.assertArrayShapeStats(res, (36, 32), 280.33726, 16.064001) - - -@tests.skip_data -class TestUnstructured(tests.IrisTest): - def setUp(self): - path = tests.get_data_path( - ("NetCDF", "unstructured_grid", "theta_nodal_xios.nc") - ) - self.src = iris.load_cube(path, "Potential Temperature") - self.grid = simple_3d()[0, :, :] - - def test_nearest(self): - res = self.src.regrid(self.grid, UnstructuredNearest()) - self.assertArrayShapeStats(res, (1, 6, 3, 4), 315.890808, 11.000724) - - -class TestZonalMean_global(tests.IrisTest): - def setUp(self): - np.random.seed(0) - self.src = iris.cube.Cube(np.random.randint(0, 10, size=(140, 1))) - s_crs = iris.coord_systems.GeogCS(6371229.0) - sy_coord = iris.coords.DimCoord( - np.linspace(-90, 90, 140), - standard_name="latitude", - units="degrees", - coord_system=s_crs, - ) - sx_coord = iris.coords.DimCoord( - -180, - bounds=[-180, 180], - standard_name="longitude", - units="degrees", - circular=True, - coord_system=s_crs, - ) - self.src.add_dim_coord(sy_coord, 0) - self.src.add_dim_coord(sx_coord, 1) - - def test_linear_same_crs_global(self): - # Regrid the zonal mean onto an identical coordinate system target, but - # on a different set of longitudes - which should result in no change. - points = [-150, -90, -30, 30, 90, 150] - bounds = [ - [-180, -120], - [-120, -60], - [-60, 0], - [0, 60], - [60, 120], - [120, 180], - ] - sx_coord = self.src.coord(axis="x") - sy_coord = self.src.coord(axis="y") - x_coord = sx_coord.copy(points, bounds=bounds) - grid = iris.cube.Cube( - np.zeros([sy_coord.points.size, x_coord.points.size]) - ) - grid.add_dim_coord(sy_coord, 0) - grid.add_dim_coord(x_coord, 1) - - res = self.src.regrid(grid, iris.analysis.Linear()) - - # Ensure data remains unchanged. - # (the same along each column) - self.assertTrue( - np.array( - [ - (res.data[:, 0] - res.data[:, i]).max() - for i in range(1, res.shape[1]) - ] - ).max() - < 1e-10 - ) - self.assertArrayAlmostEqual(res.data[:, 0], self.src.data.reshape(-1)) - - -class TestZonalMean_regional(TestZonalMean_global, tests.IrisTest): - def setUp(self): - super().setUp() - - # Define a target grid and a target result (what we expect the - # regridder to return). - sx_coord = self.src.coord(axis="x") - sy_coord = self.src.coord(axis="y") - grid_crs = iris.coord_systems.RotatedGeogCS( - 37.5, 177.5, ellipsoid=iris.coord_systems.GeogCS(6371229.0) - ) - grid_x = sx_coord.copy(np.linspace(350, 370, 100)) - grid_x.circular = False - grid_x.coord_system = grid_crs - grid_y = sy_coord.copy(np.linspace(-10, 10, 100)) - grid_y.coord_system = grid_crs - grid = iris.cube.Cube( - np.zeros([grid_y.points.size, grid_x.points.size]) - ) - grid.add_dim_coord(grid_y, 0) - grid.add_dim_coord(grid_x, 1) - - # The target result is derived by regridding a multi-column version of - # the source to the target (i.e. turning a zonal mean regrid into a - # conventional regrid). - self.tar = self.zonal_mean_as_multi_column(self.src).regrid( - grid, iris.analysis.Linear() - ) - self.grid = grid - - def zonal_mean_as_multi_column(self, src_cube): - # Munge the source (duplicate source latitudes) so that we can - # utilise linear regridding as a conventional problem (that is, to - # duplicate columns so that it is no longer a zonal mean problem). - src_cube2 = src_cube.copy() - src_cube2.coord(axis="x").points = -90 - src_cube2.coord(axis="x").bounds = [-180, 0] - src_cube.coord(axis="x").points = 90 - src_cube.coord(axis="x").bounds = [0, 180] - src_cubes = iris.cube.CubeList([src_cube, src_cube2]) - return src_cubes.concatenate_cube() - - def test_linear_rotated_regional(self): - # Ensure that zonal mean source data is linearly interpolated onto a - # high resolution target. - regridder = iris.analysis.Linear() - res = self.src.regrid(self.grid, regridder) - self.assertArrayAlmostEqual(res.data, self.tar.data) - - def test_linear_rotated_regional_no_extrapolation(self): - # Capture the case where our source remains circular but we don't use - # extrapolation. - regridder = iris.analysis.Linear(extrapolation_mode="nan") - res = self.src.regrid(self.grid, regridder) - self.assertArrayAlmostEqual(res.data, self.tar.data) - - def test_linear_rotated_regional_not_circular(self): - # Capture the case where our source is not circular but we utilise - # extrapolation. - regridder = iris.analysis.Linear() - self.src.coord(axis="x").circular = False - res = self.src.regrid(self.grid, regridder) - self.assertArrayAlmostEqual(res.data, self.tar.data) - - def test_linear_rotated_regional_no_extrapolation_not_circular(self): - # Confirm how zonal mean actually works in so far as, that - # extrapolation and circular source handling is the means by which - # these usecases are supported. - # In the case where the source is neither using extrapolation and is - # not circular, then 'nan' values will result (as we would expect). - regridder = iris.analysis.Linear(extrapolation_mode="nan") - self.src.coord(axis="x").circular = False - res = self.src.regrid(self.grid, regridder) - self.assertTrue(np.isnan(res.data).all()) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_subset.py b/lib/iris/tests/integration/test_subset.py deleted file mode 100644 index bc2029afba..0000000000 --- a/lib/iris/tests/integration/test_subset.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for subset.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.coords import DimCoord -from iris.cube import Cube - - -def _make_test_cube(): - data = np.zeros((4, 4, 1)) - lats, longs = [0, 10, 20, 30], [5, 15, 25, 35] - lat_coord = DimCoord(lats, standard_name="latitude", units="degrees") - lon_coord = DimCoord(longs, standard_name="longitude", units="degrees") - vrt_coord = DimCoord([850], long_name="pressure", units="hPa") - return Cube( - data, - long_name="test_cube", - units="1", - attributes=None, - dim_coords_and_dims=[(lat_coord, 0), (lon_coord, 1)], - aux_coords_and_dims=[(vrt_coord, None)], - ) - - -class TestSubset(tests.IrisTest): - def setUp(self): - self.cube = _make_test_cube() - - def test_coordinate_subset(self): - coord = self.cube.coord("pressure") - subsetted = self.cube.subset(coord) - self.assertEqual(self.cube, subsetted) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/test_trajectory.py b/lib/iris/tests/integration/test_trajectory.py deleted file mode 100644 index a8e3acaa41..0000000000 --- a/lib/iris/tests/integration/test_trajectory.py +++ /dev/null @@ -1,353 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :mod:`iris.analysis.trajectory`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np - -import iris -from iris._lazy_data import as_lazy_data -from iris.analysis.trajectory import Trajectory -from iris.analysis.trajectory import interpolate as traj_interpolate -import iris.tests.stock as istk - - -@tests.skip_data -class TestColpex(tests.IrisTest): - def setUp(self): - # Load the COLPEX data => TZYX - path = tests.get_data_path( - ["PP", "COLPEX", "theta_and_orog_subset.pp"] - ) - cube = iris.load_cube(path, "air_potential_temperature") - cube.coord("grid_latitude").bounds = None - cube.coord("grid_longitude").bounds = None - # TODO: Workaround until regrid can handle factories - cube.remove_aux_factory(cube.aux_factories[0]) - cube.remove_coord("surface_altitude") - self.cube = cube - - def test_trajectory_extraction(self): - # Pull out a single point - no interpolation required - single_point = traj_interpolate( - self.cube, - [("grid_latitude", [-0.1188]), ("grid_longitude", [359.57958984])], - ) - expected = self.cube[..., 10, 0].data - self.assertArrayAllClose( - single_point[..., 0].data, expected, rtol=2.0e-7 - ) - self.assertCML( - single_point, ("trajectory", "single_point.cml"), checksum=False - ) - - def test_trajectory_extraction_calc(self): - # Pull out another point and test against a manually calculated result. - single_point = [ - ["grid_latitude", [-0.1188]], - ["grid_longitude", [359.584090412]], - ] - scube = self.cube[0, 0, 10:11, 4:6] - x0 = scube.coord("grid_longitude")[0].points - x1 = scube.coord("grid_longitude")[1].points - y0 = scube.data[0, 0] - y1 = scube.data[0, 1] - expected = y0 + ((y1 - y0) * ((359.584090412 - x0) / (x1 - x0))) - trajectory_cube = traj_interpolate(scube, single_point) - self.assertArrayAllClose(trajectory_cube.data, expected, rtol=2.0e-7) - - def _traj_to_sample_points(self, trajectory): - sample_points = [] - src_points = trajectory.sampled_points - for name in src_points[0].keys(): - values = [point[name] for point in src_points] - sample_points.append((name, values)) - return sample_points - - def test_trajectory_extraction_axis_aligned(self): - # Extract a simple, axis-aligned trajectory that is similar to an - # indexing operation. - # (It's not exactly the same because the source cube doesn't have - # regular spacing.) - waypoints = [ - {"grid_latitude": -0.1188, "grid_longitude": 359.57958984}, - {"grid_latitude": -0.1188, "grid_longitude": 359.66870117}, - ] - trajectory = Trajectory(waypoints, sample_count=100) - sample_points = self._traj_to_sample_points(trajectory) - trajectory_cube = traj_interpolate(self.cube, sample_points) - self.assertCML( - trajectory_cube, ("trajectory", "constant_latitude.cml") - ) - - def test_trajectory_extraction_zigzag(self): - # Extract a zig-zag trajectory - waypoints = [ - {"grid_latitude": -0.1188, "grid_longitude": 359.5886}, - {"grid_latitude": -0.0828, "grid_longitude": 359.6606}, - {"grid_latitude": -0.0468, "grid_longitude": 359.6246}, - ] - trajectory = Trajectory(waypoints, sample_count=20) - sample_points = self._traj_to_sample_points(trajectory) - trajectory_cube = traj_interpolate(self.cube[0, 0], sample_points) - expected = np.array( - [ - 287.95953369, - 287.9190979, - 287.95550537, - 287.93240356, - 287.83850098, - 287.87869263, - 287.90942383, - 287.9463501, - 287.74365234, - 287.68856812, - 287.75588989, - 287.54611206, - 287.48522949, - 287.53356934, - 287.60217285, - 287.43795776, - 287.59701538, - 287.52468872, - 287.45025635, - 287.52716064, - ], - dtype=np.float32, - ) - - self.assertCML( - trajectory_cube, ("trajectory", "zigzag.cml"), checksum=False - ) - self.assertArrayAllClose(trajectory_cube.data, expected, rtol=2.0e-7) - - def test_colpex__nearest(self): - # Check a smallish nearest-neighbour interpolation against a result - # snapshot. - test_cube = self.cube[0][0] - # Test points on a regular grid, a bit larger than the source region. - xmin, xmax = [ - fn(test_cube.coord(axis="x").points) for fn in (np.min, np.max) - ] - ymin, ymax = [ - fn(test_cube.coord(axis="x").points) for fn in (np.min, np.max) - ] - fractions = [-0.23, -0.01, 0.27, 0.624, 0.983, 1.052, 1.43] - x_points = [xmin + frac * (xmax - xmin) for frac in fractions] - y_points = [ymin + frac * (ymax - ymin) for frac in fractions] - x_points, y_points = np.meshgrid(x_points, y_points) - sample_points = [ - ("grid_longitude", x_points.flatten()), - ("grid_latitude", y_points.flatten()), - ] - result = traj_interpolate(test_cube, sample_points, method="nearest") - expected = [ - 288.07168579, - 288.07168579, - 287.9367981, - 287.82736206, - 287.78564453, - 287.8374939, - 287.8374939, - 288.07168579, - 288.07168579, - 287.9367981, - 287.82736206, - 287.78564453, - 287.8374939, - 287.8374939, - 288.07168579, - 288.07168579, - 287.9367981, - 287.82736206, - 287.78564453, - 287.8374939, - 287.8374939, - 288.07168579, - 288.07168579, - 287.9367981, - 287.82736206, - 287.78564453, - 287.8374939, - 287.8374939, - 288.07168579, - 288.07168579, - 287.9367981, - 287.82736206, - 287.78564453, - 287.8374939, - 287.8374939, - 288.07168579, - 288.07168579, - 287.9367981, - 287.82736206, - 287.78564453, - 287.8374939, - 287.8374939, - 288.07168579, - 288.07168579, - 287.9367981, - 287.82736206, - 287.78564453, - 287.8374939, - 287.8374939, - ] - self.assertArrayAllClose(result.data, expected) - - -@tests.skip_data -class TestTriPolar(tests.IrisTest): - def setUp(self): - # load data - cubes = iris.load( - tests.get_data_path(["NetCDF", "ORCA2", "votemper.nc"]) - ) - cube = cubes[0] - # The netCDF file has different data types for the points and - # bounds of 'depth'. This wasn't previously supported, so we - # emulate that old behaviour. - b32 = cube.coord("depth").bounds.astype(np.float32) - cube.coord("depth").bounds = b32 - self.cube = cube - # define a latitude trajectory (put coords in a different order - # to the cube, just to be awkward) although avoid south pole - # singularity as a sample point and the issue of snapping to - # multi-equidistant closest points from within orca antarctic hole - latitudes = list(range(-80, 90, 2)) - longitudes = [-90] * len(latitudes) - self.sample_points = [ - ("longitude", longitudes), - ("latitude", latitudes), - ] - - def test_tri_polar(self): - # extract - sampled_cube = traj_interpolate( - self.cube, self.sample_points, method="nearest" - ) - self.assertCML( - sampled_cube, ("trajectory", "tri_polar_latitude_slice.cml") - ) - - def test_tri_polar_method_linear_fails(self): - # Try to request linear interpolation. - # Not allowed, as we have multi-dimensional coords. - self.assertRaises( - iris.exceptions.CoordinateMultiDimError, - traj_interpolate, - self.cube, - self.sample_points, - method="linear", - ) - - def test_tri_polar_method_unknown_fails(self): - # Try to request unknown interpolation. - self.assertRaises( - ValueError, - traj_interpolate, - self.cube, - self.sample_points, - method="linekar", - ) - - def test_tri_polar__nearest(self): - # Check a smallish nearest-neighbour interpolation against a result - # snapshot. - test_cube = self.cube - # Use just one 2d layer, just to be faster. - test_cube = test_cube[0][0] - # Fix the fill value of the data to zero, just so that we get the same - # result under numpy < 1.11 as with 1.11. - # NOTE: numpy<1.11 *used* to assign missing data points into an - # unmasked array as =0.0, now =fill-value. - # TODO: arguably, we should support masked data properly in the - # interpolation routine. In the legacy code, that is unfortunately - # just not the case. - test_cube.data.fill_value = 0 - - # Test points on a regular global grid, with unrelated steps + offsets - # and an extended range of longitude values. - x_points = np.arange(-185.23, +360.0, 73.123) - y_points = np.arange(-89.12, +90.0, 42.847) - x_points, y_points = np.meshgrid(x_points, y_points) - sample_points = [ - ("longitude", x_points.flatten()), - ("latitude", y_points.flatten()), - ] - result = traj_interpolate(test_cube, sample_points, method="nearest") - expected = [ - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 0.0, - 12.13186264, - 10.69991493, - 9.86881161, - 7.08723927, - 9.04308414, - 12.56258678, - 10.63761806, - 9.19426727, - 28.93525505, - 23.85289955, - 26.94649506, - 0.0, - 27.88831711, - 28.65439224, - 23.39414215, - 26.78363228, - 13.53453922, - 0.0, - 17.41485596, - 0.0, - 0.0, - 13.0413475, - 0.0, - 17.10849571, - -1.67040622, - -1.64783156, - 0.0, - -1.97898054, - -1.67642927, - -1.65173221, - -1.623945, - 0.0, - ] - - self.assertArrayAllClose(result.data, expected) - - -class TestLazyData(tests.IrisTest): - def test_hybrid_height(self): - cube = istk.simple_4d_with_hybrid_height() - # Put a lazy array into the cube so we can test deferred loading. - cube.data = as_lazy_data(cube.data) - - # Use opionated grid-latitudes to avoid the issue of platform - # specific behaviour within SciPy cKDTree choosing a different - # equi-distant nearest neighbour point when there are multiple - # valid candidates. - traj = ( - ("grid_latitude", [20.4, 21.6, 22.6, 23.6]), - ("grid_longitude", [31, 32, 33, 34]), - ) - xsec = traj_interpolate(cube, traj, method="nearest") - - # Check that creating the trajectory hasn't led to the original - # data being loaded. - self.assertTrue(cube.has_lazy_data()) - self.assertCML([cube, xsec], ("trajectory", "hybrid_height.cml")) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/integration/um/__init__.py b/lib/iris/tests/integration/um/__init__.py deleted file mode 100644 index a94785ca58..0000000000 --- a/lib/iris/tests/integration/um/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Integration tests for :mod:`iris.fileformats.um` fast load functions.""" diff --git a/lib/iris/tests/integration/um/test_fieldsfile.py b/lib/iris/tests/integration/um/test_fieldsfile.py deleted file mode 100644 index 56b88c2b6d..0000000000 --- a/lib/iris/tests/integration/um/test_fieldsfile.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test the fast loading of structured Fieldsfiles. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip -from iris.cube import CubeList -from iris.fileformats.um import load_cubes as load - - -@tests.skip_data -class TestStructuredLoadFF(tests.IrisTest): - def setUp(self): - self.fname = tests.get_data_path(("FF", "structured", "small")) - - def _merge_cubes(self, cubes): - # Merge the 2D cubes returned by `iris.fileformats.um.load_cubes`. - return CubeList(cubes).merge_cube() - - def test_simple(self): - list_of_cubes = list(load(self.fname, None)) - cube = self._merge_cubes(list_of_cubes) - self.assertCML(cube) - - def test_simple_callback(self): - def callback(cube, field, filename): - cube.attributes["processing"] = "fast-ff" - - list_of_cubes = list(load(self.fname, callback=callback)) - cube = self._merge_cubes(list_of_cubes) - self.assertCML(cube) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/__init__.py b/lib/iris/tests/unit/__init__.py deleted file mode 100644 index 50929c8020..0000000000 --- a/lib/iris/tests/unit/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris` package.""" diff --git a/lib/iris/tests/unit/analysis/__init__.py b/lib/iris/tests/unit/analysis/__init__.py deleted file mode 100644 index 974b4e3584..0000000000 --- a/lib/iris/tests/unit/analysis/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis` package.""" diff --git a/lib/iris/tests/unit/analysis/area_weighted/__init__.py b/lib/iris/tests/unit/analysis/area_weighted/__init__.py deleted file mode 100644 index 464036a6dd..0000000000 --- a/lib/iris/tests/unit/analysis/area_weighted/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis._area_weighted` module.""" diff --git a/lib/iris/tests/unit/analysis/area_weighted/test_AreaWeightedRegridder.py b/lib/iris/tests/unit/analysis/area_weighted/test_AreaWeightedRegridder.py deleted file mode 100644 index ecaa028ab3..0000000000 --- a/lib/iris/tests/unit/analysis/area_weighted/test_AreaWeightedRegridder.py +++ /dev/null @@ -1,287 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :class:`iris.analysis._area_weighted.AreaWeightedRegridder`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip -from unittest import mock - -import numpy as np - -from iris import load_cube -from iris.analysis._area_weighted import ( - AreaWeightedRegridder, - _regrid_area_weighted_rectilinear_src_and_grid__prepare, -) -from iris.coord_systems import GeogCS -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test(tests.IrisTest): - def cube(self, x, y): - data = np.arange(len(x) * len(y)).reshape(len(y), len(x)) - cube = Cube(data) - lat = DimCoord(y, "latitude", units="degrees") - lon = DimCoord(x, "longitude", units="degrees") - cube.add_dim_coord(lat, 0) - cube.add_dim_coord(lon, 1) - cube.coord("latitude").guess_bounds() - cube.coord("longitude").guess_bounds() - return cube - - def grids(self): - src = self.cube(np.linspace(20, 30, 3), np.linspace(10, 25, 4)) - target = self.cube(np.linspace(22, 28, 8), np.linspace(11, 22, 9)) - return src, target - - def extract_grid(self, cube): - return cube.coord("latitude"), cube.coord("longitude") - - def check_mdtol(self, mdtol=None): - src_grid, target_grid = self.grids() - # Get _regrid_info result - _regrid_info = _regrid_area_weighted_rectilinear_src_and_grid__prepare( - src_grid, target_grid - ) - self.assertEqual(len(_regrid_info), 10) - with mock.patch( - "iris.analysis._area_weighted." - "_regrid_area_weighted_rectilinear_src_and_grid__prepare", - return_value=_regrid_info, - ) as prepare: - with mock.patch( - "iris.analysis._area_weighted." - "_regrid_area_weighted_rectilinear_src_and_grid__perform", - return_value=mock.sentinel.result, - ) as perform: - # Setup the regridder - if mdtol is None: - regridder = AreaWeightedRegridder(src_grid, target_grid) - mdtol = 1 - else: - regridder = AreaWeightedRegridder( - src_grid, target_grid, mdtol=mdtol - ) - # Now regrid the source cube - src = src_grid - result = regridder(src) - - # Make a new cube to regrid with different data so we can - # distinguish between regridding the original src grid - # definition cube and the cube passed to the regridder. - src = src_grid.copy() - src.data += 10 - result = regridder(src) - - # Prepare: - self.assertEqual(prepare.call_count, 1) - _, args, kwargs = prepare.mock_calls[0] - self.assertEqual( - self.extract_grid(args[1]), self.extract_grid(target_grid) - ) - - # Perform: - self.assertEqual(perform.call_count, 2) - _, args, kwargs = perform.mock_calls[1] - self.assertEqual(args[0], src) - self.assertEqual(kwargs, {"mdtol": mdtol}) - self.assertIs(result, mock.sentinel.result) - - def test_default(self): - self.check_mdtol() - - def test_specified_mdtol(self): - self.check_mdtol(0.5) - - def test_invalid_high_mdtol(self): - src, target = self.grids() - msg = "mdtol must be in range 0 - 1" - with self.assertRaisesRegex(ValueError, msg): - AreaWeightedRegridder(src, target, mdtol=1.2) - - def test_invalid_low_mdtol(self): - src, target = self.grids() - msg = "mdtol must be in range 0 - 1" - with self.assertRaisesRegex(ValueError, msg): - AreaWeightedRegridder(src, target, mdtol=-0.2) - - def test_mismatched_src_coord_systems(self): - src = Cube(np.zeros((3, 4))) - cs = GeogCS(6543210) - lat = DimCoord(np.arange(3), "latitude", coord_system=cs) - lon = DimCoord(np.arange(4), "longitude") - src.add_dim_coord(lat, 0) - src.add_dim_coord(lon, 1) - target = mock.Mock() - with self.assertRaises(ValueError): - AreaWeightedRegridder(src, target) - - def test_src_and_target_are_the_same(self): - src = self.cube(np.linspace(20, 30, 3), np.linspace(10, 25, 4)) - target = self.cube(np.linspace(20, 30, 3), np.linspace(10, 25, 4)) - regridder = AreaWeightedRegridder(src, target) - result = regridder(src) - self.assertArrayAllClose(result.data, target.data) - - def test_multiple_src_on_same_grid(self): - coord_names = ["latitude", "longitude"] - src1 = self.cube(np.linspace(20, 32, 4), np.linspace(10, 22, 4)) - src2 = self.cube(np.linspace(20, 32, 4), np.linspace(10, 22, 4)) - src2.data *= 4 - self.assertArrayEqual(src1.data * 4, src2.data) - for name in coord_names: - # Remove coords system and units so it is no longer spherical. - src1.coord(name).coord_system = None - src1.coord(name).units = None - src2.coord(name).coord_system = None - src2.coord(name).units = None - - target = self.cube(np.linspace(20, 32, 2), np.linspace(10, 22, 2)) - # Ensure the bounds of the target cover the same range as the - # source. - target_lat_bounds = np.column_stack( - ( - src1.coord("latitude").bounds[[0, 1], [0, 1]], - src1.coord("latitude").bounds[[2, 3], [0, 1]], - ) - ) - target.coord("latitude").bounds = target_lat_bounds - target_lon_bounds = np.column_stack( - ( - src1.coord("longitude").bounds[[0, 1], [0, 1]], - src1.coord("longitude").bounds[[2, 3], [0, 1]], - ) - ) - target.coord("longitude").bounds = target_lon_bounds - for name in coord_names: - # Remove coords system and units so it is no longer spherical. - target.coord(name).coord_system = None - target.coord(name).units = None - - regridder = AreaWeightedRegridder(src1, target) - result1 = regridder(src1) - result2 = regridder(src2) - - reference1 = self.cube(np.linspace(20, 32, 2), np.linspace(10, 22, 2)) - reference1.data = np.array( - [ - [np.mean(src1.data[0:2, 0:2]), np.mean(src1.data[0:2, 2:4])], - [np.mean(src1.data[2:4, 0:2]), np.mean(src1.data[2:4, 2:4])], - ] - ) - reference1.coord("latitude").bounds = target_lat_bounds - reference1.coord("longitude").bounds = target_lon_bounds - - reference2 = self.cube(np.linspace(20, 32, 2), np.linspace(10, 22, 2)) - reference2.data = np.array( - [ - [np.mean(src2.data[0:2, 0:2]), np.mean(src2.data[0:2, 2:4])], - [np.mean(src2.data[2:4, 0:2]), np.mean(src2.data[2:4, 2:4])], - ] - ) - reference2.coord("latitude").bounds = target_lat_bounds - reference2.coord("longitude").bounds = target_lon_bounds - - for name in coord_names: - # Remove coords system and units so it is no longer spherical. - reference1.coord(name).coord_system = None - reference1.coord(name).units = None - reference2.coord(name).coord_system = None - reference2.coord(name).units = None - - # Compare the cubes rather than just the data. - self.assertEqual(result1, reference1) - self.assertEqual(result2, reference2) - - def test_src_data_different_dims(self): - src, target = self.grids() - regridder = AreaWeightedRegridder(src, target) - result = regridder(src) - expected_mean, expected_std = 4.772097735195653, 2.211698479817678 - self.assertArrayShapeStats(result, (9, 8), expected_mean, expected_std) - # New source cube with additional "levels" dimension - # Each level has identical x-y data so the mean and std stats remain - # identical when x, y and z dims are reordered - levels = DimCoord(np.arange(5), "model_level_number") - lat = src.coord("latitude") - lon = src.coord("longitude") - data = np.repeat(src.data[np.newaxis, ...], 5, axis=0) - src = Cube(data) - src.add_dim_coord(levels, 0) - src.add_dim_coord(lat, 1) - src.add_dim_coord(lon, 2) - result = regridder(src) - self.assertArrayShapeStats( - result, (5, 9, 8), expected_mean, expected_std - ) - # Check data with dims in different order - # Reshape src so that the coords are ordered [x, z, y], - # the mean and std statistics should be the same - data = np.moveaxis(src.data.copy(), 2, 0) - src = Cube(data) - src.add_dim_coord(lon, 0) - src.add_dim_coord(levels, 1) - src.add_dim_coord(lat, 2) - result = regridder(src) - self.assertArrayShapeStats( - result, (8, 5, 9), expected_mean, expected_std - ) - # Check data with dims in different order - # Reshape src so that the coords are ordered [y, x, z], - # the mean and std statistics should be the same - data = np.moveaxis(src.data.copy(), 2, 0) - src = Cube(data) - src.add_dim_coord(lat, 0) - src.add_dim_coord(lon, 1) - src.add_dim_coord(levels, 2) - result = regridder(src) - self.assertArrayShapeStats( - result, (9, 8, 5), expected_mean, expected_std - ) - - -@tests.skip_data -class TestLazy(tests.IrisTest): - # Setup - def setUp(self) -> None: - # Prepare a cube and a template - cube_file_path = tests.get_data_path( - ["NetCDF", "regrid", "regrid_xyt.nc"] - ) - self.cube = load_cube(cube_file_path) - - template_file_path = tests.get_data_path( - ["NetCDF", "regrid", "regrid_template_global_latlon.nc"] - ) - self.template_cube = load_cube(template_file_path) - - # Chunked data makes the regridder run repeatedly - self.cube.data = self.cube.lazy_data().rechunk((1, -1, -1)) - - def test_src_stays_lazy(self) -> None: - cube = self.cube.copy() - # Regrid the cube onto the template. - regridder = AreaWeightedRegridder(cube, self.template_cube) - regridder(cube) - # Base cube stays lazy - self.assertTrue(cube.has_lazy_data()) - - def test_output_lazy(self) -> None: - cube = self.cube.copy() - # Regrid the cube onto the template. - regridder = AreaWeightedRegridder(cube, self.template_cube) - out = regridder(cube) - # Lazy base cube means lazy output - self.assertTrue(out.has_lazy_data()) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/cartography/__init__.py b/lib/iris/tests/unit/analysis/cartography/__init__.py deleted file mode 100644 index 625a6fa141..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis.cartography` module.""" diff --git a/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py b/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py deleted file mode 100644 index 612e5d8ecf..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test__get_lon_lat_coords.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.analysis.cartography._get_lon_lat_coords""" - -import pytest - -from iris.analysis.cartography import _get_lon_lat_coords as g_lon_lat -from iris.coords import AuxCoord -from iris.tests.stock import lat_lon_cube - - -@pytest.fixture -def dim_only_cube(): - return lat_lon_cube() - - -def test_dim_only(dim_only_cube): - t_lat, t_lon = dim_only_cube.dim_coords - - lon, lat = g_lon_lat(dim_only_cube) - - assert lon == t_lon - assert lat == t_lat - - -@pytest.fixture -def dim_aux_cube(dim_only_cube): - lat_dim, lon_dim = dim_only_cube.dim_coords - - lat_aux = AuxCoord.from_coord(lat_dim) - lat_aux.standard_name = "grid_latitude" - lon_aux = AuxCoord.from_coord(lon_dim) - lon_aux.standard_name = "grid_longitude" - - dim_aux_cube = dim_only_cube - dim_aux_cube.add_aux_coord(lat_aux, 0) - dim_aux_cube.add_aux_coord(lon_aux, 1) - - return dim_aux_cube - - -def test_dim_aux(dim_aux_cube): - t_lat_dim, t_lon_dim = dim_aux_cube.dim_coords - - lon, lat = g_lon_lat(dim_aux_cube) - - assert lon == t_lon_dim - assert lat == t_lat_dim - - -@pytest.fixture -def aux_only_cube(dim_aux_cube): - lon_dim, lat_dim = dim_aux_cube.dim_coords - - aux_only_cube = dim_aux_cube - aux_only_cube.remove_coord(lon_dim) - aux_only_cube.remove_coord(lat_dim) - - return dim_aux_cube - - -def test_aux_only(aux_only_cube): - aux_lat, aux_lon = aux_only_cube.aux_coords - - lon, lat = g_lon_lat(aux_only_cube) - - assert lon == aux_lon - assert lat == aux_lat - - -@pytest.fixture -def double_dim_cube(dim_only_cube): - double_dim_cube = dim_only_cube - double_dim_cube.coord("latitude").standard_name = "grid_longitude" - - return double_dim_cube - - -def test_double_dim(double_dim_cube): - t_error_message = "with multiple.*is currently disallowed" - - with pytest.raises(ValueError, match=t_error_message): - g_lon_lat(double_dim_cube) - - -@pytest.fixture -def double_aux_cube(aux_only_cube): - double_aux_cube = aux_only_cube - double_aux_cube.coord("grid_latitude").standard_name = "longitude" - - return double_aux_cube - - -def test_double_aux(double_aux_cube): - t_error_message = "with multiple.*is currently disallowed" - - with pytest.raises(ValueError, match=t_error_message): - g_lon_lat(double_aux_cube) - - -@pytest.fixture -def missing_lat_cube(dim_only_cube): - missing_lat_cube = dim_only_cube - missing_lat_cube.remove_coord("latitude") - - return missing_lat_cube - - -def test_missing_coord(missing_lat_cube): - with pytest.raises(IndexError): - g_lon_lat(missing_lat_cube) diff --git a/lib/iris/tests/unit/analysis/cartography/test__quadrant_area.py b/lib/iris/tests/unit/analysis/cartography/test__quadrant_area.py deleted file mode 100644 index a44661292f..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test__quadrant_area.py +++ /dev/null @@ -1,123 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -"""Unit tests for the `iris.analysis.cartography._quadrant_area` function""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. - -import iris.tests as tests # isort:skip - -import cf_units -import numpy as np - -from iris.analysis.cartography import ( - DEFAULT_SPHERICAL_EARTH_RADIUS, - _quadrant_area, -) - - -class TestExampleCases(tests.IrisTest): - def _radian_bounds(self, coord_list, dtype): - bound_deg = np.array(coord_list, dtype=dtype) - bound_deg = np.atleast_2d(bound_deg) - degrees = cf_units.Unit("degrees") - radians = cf_units.Unit("radians") - return degrees.convert(bound_deg, radians) - - def _as_bounded_coords(self, lats, lons, dtype=np.float64): - return ( - self._radian_bounds(lats, dtype=dtype), - self._radian_bounds(lons, dtype=dtype), - ) - - def test_area_in_north(self): - lats, lons = self._as_bounded_coords([0, 10], [0, 10]) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - self.assertArrayAllClose(area, [[1228800593851.443115234375]]) - - def test_area_in_far_north(self): - lats, lons = self._as_bounded_coords([70, 80], [0, 10]) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - self.assertArrayAllClose(area, [[319251845980.7646484375]]) - - def test_area_in_far_south(self): - lats, lons = self._as_bounded_coords([-80, -70], [0, 10]) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - self.assertArrayAllClose(area, [[319251845980.763671875]]) - - def test_area_in_north_with_reversed_lats(self): - lats, lons = self._as_bounded_coords([10, 0], [0, 10]) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - self.assertArrayAllClose(area, [[1228800593851.443115234375]]) - - def test_area_multiple_lats(self): - lats, lons = self._as_bounded_coords( - [[-80, -70], [0, 10], [70, 80]], [0, 10] - ) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - - self.assertArrayAllClose( - area, - [ - [319251845980.763671875], - [1228800593851.443115234375], - [319251845980.7646484375], - ], - ) - - def test_area_multiple_lats_and_lons(self): - lats, lons = self._as_bounded_coords( - [[-80, -70], [0, 10], [70, 80]], [[0, 10], [10, 30]] - ) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - - self.assertArrayAllClose( - area, - [ - [3.19251846e11, 6.38503692e11], - [1.22880059e12, 2.45760119e12], - [3.19251846e11, 6.38503692e11], - ], - ) - - def test_symmetric_64_bit(self): - lats, lons = self._as_bounded_coords( - [[-90, -89.375], [89.375, 90]], [0, 10], dtype=np.float64 - ) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - self.assertArrayAllClose(area, area[::-1]) - - def test_symmetric_32_bit(self): - lats, lons = self._as_bounded_coords( - [[-90, -89.375], [89.375, 90]], [0, 10], dtype=np.float32 - ) - area = _quadrant_area(lats, lons, DEFAULT_SPHERICAL_EARTH_RADIUS) - self.assertArrayAllClose(area, area[::-1]) - - -class TestErrorHandling(tests.IrisTest): - def test_lat_bounds_1d_error(self): - self._assert_error_on_malformed_bounds([0, 10], [[0, 10]]) - - def test_lon_bounds_1d_error(self): - self._assert_error_on_malformed_bounds([[0, 10]], [0, 10]) - - def test_too_many_lat_bounds_error(self): - self._assert_error_on_malformed_bounds([[0, 10, 20]], [[0, 10]]) - - def test_too_many_lon_bounds_error(self): - self._assert_error_on_malformed_bounds([[0, 10]], [[0, 10, 20]]) - - def _assert_error_on_malformed_bounds(self, lat_bnds, lon_bnds): - with self.assertRaisesRegex( - ValueError, r"Bounds must be \[n,2\] array" - ): - _quadrant_area(np.array(lat_bnds), np.array(lon_bnds), 1.0) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/cartography/test__xy_range.py b/lib/iris/tests/unit/analysis/cartography/test__xy_range.py deleted file mode 100644 index 009c97fc34..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test__xy_range.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -"""Unit tests for :func:`iris.analysis.cartography._xy_range`""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. - -import iris.tests as tests # isort:skip -import numpy as np - -from iris.analysis.cartography import _xy_range -import iris.tests.stock as stock - - -class Test(tests.IrisTest): - def test_bounds_mismatch(self): - cube = stock.realistic_3d() - cube.coord("grid_longitude").guess_bounds() - - with self.assertRaisesRegex(ValueError, "bounds"): - _ = _xy_range(cube) - - def test_non_circular(self): - cube = stock.realistic_3d() - assert not cube.coord("grid_longitude").circular - - result_non_circ = _xy_range(cube) - self.assertEqual(result_non_circ, ((-5.0, 5.0), (-4.0, 4.0))) - - @tests.skip_data - def test_geog_cs_circular(self): - cube = stock.global_pp() - assert cube.coord("longitude").circular - - result = _xy_range(cube) - np.testing.assert_array_almost_equal( - result, ((0, 360), (-90, 90)), decimal=0 - ) - - @tests.skip_data - def test_geog_cs_regional(self): - cube = stock.global_pp() - cube = cube[10:20, 20:30] - assert not cube.coord("longitude").circular - - result = _xy_range(cube) - np.testing.assert_array_almost_equal( - result, ((75, 108.75), (42.5, 65)), decimal=0 - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/cartography/test_area_weights.py b/lib/iris/tests/unit/analysis/cartography/test_area_weights.py deleted file mode 100644 index 696841ddd6..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test_area_weights.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. - -"""Unit tests for the `iris.analysis.cartography.area_weights` function""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip -import iris.analysis.cartography -import iris.tests.stock as stock - - -class TestInvalidUnits(tests.IrisTest): - def test_latitude_no_units(self): - cube = stock.lat_lon_cube() - cube.coord("longitude").guess_bounds() - cube.coord("latitude").guess_bounds() - cube.coord("latitude").units = None - with self.assertRaisesRegex( - ValueError, "Units of degrees or " "radians required" - ): - iris.analysis.cartography.area_weights(cube) - - def test_longitude_no_units(self): - cube = stock.lat_lon_cube() - cube.coord("latitude").guess_bounds() - cube.coord("longitude").guess_bounds() - cube.coord("longitude").units = None - with self.assertRaisesRegex( - ValueError, "Units of degrees or " "radians required" - ): - iris.analysis.cartography.area_weights(cube) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py b/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py deleted file mode 100644 index 810851362e..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test_gridcell_angles.py +++ /dev/null @@ -1,345 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function -:func:`iris.analysis.cartography.gridcell_angles`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from cf_units import Unit -import numpy as np - -from iris.analysis.cartography import gridcell_angles -from iris.coords import AuxCoord -from iris.cube import Cube -from iris.tests.stock import lat_lon_cube, sample_2d_latlons - - -def _2d_multicells_testcube(cellsize_degrees=1.0): - """ - Create a test cube with a grid of X and Y points, where each gridcell - is independent (disjoint), arranged at an angle == the x-coord point. - - """ - # Setup np.linspace arguments to make the coordinate points. - x0, x1, nx = -164, 164, 9 - y0, y1, ny = -75, 75, 7 - - lats = np.linspace(y0, y1, ny, endpoint=True) - lons_angles = np.linspace(x0, x1, nx, endpoint=True) - x_pts_2d, y_pts_2d = np.meshgrid(lons_angles, lats) - - # Make gridcells rectangles surrounding these centrepoints, but also - # tilted at various angles (= same as x-point lons, as that's easy). - - # Calculate centrepoint lons+lats : in radians, and shape (ny, nx, 1). - xangs, yangs = np.deg2rad(x_pts_2d), np.deg2rad(y_pts_2d) - xangs, yangs = [arr[..., None] for arr in (xangs, yangs)] - # Program which corners are up+down on each gridcell axis. - dx_corners = [[[-1, 1, 1, -1]]] - dy_corners = [[[-1, -1, 1, 1]]] - # Calculate the relative offsets in x+y at the 4 corners. - x_ofs_2d = cellsize_degrees * np.cos(xangs) * dx_corners - x_ofs_2d -= cellsize_degrees * np.sin(xangs) * dy_corners - y_ofs_2d = cellsize_degrees * np.cos(xangs) * dy_corners - y_ofs_2d += cellsize_degrees * np.sin(xangs) * dx_corners - # Apply a latitude stretch to make correct angles on the globe. - y_ofs_2d *= np.cos(yangs) - # Make bounds arrays by adding the corner offsets to the centrepoints. - x_bds_2d = x_pts_2d[..., None] + x_ofs_2d - y_bds_2d = y_pts_2d[..., None] + y_ofs_2d - - # Create a cube with these points + bounds in its 'X' and 'Y' coords. - co_x = AuxCoord( - points=x_pts_2d, - bounds=x_bds_2d, - standard_name="longitude", - units="degrees", - ) - co_y = AuxCoord( - points=y_pts_2d, - bounds=y_bds_2d, - standard_name="latitude", - units="degrees", - ) - cube = Cube(np.zeros((ny, nx))) - cube.add_aux_coord(co_x, (0, 1)) - cube.add_aux_coord(co_y, (0, 1)) - return cube - - -class TestGridcellAngles(tests.IrisTest): - def setUp(self): - # Make a small "normal" contiguous-bounded cube to test on. - # This one is regional. - self.standard_regional_cube = sample_2d_latlons( - regional=True, transformed=True - ) - # Record the standard correct angle answers. - result_cube = gridcell_angles(self.standard_regional_cube) - result_cube.convert_units("degrees") - self.standard_result_cube = result_cube - self.standard_small_cube_results = result_cube.data - - def _check_multiple_orientations_and_latitudes( - self, - method="mid-lhs, mid-rhs", - atol_degrees=0.005, - cellsize_degrees=1.0, - ): - cube = _2d_multicells_testcube(cellsize_degrees=cellsize_degrees) - - # Calculate gridcell angles at each point. - angles_cube = gridcell_angles(cube, cell_angle_boundpoints=method) - - # Check that the results are a close match to the original intended - # gridcell orientation angles. - # NOTE: neither the above gridcell construction nor the calculation - # itself are exact : Errors scale as the square of gridcell sizes. - angles_cube.convert_units("degrees") - angles_calculated = angles_cube.data - - # Note: the gridcell angles **should** just match the longitudes at - # each point - angles_expected = cube.coord("longitude").points - - # Wrap both into standard range for comparison. - angles_calculated = (angles_calculated + 360.0) % 360.0 - angles_expected = (angles_expected + 360.0) % 360.0 - - # Assert (toleranced) equality, and return results. - self.assertArrayAllClose( - angles_calculated, angles_expected, atol=atol_degrees - ) - - return angles_calculated, angles_expected - - def test_various_orientations_and_locations(self): - self._check_multiple_orientations_and_latitudes() - - def test_result_form(self): - # Check properties of the result cube *other than* the data values. - test_cube = self.standard_regional_cube - result_cube = self.standard_result_cube - self.assertEqual( - result_cube.long_name, "gridcell_angle_from_true_east" - ) - self.assertEqual(result_cube.units, Unit("degrees")) - self.assertEqual(len(result_cube.coords()), 2) - self.assertEqual( - result_cube.coord(axis="x"), test_cube.coord(axis="x") - ) - self.assertEqual( - result_cube.coord(axis="y"), test_cube.coord(axis="y") - ) - - def test_bottom_edge_method(self): - # Get results with the "other" calculation method + check to tolerance. - # A smallish cellsize should yield similar results in both cases. - r1, _ = self._check_multiple_orientations_and_latitudes() - r2, _ = self._check_multiple_orientations_and_latitudes( - method="lower-left, lower-right", - cellsize_degrees=0.1, - atol_degrees=0.1, - ) - - # Not *exactly* the same : this checks we tested the 'other' method ! - self.assertFalse(np.allclose(r1, r2)) - # Note: results are a bit different in places. This is acceptable. - self.assertArrayAllClose(r1, r2, atol=0.1) - - def test_bounded_coord_args(self): - # Check that passing the coords gives the same result as the cube. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - result = gridcell_angles(co_x, co_y) - self.assertArrayAllClose(result.data, self.standard_small_cube_results) - - def test_coords_radians_args(self): - # Check it still works with coords converted to radians. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - for coord in (co_x, co_y): - coord.convert_units("radians") - result = gridcell_angles(co_x, co_y) - self.assertArrayAllClose(result.data, self.standard_small_cube_results) - - def test_bounds_array_args(self): - # Check we can calculate from bounds values alone. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - # Results drawn from coord bounds should be nearly the same, - # but not exactly, because of the different 'midpoint' values. - result = gridcell_angles(co_x.bounds, co_y.bounds) - self.assertArrayAllClose( - result.data, self.standard_small_cube_results, atol=0.1 - ) - - def test_unbounded_regional_coord_args(self): - # Remove the coord bounds to check points-based calculation. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - for coord in (co_x, co_y): - coord.bounds = None - result = gridcell_angles(co_x, co_y) - # Note: in this case, we can expect the leftmost and rightmost columns - # to be rubbish, because the data is not global. - # But the rest should match okay. - self.assertArrayAllClose( - result.data[:, 1:-1], self.standard_small_cube_results[:, 1:-1] - ) - - def test_points_array_args(self): - # Check we can calculate from points arrays alone (no coords). - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - # As previous, the leftmost and rightmost columns are not good. - result = gridcell_angles(co_x.points, co_y.points) - self.assertArrayAllClose( - result.data[:, 1:-1], self.standard_small_cube_results[:, 1:-1] - ) - - def test_unbounded_global(self): - # For a contiguous global grid, a result based on points, i.e. with the - # bounds removed, should be a reasonable match for the 'ideal' one - # based on the bounds. - - # Make a global cube + calculate ideal bounds-based results. - global_cube = sample_2d_latlons(transformed=True) - result_cube = gridcell_angles(global_cube) - result_cube.convert_units("degrees") - global_cube_results = result_cube.data - - # Check a points-based calculation on the same basic grid. - co_x, co_y = (global_cube.coord(axis=ax) for ax in ("x", "y")) - for coord in (co_x, co_y): - coord.bounds = None - result = gridcell_angles(co_x, co_y) - # In this case, the match is actually rather poor (!). - self.assertArrayAllClose(result.data, global_cube_results, atol=7.5) - # Leaving off first + last columns again gives a decent result. - self.assertArrayAllClose( - result.data[:, 1:-1], global_cube_results[:, 1:-1] - ) - - # NOTE: although this looks just as bad as 'test_points_array_args', - # maximum errors there in the end columns are actually > 100 degrees ! - - def test_nonlatlon_coord_system(self): - # Check with points specified in an unexpected coord system. - cube = sample_2d_latlons(regional=True, rotated=True) - result = gridcell_angles(cube) - self.assertArrayAllClose(result.data, self.standard_small_cube_results) - # Check that the result has transformed (true-latlon) coordinates. - self.assertEqual(len(result.coords()), 2) - x_coord = result.coord(axis="x") - y_coord = result.coord(axis="y") - self.assertEqual(x_coord.shape, cube.shape) - self.assertEqual(y_coord.shape, cube.shape) - self.assertIsNotNone(cube.coord_system) - self.assertIsNone(x_coord.coord_system) - self.assertIsNone(y_coord.coord_system) - - def test_fail_coords_bad_units(self): - # Check error with bad coords units. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - co_y.units = "m" - with self.assertRaisesRegex(ValueError, "must have angular units"): - gridcell_angles(co_x, co_y) - - def test_fail_nonarraylike(self): - # Check error with bad args. - co_x, co_y = 1, 2 - with self.assertRaisesRegex( - ValueError, "must have array shape property" - ): - gridcell_angles(co_x, co_y) - - def test_fail_non2d_coords(self): - # Check error with bad args. - cube = lat_lon_cube() - with self.assertRaisesRegex( - ValueError, "inputs must have 2-dimensional shape" - ): - gridcell_angles(cube) - - def test_fail_different_shapes(self): - # Check error with mismatched shapes. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - co_y = co_y[1:] - with self.assertRaisesRegex(ValueError, "must have same shape"): - gridcell_angles(co_x, co_y) - - def test_fail_different_coord_system(self): - # Check error with mismatched coord systems. - cube = sample_2d_latlons(regional=True, rotated=True) - cube.coord(axis="x").coord_system = None - with self.assertRaisesRegex( - ValueError, "must have same coordinate system" - ): - gridcell_angles(cube) - - def test_fail_cube_dims(self): - # Check error with mismatched cube dims. - cube = self.standard_regional_cube - # Make 5x6 into 5x5. - cube = cube[:, :-1] - co_x = cube.coord(axis="x") - pts, bds = co_x.points, co_x.bounds - co_new_x = co_x.copy( - points=pts.transpose((1, 0)), bounds=bds.transpose((1, 0, 2)) - ) - cube.remove_coord(co_x) - cube.add_aux_coord(co_new_x, (1, 0)) - with self.assertRaisesRegex( - ValueError, "must have the same cube dimensions" - ): - gridcell_angles(cube) - - def test_fail_coord_noncoord(self): - # Check that passing a coord + an array gives an error. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - with self.assertRaisesRegex( - ValueError, "is a Coordinate, but .* is not" - ): - gridcell_angles(co_x, co_y.bounds) - - def test_fail_noncoord_coord(self): - # Check that passing an array + a coord gives an error. - co_x, co_y = ( - self.standard_regional_cube.coord(axis=ax) for ax in ("x", "y") - ) - with self.assertRaisesRegex( - ValueError, "is a Coordinate, but .* is not" - ): - gridcell_angles(co_x.points, co_y) - - def test_fail_bad_method(self): - with self.assertRaisesRegex( - ValueError, "unrecognised cell_angle_boundpoints" - ): - self._check_multiple_orientations_and_latitudes( - method="something_unknown" - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/cartography/test_project.py b/lib/iris/tests/unit/analysis/cartography/test_project.py deleted file mode 100644 index 8649cc55ea..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test_project.py +++ /dev/null @@ -1,169 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :func:`iris.analysis.cartography.project`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs -import numpy as np - -from iris.analysis.cartography import project -import iris.coord_systems -import iris.coords -import iris.cube -import iris.tests -import iris.tests.stock - -ROBINSON = ccrs.Robinson() - - -def low_res_4d(): - cube = iris.tests.stock.realistic_4d_no_derived() - cube = cube[0:2, 0:3, ::10, ::10] - cube.remove_coord("surface_altitude") - return cube - - -class TestAll(tests.IrisTest): - def setUp(self): - cs = iris.coord_systems.GeogCS(6371229) - self.cube = iris.cube.Cube(np.zeros(25).reshape(5, 5)) - self.cube.add_dim_coord( - iris.coords.DimCoord( - np.arange(5), - standard_name="latitude", - units="degrees", - coord_system=cs, - ), - 0, - ) - self.cube.add_dim_coord( - iris.coords.DimCoord( - np.arange(5), - standard_name="longitude", - units="degrees", - coord_system=cs, - ), - 1, - ) - - self.tcs = iris.coord_systems.GeogCS(6371229) - - def test_is_iris_coord_system(self): - res, _ = project(self.cube, self.tcs) - self.assertEqual( - res.coord("projection_y_coordinate").coord_system, self.tcs - ) - self.assertEqual( - res.coord("projection_x_coordinate").coord_system, self.tcs - ) - - self.assertIsNot( - res.coord("projection_y_coordinate").coord_system, self.tcs - ) - self.assertIsNot( - res.coord("projection_x_coordinate").coord_system, self.tcs - ) - - @tests.skip_data - def test_bad_resolution_negative(self): - cube = low_res_4d() - with self.assertRaises(ValueError): - project(cube, ROBINSON, nx=-200, ny=200) - - @tests.skip_data - def test_bad_resolution_non_numeric(self): - cube = low_res_4d() - with self.assertRaises(TypeError): - project(cube, ROBINSON, nx=200, ny="abc") - - @tests.skip_data - def test_missing_lat(self): - cube = low_res_4d() - cube.remove_coord("grid_latitude") - with self.assertRaises(ValueError): - project(cube, ROBINSON) - - @tests.skip_data - def test_missing_lon(self): - cube = low_res_4d() - cube.remove_coord("grid_longitude") - with self.assertRaises(ValueError): - project(cube, ROBINSON) - - @tests.skip_data - def test_missing_latlon(self): - cube = low_res_4d() - cube.remove_coord("grid_longitude") - cube.remove_coord("grid_latitude") - with self.assertRaises(ValueError): - project(cube, ROBINSON) - - @tests.skip_data - def test_default_resolution(self): - cube = low_res_4d() - new_cube, extent = project(cube, ROBINSON) - self.assertEqual(new_cube.shape, cube.shape) - - @tests.skip_data - def test_explicit_resolution(self): - cube = low_res_4d() - nx, ny = 5, 4 - new_cube, extent = project(cube, ROBINSON, nx=nx, ny=ny) - self.assertEqual(new_cube.shape, cube.shape[:2] + (ny, nx)) - - @tests.skip_data - def test_explicit_resolution_single_point(self): - cube = low_res_4d() - nx, ny = 1, 1 - new_cube, extent = project(cube, ROBINSON, nx=nx, ny=ny) - self.assertEqual(new_cube.shape, cube.shape[:2] + (ny, nx)) - - @tests.skip_data - def test_mismatched_coord_systems(self): - cube = low_res_4d() - cube.coord("grid_longitude").coord_system = None - with self.assertRaises(ValueError): - project(cube, ROBINSON) - - @tests.skip_data - def test_extent(self): - cube = low_res_4d() - _, extent = project(cube, ROBINSON) - self.assertEqual( - extent, - [ - -17005833.33052523, - 17005833.33052523, - -8625154.6651, - 8625154.6651, - ], - ) - - @tests.skip_data - def test_cube(self): - cube = low_res_4d() - new_cube, _ = project(cube, ROBINSON) - self.assertCMLApproxData(new_cube) - - @tests.skip_data - def test_no_coord_system(self): - cube = low_res_4d() - cube.coord("grid_longitude").coord_system = None - cube.coord("grid_latitude").coord_system = None - with iris.tests.mock.patch("warnings.warn") as warn: - _, _ = project(cube, ROBINSON) - warn.assert_called_once_with( - "Coordinate system of latitude and " - "longitude coordinates is not specified. " - "Assuming WGS84 Geodetic." - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/cartography/test_rotate_grid_vectors.py b/lib/iris/tests/unit/analysis/cartography/test_rotate_grid_vectors.py deleted file mode 100644 index f5c882a983..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test_rotate_grid_vectors.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function -:func:`iris.analysis.cartography.rotate_grid_vectors`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest.mock import Mock -from unittest.mock import call as mock_call - -import numpy as np - -from iris.analysis.cartography import rotate_grid_vectors -from iris.cube import Cube -from iris.tests.stock import sample_2d_latlons - - -class TestRotateGridVectors(tests.IrisTest): - def _check_angles_calculation( - self, angles_in_degrees=True, nan_angles_mask=None - ): - # Check basic maths on a 2d latlon grid. - u_cube = sample_2d_latlons(regional=True, transformed=True) - u_cube.units = "ms-1" - u_cube.rename("dx") - u_cube.data[...] = 0 - v_cube = u_cube.copy() - v_cube.rename("dy") - - # Define 6 different vectors, repeated in each data row. - in_vu = np.array([(0, 1), (2, -1), (-1, -1), (-3, 1), (2, 0), (0, 0)]) - in_angs = np.rad2deg(np.arctan2(in_vu[..., 0], in_vu[..., 1])) - in_mags = np.sqrt(np.sum(in_vu * in_vu, axis=1)) - v_cube.data[...] = in_vu[..., 0] - u_cube.data[...] = in_vu[..., 1] - - # Define 5 different test rotation angles, one for each data row. - rotation_angles = np.array([0.0, -45.0, 135, -140.0, 90.0]) - ang_cube_data = np.broadcast_to(rotation_angles[:, None], u_cube.shape) - ang_cube = u_cube.copy() - if angles_in_degrees: - ang_cube.units = "degrees" - else: - ang_cube.units = "radians" - ang_cube_data = np.deg2rad(ang_cube_data) - ang_cube.data[:] = ang_cube_data - - if nan_angles_mask is not None: - ang_cube.data[nan_angles_mask] = np.nan - - # Rotate all vectors by all the given angles. - result = rotate_grid_vectors(u_cube, v_cube, ang_cube) - out_u, out_v = [cube.data for cube in result] - - # Check that vector magnitudes were unchanged. - out_mags = np.sqrt(out_u * out_u + out_v * out_v) - expect_mags = in_mags[None, :] - self.assertArrayAllClose(out_mags, expect_mags) - - # Check that vector angles are all as expected. - out_angs = np.rad2deg(np.arctan2(out_v, out_u)) - expect_angs = in_angs[None, :] + rotation_angles[:, None] - ang_diffs = out_angs - expect_angs - # Fix for null vectors, and +/-360 differences. - ang_diffs[np.abs(out_mags) < 0.001] = 0.0 - ang_diffs[np.isclose(np.abs(ang_diffs), 360.0)] = 0.0 - # Check that any differences are very small. - self.assertArrayAllClose(ang_diffs, 0.0) - - # Check that results are always masked arrays, masked at NaN angles. - self.assertTrue(np.ma.isMaskedArray(out_u)) - self.assertTrue(np.ma.isMaskedArray(out_v)) - if nan_angles_mask is not None: - self.assertArrayEqual(out_u.mask, nan_angles_mask) - self.assertArrayEqual(out_v.mask, nan_angles_mask) - - def test_angles_calculation(self): - self._check_angles_calculation() - - def test_angles_in_radians(self): - self._check_angles_calculation(angles_in_degrees=False) - - def test_angles_from_grid(self): - # Check it will gets angles from 'u_cube', and pass any kwargs on to - # the angles routine. - u_cube = sample_2d_latlons(regional=True, transformed=True) - u_cube = u_cube[:2, :3] - u_cube.units = "ms-1" - u_cube.rename("dx") - u_cube.data[...] = 1.0 - v_cube = u_cube.copy() - v_cube.rename("dy") - v_cube.data[...] = 0.0 - - # Setup a fake angles result from the inner call to 'gridcell_angles'. - angles_result_data = np.array( - [[0.0, 90.0, 180.0], [-180.0, -90.0, 270.0]] - ) - angles_result_cube = Cube(angles_result_data, units="degrees") - angles_kwargs = {"this": 2} - angles_call_patch = self.patch( - "iris.analysis._grid_angles.gridcell_angles", - Mock(return_value=angles_result_cube), - ) - - # Call the routine. - result = rotate_grid_vectors( - u_cube, v_cube, grid_angles_kwargs=angles_kwargs - ) - - self.assertEqual( - angles_call_patch.call_args_list, [mock_call(u_cube, this=2)] - ) - - out_u, out_v = [cube.data for cube in result] - # Records what results should be for the various n*90deg rotations. - expect_u = np.array([[1.0, 0.0, -1.0], [-1.0, 0.0, 0.0]]) - expect_v = np.array([[0.0, 1.0, 0.0], [0.0, -1.0, -1.0]]) - # Check results are as expected. - self.assertArrayAllClose(out_u, expect_u) - self.assertArrayAllClose(out_v, expect_v) - - def test_nan_vectors(self): - bad_angle_points = np.zeros((5, 6), dtype=bool) - bad_angle_points[2, 3] = True - self._check_angles_calculation(nan_angles_mask=bad_angle_points) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py b/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py deleted file mode 100644 index 7952b3bb46..0000000000 --- a/lib/iris/tests/unit/analysis/cartography/test_rotate_winds.py +++ /dev/null @@ -1,515 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function -:func:`iris.analysis.cartography.rotate_winds`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs -import numpy as np -import numpy.ma as ma -import pytest - -from iris.analysis.cartography import rotate_winds, unrotate_pole -import iris.coord_systems -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube - - -def uv_cubes(x=None, y=None): - """Return u, v cubes with a grid in a rotated pole CRS.""" - cs = iris.coord_systems.RotatedGeogCS( - grid_north_pole_latitude=37.5, grid_north_pole_longitude=177.5 - ) - if x is None: - x = np.linspace(311.9, 391.1, 6) - if y is None: - y = np.linspace(-23.6, 24.8, 5) - - x2d, y2d = np.meshgrid(x, y) - u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2) - v = 20 * np.cos(6 * np.deg2rad(x2d)) - lon = DimCoord( - x, standard_name="grid_longitude", units="degrees", coord_system=cs - ) - lat = DimCoord( - y, standard_name="grid_latitude", units="degrees", coord_system=cs - ) - u_cube = Cube(u, standard_name="x_wind", units="m/s") - v_cube = Cube(v, standard_name="y_wind", units="m/s") - for cube in (u_cube, v_cube): - cube.add_dim_coord(lat.copy(), 0) - cube.add_dim_coord(lon.copy(), 1) - return u_cube, v_cube - - -def uv_cubes_3d(ref_cube, n_realization=3): - """ - Return 3d u, v cubes with a grid in a rotated pole CRS taken from - the provided 2d cube, by adding a realization dimension - coordinate bound to teh zeroth dimension. - - """ - lat = ref_cube.coord("grid_latitude") - lon = ref_cube.coord("grid_longitude") - x2d, y2d = np.meshgrid(lon.points, lat.points) - u = 10 * (2 * np.cos(2 * np.deg2rad(x2d) + 3 * np.deg2rad(y2d + 30)) ** 2) - v = 20 * np.cos(6 * np.deg2rad(x2d)) - # Multiply slices by factor to give variation over 0th dim. - factor = np.arange(1, n_realization + 1).reshape(n_realization, 1, 1) - u = factor * u - v = factor * v - realization = DimCoord(np.arange(n_realization), "realization") - u_cube = Cube(u, standard_name="x_wind", units="m/s") - v_cube = Cube(v, standard_name="y_wind", units="m/s") - for cube in (u_cube, v_cube): - cube.add_dim_coord(realization.copy(), 0) - cube.add_dim_coord(lat.copy(), 1) - cube.add_dim_coord(lon.copy(), 2) - return u_cube, v_cube - - -class TestPrerequisites(tests.IrisTest): - def test_different_coord_systems(self): - u, v = uv_cubes() - v.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(1) - with self.assertRaisesRegex( - ValueError, "Coordinates differ between u and v cubes" - ): - rotate_winds(u, v, iris.coord_systems.OSGB()) - - def test_different_xy_coord_systems(self): - u, v = uv_cubes() - u.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(1) - v.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(1) - with self.assertRaisesRegex( - ValueError, "Coordinate systems of x and y coordinates differ" - ): - rotate_winds(u, v, iris.coord_systems.OSGB()) - - def test_different_shape(self): - x = np.linspace(311.9, 391.1, 6) - y = np.linspace(-23.6, 24.8, 5) - u, _ = uv_cubes(x, y) - _, v = uv_cubes(x[:-1], y) - with self.assertRaisesRegex(ValueError, "same shape"): - rotate_winds(u, v, iris.coord_systems.OSGB()) - - def test_xy_dimensionality(self): - u, v = uv_cubes() - # Replace 1d lat with 2d lat. - x = u.coord("grid_longitude").points - y = u.coord("grid_latitude").points - x2d, y2d = np.meshgrid(x, y) - lat_2d = AuxCoord( - y2d, - "grid_latitude", - units="degrees", - coord_system=u.coord("grid_latitude").coord_system, - ) - for cube in (u, v): - cube.remove_coord("grid_latitude") - cube.add_aux_coord(lat_2d.copy(), (0, 1)) - - with self.assertRaisesRegex( - ValueError, - "x and y coordinates must have the same number of dimensions", - ): - rotate_winds(u, v, iris.coord_systems.OSGB()) - - def test_dim_mapping(self): - x = np.linspace(311.9, 391.1, 3) - y = np.linspace(-23.6, 24.8, 3) - u, v = uv_cubes(x, y) - v.transpose() - with self.assertRaisesRegex(ValueError, "Dimension mapping"): - rotate_winds(u, v, iris.coord_systems.OSGB()) - - -class TestAnalyticComparison(tests.IrisTest): - @staticmethod - def _unrotate_equation( - rotated_lons, rotated_lats, rotated_us, rotated_vs, pole_lon, pole_lat - ): - # Perform a rotated-pole 'unrotate winds' transformation on arrays of - # rotated-lat, rotated-lon, u and v. - # This can be defined as an analytic function : cf. UMDP015 - - # Work out the rotation angles. - lambda_angle = np.radians(pole_lon - 180.0) - phi_angle = np.radians(90.0 - pole_lat) - - # Get the locations in true lats+lons. - trueLongitude, trueLatitude = unrotate_pole( - rotated_lons, rotated_lats, pole_lon, pole_lat - ) - - # Calculate inter-coordinate rotation coefficients. - cos_rot = np.cos(np.radians(rotated_lons)) * np.cos( - np.radians(trueLongitude) - lambda_angle - ) + np.sin(np.radians(rotated_lons)) * np.sin( - np.radians(trueLongitude) - lambda_angle - ) * np.cos( - phi_angle - ) - sin_rot = -( - ( - np.sin(np.radians(trueLongitude) - lambda_angle) - * np.sin(phi_angle) - ) - / np.cos(np.radians(rotated_lats)) - ) - - # Matrix-multiply to rotate the vectors. - u_true = rotated_us * cos_rot - rotated_vs * sin_rot - v_true = rotated_vs * cos_rot + rotated_us * sin_rot - - return u_true, v_true - - def _check_rotated_to_true(self, u_rot, v_rot, target_cs, **kwds): - # Run test calculation (numeric). - u_true, v_true = rotate_winds(u_rot, v_rot, target_cs) - - # Perform same calculation via the reference method (equations). - cs_rot = u_rot.coord("grid_longitude").coord_system - pole_lat = cs_rot.grid_north_pole_latitude - pole_lon = cs_rot.grid_north_pole_longitude - rotated_lons = u_rot.coord("grid_longitude").points - rotated_lats = u_rot.coord("grid_latitude").points - rotated_lons_2d, rotated_lats_2d = np.meshgrid( - rotated_lons, rotated_lats - ) - rotated_u, rotated_v = u_rot.data, v_rot.data - u_ref, v_ref = self._unrotate_equation( - rotated_lons_2d, - rotated_lats_2d, - rotated_u, - rotated_v, - pole_lon, - pole_lat, - ) - - # Check that all the numerical results are within given tolerances. - self.assertArrayAllClose(u_true.data, u_ref, **kwds) - self.assertArrayAllClose(v_true.data, v_ref, **kwds) - - def test_rotated_to_true__small(self): - # Check for a small field with varying data. - target_cs = iris.coord_systems.GeogCS(6371229) - u_rot, v_rot = uv_cubes() - self._check_rotated_to_true( - u_rot, v_rot, target_cs, rtol=1e-5, atol=0.0005 - ) - - def test_rotated_to_true_global(self): - # Check for global fields with various constant wind values - # - constant in the rotated pole system, that is. - # We expect less accuracy where this gets close to the true poles. - target_cs = iris.coord_systems.GeogCS(6371229) - u_rot, v_rot = uv_cubes( - x=np.arange(0, 360.0, 15), y=np.arange(-89, 89, 10) - ) - for vector in ((1, 0), (0, 1), (1, 1), (-3, -1.5)): - u_rot.data[...] = vector[0] - v_rot.data[...] = vector[1] - self._check_rotated_to_true( - u_rot, - v_rot, - target_cs, - rtol=5e-4, - atol=5e-4, - err_msg="vector={}".format(vector), - ) - - -class TestRotatedToOSGB(tests.IrisTest): - # Define some coordinate ranges for the uv_cubes 'standard' RotatedPole - # system, that exceed the OSGB margins, but not by "too much". - _rp_x_min, _rp_x_max = -5.0, 5.0 - _rp_y_min, _rp_y_max = -5.0, 15.0 - - def _uv_cubes_limited_extent(self): - # Make test cubes suitable for transforming to OSGB, as the standard - # 'uv_cubes' result goes too far outside, leading to errors. - x = np.linspace(self._rp_x_min, self._rp_x_max, 6) - y = np.linspace(self._rp_y_min, self._rp_y_max, 5) - return uv_cubes(x=x, y=y) - - def test_name(self): - u, v = self._uv_cubes_limited_extent() - u.rename("bob") - v.rename("alice") - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - self.assertEqual(ut.name(), "transformed_" + u.name()) - self.assertEqual(vt.name(), "transformed_" + v.name()) - - def test_new_coords(self): - u, v = self._uv_cubes_limited_extent() - x = u.coord("grid_longitude").points - y = u.coord("grid_latitude").points - x2d, y2d = np.meshgrid(x, y) - src_crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5) - tgt_crs = ccrs.OSGB() - xyz_tran = tgt_crs.transform_points(src_crs, x2d, y2d) - - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - - points = xyz_tran[..., 0].reshape(x2d.shape) - expected_x = AuxCoord( - points, - standard_name="projection_x_coordinate", - units="m", - coord_system=iris.coord_systems.OSGB(), - ) - self.assertEqual(ut.coord("projection_x_coordinate"), expected_x) - self.assertEqual(vt.coord("projection_x_coordinate"), expected_x) - - points = xyz_tran[..., 1].reshape(y2d.shape) - expected_y = AuxCoord( - points, - standard_name="projection_y_coordinate", - units="m", - coord_system=iris.coord_systems.OSGB(), - ) - self.assertEqual(ut.coord("projection_y_coordinate"), expected_y) - self.assertEqual(vt.coord("projection_y_coordinate"), expected_y) - - def test_new_coords_transposed(self): - u, v = self._uv_cubes_limited_extent() - # Transpose cubes so that cube is in xy order rather than the - # typical yx order of meshgrid. - u.transpose() - v.transpose() - x = u.coord("grid_longitude").points - y = u.coord("grid_latitude").points - x2d, y2d = np.meshgrid(x, y) - src_crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5) - tgt_crs = ccrs.OSGB() - xyz_tran = tgt_crs.transform_points(src_crs, x2d, y2d) - - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - - points = xyz_tran[..., 0].reshape(x2d.shape) - expected_x = AuxCoord( - points, - standard_name="projection_x_coordinate", - units="m", - coord_system=iris.coord_systems.OSGB(), - ) - self.assertEqual(ut.coord("projection_x_coordinate"), expected_x) - self.assertEqual(vt.coord("projection_x_coordinate"), expected_x) - - points = xyz_tran[..., 1].reshape(y2d.shape) - expected_y = AuxCoord( - points, - standard_name="projection_y_coordinate", - units="m", - coord_system=iris.coord_systems.OSGB(), - ) - self.assertEqual(ut.coord("projection_y_coordinate"), expected_y) - self.assertEqual(vt.coord("projection_y_coordinate"), expected_y) - # Check dim mapping for 2d coords is yx. - expected_dims = u.coord_dims("grid_latitude") + u.coord_dims( - "grid_longitude" - ) - self.assertEqual( - ut.coord_dims("projection_x_coordinate"), expected_dims - ) - self.assertEqual( - ut.coord_dims("projection_y_coordinate"), expected_dims - ) - self.assertEqual( - vt.coord_dims("projection_x_coordinate"), expected_dims - ) - self.assertEqual( - vt.coord_dims("projection_y_coordinate"), expected_dims - ) - - def test_orig_coords(self): - u, v = self._uv_cubes_limited_extent() - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - self.assertEqual(u.coord("grid_latitude"), ut.coord("grid_latitude")) - self.assertEqual(v.coord("grid_latitude"), vt.coord("grid_latitude")) - self.assertEqual(u.coord("grid_longitude"), ut.coord("grid_longitude")) - self.assertEqual(v.coord("grid_longitude"), vt.coord("grid_longitude")) - - def test_magnitude_preservation(self): - u, v = self._uv_cubes_limited_extent() - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - orig_sq_mag = u.data**2 + v.data**2 - res_sq_mag = ut.data**2 + vt.data**2 - self.assertArrayAllClose(orig_sq_mag, res_sq_mag, rtol=5e-4) - - def test_data_values(self): - u, v = self._uv_cubes_limited_extent() - # Slice out 4 points that lie in and outside OSGB extent. - u = u[1:3, 3:5] - v = v[1:3, 3:5] - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - # Values precalculated and checked. - expected_ut_data = np.array( - [[0.16285514, 0.35323639], [1.82650698, 2.62455840]] - ) - expected_vt_data = np.array( - [[19.88979966, 19.01921346], [19.88018847, 19.01424281]] - ) - # Compare u and v data values against previously calculated values. - self.assertArrayAllClose(ut.data, expected_ut_data, rtol=1e-5) - self.assertArrayAllClose(vt.data, expected_vt_data, rtol=1e-5) - - def test_nd_data(self): - u2d, y2d = self._uv_cubes_limited_extent() - u, v = uv_cubes_3d(u2d) - u = u[:, 1:3, 3:5] - v = v[:, 1:3, 3:5] - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - # Values precalculated and checked (as test_data_values above), - # then scaled by factor [1, 2, 3] along 0th dim (see uv_cubes_3d()). - expected_ut_data = np.array( - [[0.16285514, 0.35323639], [1.82650698, 2.62455840]] - ) - expected_vt_data = np.array( - [[19.88979966, 19.01921346], [19.88018847, 19.01424281]] - ) - factor = np.array([1, 2, 3]).reshape(3, 1, 1) - expected_ut_data = factor * expected_ut_data - expected_vt_data = factor * expected_vt_data - # Compare u and v data values against previously calculated values. - self.assertArrayAlmostEqual(ut.data, expected_ut_data) - self.assertArrayAlmostEqual(vt.data, expected_vt_data) - - def test_transposed(self): - # Test case where the coordinates are not ordered yx in the cube. - u, v = self._uv_cubes_limited_extent() - # Slice out 4 points that lie in and outside OSGB extent. - u = u[1:3, 3:5] - v = v[1:3, 3:5] - # Transpose cubes (in-place) - u.transpose() - v.transpose() - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - # Values precalculated and checked. - expected_ut_data = np.array( - [[0.16285514, 0.35323639], [1.82650698, 2.62455840]] - ).T - expected_vt_data = np.array( - [[19.88979966, 19.01921346], [19.88018847, 19.01424281]] - ).T - # Compare u and v data values against previously calculated values. - self.assertArrayAllClose(ut.data, expected_ut_data, rtol=1e-5) - self.assertArrayAllClose(vt.data, expected_vt_data, rtol=1e-5) - - -class TestMasking(tests.IrisTest): - def test_rotated_to_osgb(self): - # Rotated Pole data with large extent. - # A 'correct' answer is not known for this test; it is therefore - # written as a 'benchmark' style test - a change in behaviour will - # cause a test failure, requiring developers to approve/reject the - # new behaviour. - x = np.linspace(221.9, 301.1, 10) - y = np.linspace(-23.6, 24.8, 8) - u, v = uv_cubes(x, y) - ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) - - # Ensure cells with discrepancies in magnitude are masked. - self.assertTrue(ma.isMaskedArray(ut.data)) - self.assertTrue(ma.isMaskedArray(vt.data)) - - # Snapshot of mask with fixed tolerance of atol=2e-3 - expected_mask = np.array( - [ - [0, 0, 0, 1, 1, 1, 0, 0, 0, 1], - [0, 0, 0, 0, 1, 1, 1, 0, 1, 1], - [0, 0, 0, 0, 0, 1, 1, 1, 1, 1], - [0, 0, 0, 0, 1, 1, 1, 0, 0, 0], - [0, 0, 0, 1, 1, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 0, 1, 1, 1, 0, 0], - [0, 1, 0, 0, 0, 0, 1, 1, 1, 0], - ], - np.bool_, - ) - self.assertArrayEqual(expected_mask, ut.data.mask) - self.assertArrayEqual(expected_mask, vt.data.mask) - - # Check unmasked values have sufficiently small error in mag. - expected_mag = np.sqrt(u.data**2 + v.data**2) - # Use underlying data to ignore mask in calculation. - res_mag = np.sqrt(ut.data.data**2 + vt.data.data**2) - # Calculate percentage error (note there are no zero magnitudes - # so we can divide safely). - anom = 100.0 * np.abs(res_mag - expected_mag) / expected_mag - assert anom[~ut.data.mask].max() == pytest.approx(0.3227935) - - def test_rotated_to_unrotated(self): - # Suffiently accurate so that no mask is introduced. - u, v = uv_cubes() - ut, vt = rotate_winds(u, v, iris.coord_systems.GeogCS(6371229)) - self.assertFalse(ma.isMaskedArray(ut.data)) - self.assertFalse(ma.isMaskedArray(vt.data)) - - -class TestRoundTrip(tests.IrisTest): - def test_rotated_to_unrotated(self): - # Check ability to use 2d coords as input. - u, v = uv_cubes() - ut, vt = rotate_winds(u, v, iris.coord_systems.GeogCS(6371229)) - # Remove grid lat and lon, leaving 2d projection coords. - ut.remove_coord("grid_latitude") - vt.remove_coord("grid_latitude") - ut.remove_coord("grid_longitude") - vt.remove_coord("grid_longitude") - # Change back. - orig_cs = u.coord("grid_latitude").coord_system - res_u, res_v = rotate_winds(ut, vt, orig_cs) - # Check data values - limited accuracy due to numerical approx. - self.assertArrayAlmostEqual(res_u.data, u.data, decimal=3) - self.assertArrayAlmostEqual(res_v.data, v.data, decimal=3) - # Check coords locations. - x2d, y2d = np.meshgrid( - u.coord("grid_longitude").points, u.coord("grid_latitude").points - ) - # Shift longitude from 0 to 360 -> -180 to 180. - x2d = np.where(x2d > 180, x2d - 360, x2d) - res_x = res_u.coord( - "projection_x_coordinate", coord_system=orig_cs - ).points - res_y = res_u.coord( - "projection_y_coordinate", coord_system=orig_cs - ).points - self.assertArrayAlmostEqual(res_x, x2d) - self.assertArrayAlmostEqual(res_y, y2d) - res_x = res_v.coord( - "projection_x_coordinate", coord_system=orig_cs - ).points - res_y = res_v.coord( - "projection_y_coordinate", coord_system=orig_cs - ).points - self.assertArrayAlmostEqual(res_x, x2d) - self.assertArrayAlmostEqual(res_y, y2d) - - -class TestNonEarthPlanet(tests.IrisTest): - def test_non_earth_semimajor_axis(self): - u, v = uv_cubes() - u.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(123) - u.coord("grid_longitude").coord_system = iris.coord_systems.GeogCS(123) - v.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(123) - v.coord("grid_longitude").coord_system = iris.coord_systems.GeogCS(123) - other_cs = iris.coord_systems.RotatedGeogCS( - 0, 0, ellipsoid=iris.coord_systems.GeogCS(123) - ) - rotate_winds(u, v, other_cs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/geometry/__init__.py b/lib/iris/tests/unit/analysis/geometry/__init__.py deleted file mode 100644 index c57f5e246a..0000000000 --- a/lib/iris/tests/unit/analysis/geometry/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis.geometry` module.""" diff --git a/lib/iris/tests/unit/analysis/geometry/test__extract_relevant_cube_slice.py b/lib/iris/tests/unit/analysis/geometry/test__extract_relevant_cube_slice.py deleted file mode 100644 index 2509ac1a92..0000000000 --- a/lib/iris/tests/unit/analysis/geometry/test__extract_relevant_cube_slice.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.analysis.geometry._extract_relevant_cube_slice`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. - -import iris.tests as tests # isort:skip -import shapely.geometry - -from iris.analysis.geometry import _extract_relevant_cube_slice -import iris.tests.stock as stock - - -class Test(tests.IrisTest): - def test_polygon_smaller_than_cube(self): - cube = stock.lat_lon_cube() - cube.dim_coords[0].guess_bounds() - cube.dim_coords[1].guess_bounds() - geometry = shapely.geometry.box(-0.4, -0.4, 0.4, 0.4) - actual = _extract_relevant_cube_slice(cube, geometry) - target = ( - cube[1, 1], - cube[1, 1].coords(axis="x")[0], - cube[1, 1].coords(axis="y")[0], - (1, 1, 1, 1), - ) - self.assertEqual(target, actual) - - def test_polygon_larger_than_cube(self): - cube = stock.lat_lon_cube() - cube.dim_coords[0].guess_bounds() - cube.dim_coords[1].guess_bounds() - geometry = shapely.geometry.box(-0.6, -0.6, 0.6, 0.6) - actual = _extract_relevant_cube_slice(cube, geometry) - target = ( - cube[:, :3], - cube[:, :3].coords(axis="x")[0], - cube[:, :3].coords(axis="y")[0], - (0, 0, 2, 2), - ) - self.assertEqual(target, actual) - - def test_polygon_on_cube_boundary(self): - cube = stock.lat_lon_cube() - cube.dim_coords[0].guess_bounds() - cube.dim_coords[1].guess_bounds() - geometry = shapely.geometry.box(-0.5, -0.5, 0.5, 0.5) - actual = _extract_relevant_cube_slice(cube, geometry) - target = ( - cube[1, 1], - cube[1, 1].coords(axis="x")[0], - cube[1, 1].coords(axis="y")[0], - (1, 1, 1, 1), - ) - self.assertEqual(target, actual) - - def test_rotated_polygon_on_cube_boundary(self): - cube = stock.lat_lon_cube() - cube.dim_coords[0].guess_bounds() - cube.dim_coords[1].guess_bounds() - geometry = shapely.geometry.Polygon( - ((0.0, -0.5), (-0.5, 0.0), (0.0, 0.5), (0.5, 0.0)) - ) - actual = _extract_relevant_cube_slice(cube, geometry) - target = ( - cube[1, 1], - cube[1, 1].coords(axis="x")[0], - cube[1, 1].coords(axis="y")[0], - (1, 1, 1, 1), - ) - self.assertEqual(target, actual) - - def test_rotated_polygon_larger_than_cube_boundary(self): - cube = stock.lat_lon_cube() - cube.dim_coords[0].guess_bounds() - cube.dim_coords[1].guess_bounds() - geometry = shapely.geometry.Polygon( - ((0.0, -0.6), (-0.6, 0.0), (0.0, 0.6), (0.6, 0.0)) - ) - actual = _extract_relevant_cube_slice(cube, geometry) - target = ( - cube[:, :3], - cube[:, :3].coords(axis="x")[0], - cube[:, :3].coords(axis="y")[0], - (0, 0, 2, 2), - ) - self.assertEqual(target, actual) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py b/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py deleted file mode 100644 index 49e03a1174..0000000000 --- a/lib/iris/tests/unit/analysis/geometry/test_geometry_area_weights.py +++ /dev/null @@ -1,171 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.analysis.geometry.geometry_area_weights` -function. - - """ - -# Import iris.tests first so that some things can be initialised before -# importing anything else. - -import iris.tests as tests # isort:skip -import warnings - -import numpy as np -import shapely.geometry - -from iris.analysis.geometry import geometry_area_weights -from iris.coords import DimCoord -from iris.cube import Cube -import iris.tests.stock as stock - - -class Test(tests.IrisTest): - def setUp(self): - x_coord = DimCoord([1.0, 3.0], "longitude", bounds=[[0, 2], [2, 4]]) - y_coord = DimCoord([1.0, 3.0], "latitude", bounds=[[0, 2], [2, 4]]) - self.data = np.empty((4, 2, 2)) - dim_coords_and_dims = [(y_coord, (1,)), (x_coord, (2,))] - self.cube = Cube(self.data, dim_coords_and_dims=dim_coords_and_dims) - self.geometry = shapely.geometry.Polygon( - [(3, 3), (3, 50), (50, 50), (50, 3)] - ) - - def test_no_overlap(self): - geometry = shapely.geometry.Polygon([(4, 4), (4, 6), (6, 6), (6, 4)]) - weights = geometry_area_weights(self.cube, geometry) - self.assertEqual(np.sum(weights), 0) - - def test_overlap(self): - weights = geometry_area_weights(self.cube, self.geometry) - expected = np.repeat( - [[[0.0, 0.0], [0.0, 1.0]]], self.data.shape[0], axis=0 - ) - self.assertArrayEqual(weights, expected) - - def test_overlap_normalize(self): - weights = geometry_area_weights( - self.cube, self.geometry, normalize=True - ) - expected = np.repeat( - [[[0.0, 0.0], [0.0, 0.25]]], self.data.shape[0], axis=0 - ) - self.assertArrayEqual(weights, expected) - - @tests.skip_data - def test_distinct_xy(self): - cube = stock.simple_pp() - cube = cube[:4, :4] - lon = cube.coord("longitude") - lat = cube.coord("latitude") - lon.guess_bounds() - lat.guess_bounds() - from iris.util import regular_step - - quarter = abs(regular_step(lon) * regular_step(lat) * 0.25) - half = abs(regular_step(lon) * regular_step(lat) * 0.5) - minx = 3.7499990463256836 - maxx = 7.499998092651367 - miny = 84.99998474121094 - maxy = 89.99998474121094 - geometry = shapely.geometry.box(minx, miny, maxx, maxy) - weights = geometry_area_weights(cube, geometry) - target = np.array( - [ - [0, quarter, quarter, 0], - [0, half, half, 0], - [0, quarter, quarter, 0], - [0, 0, 0, 0], - ] - ) - self.assertTrue(np.allclose(weights, target)) - - @tests.skip_data - def test_distinct_xy_bounds(self): - # cases where geometry bnds are outside cube bnds correctly handled? - cube = stock.simple_pp() - cube = cube[:4, :4] - lon = cube.coord("longitude") - lat = cube.coord("latitude") - lon.guess_bounds() - lat.guess_bounds() - from iris.util import regular_step - - quarter = abs(regular_step(lon) * regular_step(lat) * 0.25) - half = abs(regular_step(lon) * regular_step(lat) * 0.5) - full = abs(regular_step(lon) * regular_step(lat)) - minx = 3.7499990463256836 - maxx = 13.12499619 - maxx_overshoot = 15.0 - miny = 84.99998474121094 - maxy = 89.99998474121094 - geometry = shapely.geometry.box(minx, miny, maxx, maxy) - geometry_overshoot = shapely.geometry.box( - minx, miny, maxx_overshoot, maxy - ) - weights = geometry_area_weights(cube, geometry) - weights_overshoot = geometry_area_weights(cube, geometry_overshoot) - target = np.array( - [ - [0, quarter, half, half], - [0, half, full, full], - [0, quarter, half, half], - [0, 0, 0, 0], - ] - ) - self.assertTrue(np.allclose(weights, target)) - self.assertTrue(np.allclose(weights_overshoot, target)) - - @tests.skip_data - def test_distinct_xy_bounds_pole(self): - # is UserWarning issued for out-of-bounds? results will be unexpected! - cube = stock.simple_pp() - cube = cube[:4, :4] - lon = cube.coord("longitude") - lat = cube.coord("latitude") - lon.guess_bounds() - lat.guess_bounds() - from iris.util import regular_step - - quarter = abs(regular_step(lon) * regular_step(lat) * 0.25) - half = abs(regular_step(lon) * regular_step(lat) * 0.5) - top_cell_half = abs(regular_step(lon) * (90 - lat.bounds[0, 1]) * 0.5) - minx = 3.7499990463256836 - maxx = 7.499998092651367 - miny = 84.99998474121094 - maxy = 99.99998474121094 - geometry = shapely.geometry.box(minx, miny, maxx, maxy) - # see http://stackoverflow.com/a/3892301 to assert warnings - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") # always trigger all warnings - weights = geometry_area_weights(cube, geometry) - self.assertEqual( - str(w[-1].message), - "The geometry exceeds the " - "cube's y dimension at the upper end.", - ) - self.assertTrue(issubclass(w[-1].category, UserWarning)) - target = np.array( - [ - [0, top_cell_half, top_cell_half, 0], - [0, half, half, 0], - [0, quarter, quarter, 0], - [0, 0, 0, 0], - ] - ) - self.assertTrue(np.allclose(weights, target)) - - def test_shared_xy(self): - cube = stock.track_1d() - geometry = shapely.geometry.box(1, 4, 3.5, 7) - weights = geometry_area_weights(cube, geometry) - target = np.array([0, 0, 2, 0.5, 0, 0, 0, 0, 0, 0, 0]) - self.assertTrue(np.allclose(weights, target)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/interpolation/__init__.py b/lib/iris/tests/unit/analysis/interpolation/__init__.py deleted file mode 100644 index 3825dacda3..0000000000 --- a/lib/iris/tests/unit/analysis/interpolation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis._interpolation` package.""" diff --git a/lib/iris/tests/unit/analysis/interpolation/test_RectilinearInterpolator.py b/lib/iris/tests/unit/analysis/interpolation/test_RectilinearInterpolator.py deleted file mode 100644 index 6c3999a6f4..0000000000 --- a/lib/iris/tests/unit/analysis/interpolation/test_RectilinearInterpolator.py +++ /dev/null @@ -1,612 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :class:`iris.analysis._interpolation.RectilinearInterpolator`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import datetime - -import numpy as np - -import iris -from iris._lazy_data import as_lazy_data -from iris.analysis._interpolation import RectilinearInterpolator -import iris.coords -import iris.cube -import iris.exceptions -import iris.tests.stock as stock - -LINEAR = "linear" -NEAREST = "nearest" - -EXTRAPOLATE = "extrapolate" - - -class ThreeDimCube(tests.IrisTest): - def setUp(self): - cube = stock.simple_3d_w_multidim_coords() - cube.add_aux_coord( - iris.coords.DimCoord(np.arange(2), "height", units="1"), 0 - ) - cube.add_dim_coord( - iris.coords.DimCoord(np.arange(3), "latitude", units="1"), 1 - ) - cube.add_dim_coord( - iris.coords.DimCoord(np.arange(4), "longitude", units="1"), 2 - ) - self.data = np.arange(24).reshape(2, 3, 4).astype(np.float32) - cube.data = self.data - self.cube = cube - - -class Test___init__(ThreeDimCube): - def test_properties(self): - interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, EXTRAPOLATE - ) - - self.assertEqual(interpolator.method, LINEAR) - self.assertEqual(interpolator.extrapolation_mode, EXTRAPOLATE) - - # Access to cube property of the RectilinearInterpolator instance. - self.assertEqual(interpolator.cube, self.cube) - - # Access to the resulting coordinate which we are interpolating over. - self.assertEqual(interpolator.coords, [self.cube.coord("latitude")]) - - -class Test___init____validation(ThreeDimCube): - def test_interpolator_overspecified(self): - # Over specification by means of interpolating over two coordinates - # mapped to the same dimension. - msg = ( - "Coordinates repeat a data dimension - " - "the interpolation would be over-specified" - ) - with self.assertRaisesRegex(ValueError, msg): - RectilinearInterpolator( - self.cube, ["wibble", "height"], LINEAR, EXTRAPOLATE - ) - - def test_interpolator_overspecified_scalar(self): - # Over specification by means of interpolating over one dimension - # coordinate and a scalar coordinate (not mapped to a dimension). - self.cube.add_aux_coord( - iris.coords.AuxCoord(1, long_name="scalar"), None - ) - - msg = ( - "Coordinates repeat a data dimension - " - "the interpolation would be over-specified" - ) - with self.assertRaisesRegex(ValueError, msg): - RectilinearInterpolator( - self.cube, ["wibble", "scalar"], LINEAR, EXTRAPOLATE - ) - - def test_interpolate__decreasing(self): - def check_expected(): - # Check a simple case is equivalent to extracting the first row. - self.interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, EXTRAPOLATE - ) - expected = self.data[:, 0:1, :] - result = self.interpolator([[0]]) - self.assertArrayEqual(result.data, expected) - - # Check with normal cube. - check_expected() - # Check same result from a cube inverted in the latitude dimension. - self.cube = self.cube[:, ::-1] - check_expected() - - def test_interpolate_non_monotonic(self): - self.cube.add_aux_coord( - iris.coords.AuxCoord([0, 3, 2], long_name="non-monotonic"), 1 - ) - msg = ( - "Cannot interpolate over the non-monotonic coordinate " - "non-monotonic." - ) - with self.assertRaisesRegex(ValueError, msg): - RectilinearInterpolator( - self.cube, ["non-monotonic"], LINEAR, EXTRAPOLATE - ) - - -class Test___call___1D(ThreeDimCube): - def setUp(self): - ThreeDimCube.setUp(self) - self.interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, EXTRAPOLATE - ) - - def test_interpolate_bad_coord_name(self): - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): - RectilinearInterpolator( - self.cube, ["doesnt exist"], LINEAR, EXTRAPOLATE - ) - - def test_interpolate_data_single(self): - # Single sample point. - result = self.interpolator([[1.5]]) - expected = self.data[:, 1:, :].mean(axis=1).reshape(2, 1, 4) - self.assertArrayEqual(result.data, expected) - - foo_res = result.coord("foo").points - bar_res = result.coord("bar").points - expected_foo = ( - self.cube[:, 1:, :].coord("foo").points.mean(axis=0).reshape(1, 4) - ) - expected_bar = ( - self.cube[:, 1:, :].coord("bar").points.mean(axis=0).reshape(1, 4) - ) - - self.assertArrayEqual(foo_res, expected_foo) - self.assertArrayEqual(bar_res, expected_bar) - - def test_interpolate_data_multiple(self): - # Multiple sample points for a single coordinate (these points are not - # interpolated). - result = self.interpolator([[1, 2]]) - self.assertArrayEqual(result.data, self.data[:, 1:3, :]) - - foo_res = result.coord("foo").points - bar_res = result.coord("bar").points - expected_foo = self.cube[:, 1:, :].coord("foo").points - expected_bar = self.cube[:, 1:, :].coord("bar").points - - self.assertArrayEqual(foo_res, expected_foo) - self.assertArrayEqual(bar_res, expected_bar) - - def test_interpolate_data_linear_extrapolation(self): - # Sample point outside the coordinate range. - result = self.interpolator([[-1]]) - expected = self.data[:, 0:1] - (self.data[:, 1:2] - self.data[:, 0:1]) - self.assertArrayEqual(result.data, expected) - - def _extrapolation_dtype(self, dtype): - self.cube.data = self.cube.data.astype(dtype) - interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, extrapolation_mode="nan" - ) - result = interpolator([[-1]]) - self.assertTrue(np.all(np.isnan(result.data))) - - def test_extrapolation_nan_float32(self): - # Ensure np.nan in a float32 array results. - self._extrapolation_dtype(np.float32) - - def test_extrapolation_nan_float64(self): - # Ensure np.nan in a float64 array results. - self._extrapolation_dtype(np.float64) - - def test_interpolate_data_error_on_extrapolation(self): - msg = "One of the requested xi is out of bounds in dimension 0" - interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, extrapolation_mode="error" - ) - with self.assertRaisesRegex(ValueError, msg): - interpolator([[-1]]) - - def test_interpolate_data_unsupported_extrapolation(self): - msg = "Extrapolation mode 'unsupported' not supported" - with self.assertRaisesRegex(ValueError, msg): - RectilinearInterpolator( - self.cube, - ["latitude"], - LINEAR, - extrapolation_mode="unsupported", - ) - - def test_multi_points_array(self): - # Providing a multidimensional sample points for a 1D interpolation. - # i.e. points given for two coordinates where there are only one - # specified. - msg = "Expected sample points for 1 coordinates, got 2." - with self.assertRaisesRegex(ValueError, msg): - self.interpolator([[1, 2], [1]]) - - def test_interpolate_data_dtype_casting(self): - data = self.data.astype(int) - self.cube.data = data - self.interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, EXTRAPOLATE - ) - result = self.interpolator([[0.125]]) - self.assertEqual(result.data.dtype, np.float64) - - def test_default_collapse_scalar(self): - interpolator = RectilinearInterpolator( - self.cube, ["wibble"], LINEAR, EXTRAPOLATE - ) - result = interpolator([0]) - self.assertEqual(result.shape, (3, 4)) - - def test_collapse_scalar(self): - interpolator = RectilinearInterpolator( - self.cube, ["wibble"], LINEAR, EXTRAPOLATE - ) - result = interpolator([0], collapse_scalar=True) - self.assertEqual(result.shape, (3, 4)) - - def test_no_collapse_scalar(self): - interpolator = RectilinearInterpolator( - self.cube, ["wibble"], LINEAR, EXTRAPOLATE - ) - result = interpolator([0], collapse_scalar=False) - self.assertEqual(result.shape, (1, 3, 4)) - - def test_unsorted_datadim_mapping(self): - # Currently unsorted data dimension mapping is not supported as the - # indexing is not yet clever enough to remap the interpolated - # coordinates. - self.cube.transpose((0, 2, 1)) - interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, EXTRAPOLATE - ) - msg = "Currently only increasing data_dims is supported." - with self.assertRaisesRegex(NotImplementedError, msg): - interpolator([0]) - - -class Test___call___1D_circular(ThreeDimCube): - # Note: all these test data interpolation. - def setUp(self): - ThreeDimCube.setUp(self) - self.cube.coord("longitude")._points = np.linspace( - 0, 360, 4, endpoint=False - ) - self.cube.coord("longitude").circular = True - self.cube.coord("longitude").units = "degrees" - self.interpolator = RectilinearInterpolator( - self.cube, ["longitude"], LINEAR, extrapolation_mode="nan" - ) - self.cube_reverselons = self.cube[:, :, ::-1] - self.interpolator_reverselons = RectilinearInterpolator( - self.cube_reverselons, - ["longitude"], - LINEAR, - extrapolation_mode="nan", - ) - - self.testpoints_fully_wrapped = ([[180, 270]], [[-180, -90]]) - self.testpoints_partially_wrapped = ([[180, 90]], [[-180, 90]]) - self.testpoints_fully_wrapped_twice = ( - [np.linspace(-360, 360, 100)], - [(np.linspace(-360, 360, 100) + 360) % 360], - ) - - def test_fully_wrapped(self): - points, points_wrapped = self.testpoints_fully_wrapped - expected = self.interpolator(points) - result = self.interpolator(points_wrapped) - self.assertArrayEqual(expected.data, result.data) - - def test_fully_wrapped_reversed_mainpoints(self): - points, _ = self.testpoints_fully_wrapped - expected = self.interpolator(points) - result = self.interpolator_reverselons(points) - self.assertArrayEqual(expected.data, result.data) - - def test_fully_wrapped_reversed_testpoints(self): - _, points = self.testpoints_fully_wrapped - expected = self.interpolator(points) - result = self.interpolator_reverselons(points) - self.assertArrayEqual(expected.data, result.data) - - def test_partially_wrapped(self): - points, points_wrapped = self.testpoints_partially_wrapped - expected = self.interpolator(points) - result = self.interpolator(points_wrapped) - self.assertArrayEqual(expected.data, result.data) - - def test_partially_wrapped_reversed_mainpoints(self): - points, _ = self.testpoints_partially_wrapped - expected = self.interpolator(points) - result = self.interpolator_reverselons(points) - self.assertArrayEqual(expected.data, result.data) - - def test_partially_wrapped_reversed_testpoints(self): - points, _ = self.testpoints_partially_wrapped - expected = self.interpolator(points) - result = self.interpolator_reverselons(points) - self.assertArrayEqual(expected.data, result.data) - - def test_fully_wrapped_twice(self): - xs, xs_not_wrapped = self.testpoints_fully_wrapped_twice - expected = self.interpolator(xs) - result = self.interpolator(xs_not_wrapped) - self.assertArrayEqual(expected.data, result.data) - - def test_fully_wrapped_twice_reversed_mainpoints(self): - _, points = self.testpoints_fully_wrapped_twice - expected = self.interpolator(points) - result = self.interpolator_reverselons(points) - self.assertArrayEqual(expected.data, result.data) - - def test_fully_wrapped_not_circular(self): - cube = stock.lat_lon_cube() - new_long = cube.coord("longitude").copy( - cube.coord("longitude").points + 710 - ) - cube.remove_coord("longitude") - cube.add_dim_coord(new_long, 1) - - interpolator = RectilinearInterpolator( - cube, ["longitude"], LINEAR, EXTRAPOLATE - ) - res = interpolator([-10]) - self.assertArrayEqual(res.data, cube[:, 1].data) - - -class Test___call___1D_singlelendim(ThreeDimCube): - def setUp(self): - """ - thingness / (1) (wibble: 2; latitude: 1) - Dimension coordinates: - wibble x - - latitude - x - Auxiliary coordinates: - height x - - bar - x - foo - x - Scalar coordinates: - longitude: 0 - """ - ThreeDimCube.setUp(self) - self.cube = self.cube[:, 0:1, 0] - self.interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, EXTRAPOLATE - ) - - def test_interpolate_data_linear_extrapolation(self): - # Linear extrapolation of a single valued element. - result = self.interpolator([[1001]]) - self.assertArrayEqual(result.data, self.cube.data) - - def test_interpolate_data_nan_extrapolation(self): - interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, extrapolation_mode="nan" - ) - result = interpolator([[1001]]) - self.assertTrue(np.all(np.isnan(result.data))) - - def test_interpolate_data_nan_extrapolation_not_needed(self): - # No extrapolation for a single length dimension. - interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, extrapolation_mode="nan" - ) - result = interpolator([[0]]) - self.assertArrayEqual(result.data, self.cube.data) - - -class Test___call___masked(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_4d_with_hybrid_height() - mask = np.isnan(self.cube.data) - mask[::3, ::3] = True - self.cube.data = np.ma.masked_array(self.cube.data, mask=mask) - - def test_orthogonal_cube(self): - interpolator = RectilinearInterpolator( - self.cube, ["grid_latitude"], LINEAR, EXTRAPOLATE - ) - result_cube = interpolator([1]) - - # Explicit mask comparison to ensure mask retention. - # Masked value input - self.assertTrue(self.cube.data.mask[0, 0, 0, 0]) - # Mask retention on output - self.assertTrue(result_cube.data.mask[0, 0, 0]) - - self.assertCML( - result_cube, - ( - "experimental", - "analysis", - "interpolate", - "LinearInterpolator", - "orthogonal_cube_with_factory.cml", - ), - ) - - -class Test___call___2D(ThreeDimCube): - def setUp(self): - ThreeDimCube.setUp(self) - self.interpolator = RectilinearInterpolator( - self.cube, ["latitude", "longitude"], LINEAR, EXTRAPOLATE - ) - - def test_interpolate_data(self): - result = self.interpolator([[1, 2], [2]]) - expected = self.data[:, 1:3, 2:3] - self.assertArrayEqual(result.data, expected) - - index = (slice(None), slice(1, 3, 1), slice(2, 3, 1)) - for coord in self.cube.coords(): - coord_res = result.coord(coord).points - coord_expected = self.cube[index].coord(coord).points - - self.assertArrayEqual(coord_res, coord_expected) - - def test_orthogonal_points(self): - result = self.interpolator([[1, 2], [1, 2]]) - expected = self.data[:, 1:3, 1:3] - self.assertArrayEqual(result.data, expected) - - index = (slice(None), slice(1, 3, 1), slice(1, 3, 1)) - for coord in self.cube.coords(): - coord_res = result.coord(coord).points - coord_expected = self.cube[index].coord(coord).points - - self.assertArrayEqual(coord_res, coord_expected) - - def test_multi_dim_coord_interpolation(self): - msg = "Interpolation coords must be 1-d for rectilinear interpolation." - with self.assertRaisesRegex(ValueError, msg): - interpolator = RectilinearInterpolator( - self.cube, ["foo", "bar"], LINEAR, EXTRAPOLATE - ) - interpolator([[15], [10]]) - - -class Test___call___2D_non_contiguous(ThreeDimCube): - def setUp(self): - ThreeDimCube.setUp(self) - coords = ["height", "longitude"] - self.interpolator = RectilinearInterpolator( - self.cube, coords, LINEAR, EXTRAPOLATE - ) - - def test_interpolate_data_multiple(self): - result = self.interpolator([[1], [1, 2]]) - expected = self.data[1:2, :, 1:3] - self.assertArrayEqual(result.data, expected) - - index = (slice(1, 2), slice(None), slice(1, 3, 1)) - for coord in self.cube.coords(): - coord_res = result.coord(coord).points - coord_expected = self.cube[index].coord(coord).points - - self.assertArrayEqual(coord_res, coord_expected) - - def test_orthogonal_cube(self): - result_cube = self.interpolator( - [np.int64([0, 1, 1]), np.int32([0, 1])] - ) - result_path = ( - "experimental", - "analysis", - "interpolate", - "LinearInterpolator", - "basic_orthogonal_cube.cml", - ) - self.assertCMLApproxData(result_cube, result_path) - self.assertEqual(result_cube.coord("longitude").dtype, np.int32) - self.assertEqual(result_cube.coord("height").dtype, np.int64) - - def test_orthogonal_cube_squash(self): - result_cube = self.interpolator([np.int64(0), np.int32([0, 1])]) - result_path = ( - "experimental", - "analysis", - "interpolate", - "LinearInterpolator", - "orthogonal_cube_1d_squashed.cml", - ) - self.assertCMLApproxData(result_cube, result_path) - self.assertEqual(result_cube.coord("longitude").dtype, np.int32) - self.assertEqual(result_cube.coord("height").dtype, np.int64) - - non_collapsed_cube = self.interpolator( - [[np.int64(0)], np.int32([0, 1])], collapse_scalar=False - ) - result_path = ( - "experimental", - "analysis", - "interpolate", - "LinearInterpolator", - "orthogonal_cube_1d_squashed_2.cml", - ) - self.assertCML(non_collapsed_cube[0, ...], result_path) - self.assertCML(result_cube, result_path) - self.assertEqual(result_cube, non_collapsed_cube[0, ...]) - - -class Test___call___lazy_data(ThreeDimCube): - def test_src_cube_data_loaded(self): - # RectilinearInterpolator operates using a snapshot of the source cube. - # If the source cube has lazy data when the interpolator is - # instantiated we want to make sure the source cube's data is - # loaded as a consequence of interpolation to avoid the risk - # of loading it again and again. - - # Modify self.cube to have lazy data. - self.cube.data = as_lazy_data(self.data) - self.assertTrue(self.cube.has_lazy_data()) - - # Perform interpolation and check the data has been loaded. - interpolator = RectilinearInterpolator( - self.cube, ["latitude"], LINEAR, EXTRAPOLATE - ) - interpolator([[1.5]]) - self.assertFalse(self.cube.has_lazy_data()) - - -class Test___call___time(tests.IrisTest): - def interpolator(self, method=LINEAR): - data = np.arange(12).reshape(4, 3) - cube = iris.cube.Cube(data) - time_coord = iris.coords.DimCoord( - np.arange(0.0, 48.0, 12.0), "time", units="hours since epoch" - ) - height_coord = iris.coords.DimCoord( - np.arange(3), "altitude", units="m" - ) - cube.add_dim_coord(time_coord, 0) - cube.add_dim_coord(height_coord, 1) - return RectilinearInterpolator(cube, ["time"], method, EXTRAPOLATE) - - def test_number_at_existing_value(self): - interpolator = self.interpolator() - result = interpolator([12]) - self.assertArrayEqual(result.data, [3, 4, 5]) - - def test_datetime_at_existing_value(self): - interpolator = self.interpolator() - result = interpolator([datetime.datetime(1970, 1, 1, 12)]) - self.assertArrayEqual(result.data, [3, 4, 5]) - - def test_datetime_between_existing_values(self): - interpolator = self.interpolator() - result = interpolator([datetime.datetime(1970, 1, 1, 18)]) - self.assertArrayEqual(result.data, [4.5, 5.5, 6.5]) - - def test_mixed_numbers_and_datetimes(self): - interpolator = self.interpolator() - result = interpolator( - [ - ( - 12, - datetime.datetime(1970, 1, 1, 18), - datetime.datetime(1970, 1, 2, 0), - 26, - ) - ] - ) - self.assertEqual(result.coord("time").points.dtype, float) - self.assertArrayEqual( - result.data, - [[3, 4, 5], [4.5, 5.5, 6.5], [6, 7, 8], [6.5, 7.5, 8.5]], - ) - - def test_mixed_numbers_and_datetimes_nearest(self): - interpolator = self.interpolator(NEAREST) - result = interpolator( - [ - ( - 12, - datetime.datetime(1970, 1, 1, 18), - datetime.datetime(1970, 1, 2, 0), - 26, - ) - ] - ) - self.assertEqual(result.coord("time").points.dtype, float) - self.assertArrayEqual( - result.data, [[3, 4, 5], [3, 4, 5], [6, 7, 8], [6, 7, 8]] - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/interpolation/test_get_xy_dim_coords.py b/lib/iris/tests/unit/analysis/interpolation/test_get_xy_dim_coords.py deleted file mode 100644 index 54e54bc304..0000000000 --- a/lib/iris/tests/unit/analysis/interpolation/test_get_xy_dim_coords.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.analysis._interpolation.get_xy_dim_coords`. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -import copy - -import numpy as np - -from iris.analysis._interpolation import get_xy_dim_coords -import iris.coord_systems -import iris.coords -import iris.experimental.regrid -import iris.tests.stock - - -class TestGetXYCoords(tests.IrisTest): - @tests.skip_data - def test_grid_lat_lon(self): - cube = iris.tests.stock.realistic_4d() - x, y = get_xy_dim_coords(cube) - self.assertIs(x, cube.coord("grid_longitude")) - self.assertIs(y, cube.coord("grid_latitude")) - - def test_lat_lon(self): - cube = iris.tests.stock.lat_lon_cube() - x, y = get_xy_dim_coords(cube) - self.assertIs(x, cube.coord("longitude")) - self.assertIs(y, cube.coord("latitude")) - - def test_projection_coords(self): - cube = iris.tests.stock.lat_lon_cube() - cube.coord("longitude").rename("projection_x_coordinate") - cube.coord("latitude").rename("projection_y_coordinate") - x, y = get_xy_dim_coords(cube) - self.assertIs(x, cube.coord("projection_x_coordinate")) - self.assertIs(y, cube.coord("projection_y_coordinate")) - - @tests.skip_data - def test_missing_x_coord(self): - cube = iris.tests.stock.realistic_4d() - cube.remove_coord("grid_longitude") - with self.assertRaises(ValueError): - get_xy_dim_coords(cube) - - @tests.skip_data - def test_missing_y_coord(self): - cube = iris.tests.stock.realistic_4d() - cube.remove_coord("grid_latitude") - with self.assertRaises(ValueError): - get_xy_dim_coords(cube) - - @tests.skip_data - def test_multiple_coords(self): - cube = iris.tests.stock.realistic_4d() - cs = iris.coord_systems.GeogCS(6371229) - time_coord = cube.coord("time") - time_dims = cube.coord_dims(time_coord) - lat_coord = iris.coords.DimCoord( - np.arange(time_coord.shape[0]), - standard_name="latitude", - units="degrees", - coord_system=cs, - ) - cube.remove_coord(time_coord) - cube.add_dim_coord(lat_coord, time_dims) - model_level_coord = cube.coord("model_level_number") - model_level_dims = cube.coord_dims(model_level_coord) - lon_coord = iris.coords.DimCoord( - np.arange(model_level_coord.shape[0]), - standard_name="longitude", - units="degrees", - coord_system=cs, - ) - cube.remove_coord(model_level_coord) - cube.add_dim_coord(lon_coord, model_level_dims) - - with self.assertRaises(ValueError): - get_xy_dim_coords(cube) - - cube.remove_coord("grid_latitude") - cube.remove_coord("grid_longitude") - - x, y = get_xy_dim_coords(cube) - self.assertIs(x, lon_coord) - self.assertIs(y, lat_coord) - - def test_no_coordsystem(self): - cube = iris.tests.stock.lat_lon_cube() - for coord in cube.coords(): - coord.coord_system = None - x, y = get_xy_dim_coords(cube) - self.assertIs(x, cube.coord("longitude")) - self.assertIs(y, cube.coord("latitude")) - - def test_one_coordsystem(self): - cube = iris.tests.stock.lat_lon_cube() - cube.coord("longitude").coord_system = None - with self.assertRaises(ValueError): - get_xy_dim_coords(cube) - - def test_different_coordsystem(self): - cube = iris.tests.stock.lat_lon_cube() - - lat_cs = copy.copy(cube.coord("latitude").coord_system) - lat_cs.semi_major_axis = 7000000 - cube.coord("latitude").coord_system = lat_cs - - lon_cs = copy.copy(cube.coord("longitude").coord_system) - lon_cs.semi_major_axis = 7000001 - cube.coord("longitude").coord_system = lon_cs - - with self.assertRaises(ValueError): - get_xy_dim_coords(cube) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/__init__.py b/lib/iris/tests/unit/analysis/maths/__init__.py deleted file mode 100644 index 558a6fccfe..0000000000 --- a/lib/iris/tests/unit/analysis/maths/__init__.py +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis.maths` module.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from abc import ABCMeta, abstractmethod -import operator - -import dask.array as da -import numpy as np -from numpy import ma - -from iris.analysis import MEAN -from iris.analysis.maths import add -from iris.coords import DimCoord -from iris.cube import Cube -import iris.tests.stock as stock - - -class CubeArithmeticBroadcastingTestMixin(metaclass=ABCMeta): - # A framework for testing the broadcasting behaviour of the various cube - # arithmetic operations. (A test for each operation inherits this). - @property - @abstractmethod - def data_op(self): - # Define an operator to be called, I.E. 'operator.xx'. - pass - - @property - @abstractmethod - def cube_func(self): - # Define an iris arithmetic function to be called - # I.E. 'iris.analysis.maths.xx'. - pass - - def _base_testcube(self, include_derived=False): - if include_derived: - self.cube = stock.realistic_4d() - else: - self.cube = stock.realistic_4d_no_derived() - self.cube_xy_dimcoords = ["grid_latitude", "grid_longitude"] - return self.cube - - def _meshcube_collapsesafe(self, cube, coords): - # Return the cube, or if need be a modified copy, which can be safely - # collapsed over the given coords. - # This is needed for mesh-cubes, because the mesh coords have - # bounds which are not understood by the standard 'collapse' operation. - # TODO: possibly replace with a future 'safe mesh collapse' operation. - # cf. https://github.com/SciTools/iris/issues/4672 - result = cube - if cube.mesh is not None: - collapse_dims = set() - for co in coords: - # Each must produce a single coord, with a single dim - (dim,) = cube.coord_dims(co) - collapse_dims.add(dim) - i_meshdim = cube.mesh_dim() - if i_meshdim in collapse_dims: - # Make a copy with all mesh coords replaced by their AuxCoord - # equivalents. A simple slicing will do that. - slices = [slice(None)] * cube.ndim - slices[i_meshdim] = slice(0, None) - result = cube[tuple(slices)] - # Finally, **remove bounds** from all the former AuxCoords. - # This is what enables them to be successfully collapsed. - for meshco in cube.coords(mesh_coords=True): - # Note: select new coord by name, as getting the AuxCoord - # which "matches" a MeshCoord is not possible. - result.coord(meshco.name()).bounds = None - - return result - - def test_transposed(self): - cube = self._base_testcube() - other = cube.copy() - other.transpose() - res = self.cube_func(cube, other) - self.assertCML(res, checksum=False) - expected_data = self.data_op(cube.data, other.data.T) - self.assertArrayEqual(res.data, expected_data) - - def test_collapse_zeroth_dim(self): - cube = self._base_testcube() - other = cube.collapsed("time", MEAN) - res = self.cube_func(cube, other) - self.assertCML(res, checksum=False) - # No modification to other.data is needed as numpy broadcasting - # should be sufficient. - expected_data = self.data_op(cube.data, other.data) - # Use assertMaskedArrayEqual as collapsing with MEAN results - # in a cube with a masked data array. - self.assertMaskedArrayEqual(res.data, expected_data) - - def test_collapse_all_dims(self): - cube = self._base_testcube() - collapse_coords = cube.coords(dim_coords=True) - other = self._meshcube_collapsesafe(cube, collapse_coords) - other = other.collapsed(collapse_coords, MEAN) - res = self.cube_func(cube, other) - self.assertCML(res, checksum=False) - # No modification to other.data is needed as numpy broadcasting - # should be sufficient. - expected_data = self.data_op(cube.data, other.data) - # Use assertArrayEqual rather than assertMaskedArrayEqual as - # collapsing all dims does not result in a masked array. - self.assertArrayEqual(res.data, expected_data) - - def test_collapse_last_dims(self): - cube = self._base_testcube() - # Collapse : by 'last' we mean the X+Y ones... - other = self._meshcube_collapsesafe(cube, self.cube_xy_dimcoords) - other = other.collapsed(self.cube_xy_dimcoords, MEAN) - res = self.cube_func(cube, other) - self.assertCML(res, checksum=False) - # Transpose the dimensions in self.cube that have been collapsed in - # other to lie at the front, thereby enabling numpy broadcasting to - # function when applying data operator. Finish by transposing back - # again to restore order. - n_xydims = len(self.cube_xy_dimcoords) - cube_dims = tuple(np.arange(cube.ndim)) - transpose_xy_back2front = cube_dims[-n_xydims:] + cube_dims[:-n_xydims] - transpose_xy_front2back = cube_dims[n_xydims:] + cube_dims[:n_xydims] - expected_data = self.data_op( - cube.data.transpose(transpose_xy_back2front), other.data - ).transpose(transpose_xy_front2back) - # Confirm result content is as expected - self.assertMaskedArrayEqual(res.data, expected_data) - - def test_collapse_middle_dim(self): - cube = self._base_testcube() - other = cube.collapsed(["model_level_number"], MEAN) - res = self.cube_func(cube, other) - self.assertCML(res, checksum=False) - # Add the collapsed dimension back in via np.newaxis to enable - # numpy broadcasting to function. - expected_data = self.data_op(cube.data, other.data[:, np.newaxis, ...]) - self.assertMaskedArrayEqual(res.data, expected_data) - - def test_slice(self): - cube = self._base_testcube() - for dim in range(cube.ndim): - keys = [slice(None)] * cube.ndim - keys[dim] = 3 - other = cube[tuple(keys)] - - # A special "cheat" for mesh cases... - # When a mesh dimension is indexed, this produces scalar versions - # of the mesh-coords, which don't match to the originals. - # FOR NOW: remove those, for a result matching the other ones. - # TODO: coord equivalence may need reviewing, either for cube - # maths or for coord equivalance generally. - # cf. https://github.com/SciTools/iris/issues/4671 - if cube.mesh and dim == cube.mesh_dim(): - for co in cube.coords(mesh_coords=True): - other.remove_coord(co.name()) - - res = self.cube_func(cube, other) - - # NOTE: only one testfile : any dim collapsed gives SAME result - self.assertCML(res, checksum=False) - # Add the collapsed dimension back in via np.newaxis to enable - # numpy broadcasting to function. - keys[dim] = np.newaxis - expected_data = self.data_op(cube.data, other.data[tuple(keys)]) - msg = "Problem broadcasting cubes when sliced on dimension {}." - self.assertArrayEqual( - res.data, expected_data, err_msg=msg.format(dim) - ) - - -class MathsAddOperationMixin: - # Test everything with the 'add' operation. - @property - def data_op(self): - return operator.add - - @property - def cube_func(self): - return add - - -class CubeArithmeticMaskingTestMixin(metaclass=ABCMeta): - # A framework for testing the mask handling behaviour of the various cube - # arithmetic operations. (A test for each operation inherits this). - @property - @abstractmethod - def data_op(self): - # Define an operator to be called, I.E. 'operator.xx'. - pass - - @property - @abstractmethod - def cube_func(self): - # Define an iris arithmetic function to be called - # I.E. 'iris.analysis.maths.xx'. - pass - - def _test_partial_mask(self, in_place, second_lazy=False): - # Helper method for masked data tests. - dat_a = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 0, 1, 0]) - dat_b = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 1, 0, 0]) - - if second_lazy: - cube_b = Cube(da.from_array(dat_b)) - else: - cube_b = Cube(dat_b) - - cube_a = Cube(dat_a) - - com = self.data_op(dat_a, dat_b) - res = self.cube_func(cube_a, cube_b, in_place=in_place) - - return com, res, cube_a - - def test_partial_mask_in_place(self): - # Cube in_place arithmetic operation. - com, res, orig_cube = self._test_partial_mask(True) - - self.assertMaskedArrayEqual(com, res.data, strict=True) - self.assertIs(res, orig_cube) - - def test_partial_mask_second_lazy_in_place(self): - # Only second cube has lazy data. - com, res, orig_cube = self._test_partial_mask(True, second_lazy=True) - self.assertMaskedArrayEqual(com, res.data, strict=True) - self.assertIs(res, orig_cube) - - def test_partial_mask_not_in_place(self): - # Cube arithmetic not an in_place operation. - com, res, orig_cube = self._test_partial_mask(False) - - self.assertMaskedArrayEqual(com, res.data, strict=True) - self.assertIsNot(res, orig_cube) - - def test_partial_mask_second_lazy_not_in_place(self): - # Only second cube has lazy data. - com, res, orig_cube = self._test_partial_mask(False, second_lazy=True) - self.assertMaskedArrayEqual(com, res.data, strict=True) - self.assertIsNot(res, orig_cube) - - def test_in_place_introduces_mask(self): - # If second cube is masked, result should also be masked. - data1 = np.arange(4, dtype=float) - data2 = ma.array([2.0, 2.0, 2.0, 2.0], mask=[1, 1, 0, 0]) - cube1 = Cube(data1) - cube2 = Cube(data2) - - com = self.data_op(data1, data2) - res = self.cube_func(cube1, cube2, in_place=True) - - self.assertMaskedArrayEqual(com, res.data, strict=True) - self.assertIs(res, cube1) - - -class CubeArithmeticCoordsTest(tests.IrisTest): - # This class sets up pairs of cubes to test iris' ability to reject - # arithmetic operations on coordinates which do not match. - def SetUpNonMatching(self): - # On this cube pair, the coordinates to perform operations on do not - # match in either points array or name. - data = np.zeros((3, 4)) - a = DimCoord([1, 2, 3], long_name="a") - b = DimCoord([1, 2, 3, 4], long_name="b") - x = DimCoord([4, 5, 6], long_name="x") - y = DimCoord([5, 6, 7, 8], long_name="y") - - nomatch1 = Cube(data, dim_coords_and_dims=[(a, 0), (b, 1)]) - nomatch2 = Cube(data, dim_coords_and_dims=[(x, 0), (y, 1)]) - - return nomatch1, nomatch2 - - def SetUpReversed(self): - # On this cube pair, the coordinates to perform operations on have - # matching long names but the points array on one cube is reversed - # with respect to that on the other. - data = np.zeros((3, 4)) - a1 = DimCoord([1, 2, 3], long_name="a") - b1 = DimCoord([1, 2, 3, 4], long_name="b") - a2 = DimCoord([3, 2, 1], long_name="a") - b2 = DimCoord([1, 2, 3, 4], long_name="b") - - reversed1 = Cube(data, dim_coords_and_dims=[(a1, 0), (b1, 1)]) - reversed2 = Cube(data, dim_coords_and_dims=[(a2, 0), (b2, 1)]) - - return reversed1, reversed2 - - -class CubeArithmeticMaskedConstantTestMixin(metaclass=ABCMeta): - @property - @abstractmethod - def cube_func(self): - # Define an iris arithmetic function to be called - # I.E. 'iris.analysis.maths.xx'. - pass - - def test_masked_constant_in_place(self): - # Cube in_place arithmetic operation. - dtype = np.int64 - dat = ma.masked_array(0, 1, dtype) - cube = Cube(dat) - res = self.cube_func(cube, 5, in_place=True) - self.assertMaskedArrayEqual(ma.masked_array(0, 1), res.data) - self.assertEqual(dtype, res.dtype) - self.assertIs(res, cube) - - def test_masked_constant_not_in_place(self): - # Cube in_place arithmetic operation. - dtype = np.int64 - dat = ma.masked_array(0, 1, dtype) - cube = Cube(dat) - res = self.cube_func(cube, 5, in_place=False) - self.assertMaskedArrayEqual(ma.masked_array(0, 1), res.data) - self.assertEqual(dtype, res.dtype) - self.assertIsNot(res, cube) diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py b/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py deleted file mode 100644 index 57e012e1c9..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test__arith__derived_coords.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for cube arithmetic involving derived (i.e. factory) coords.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.tests.unit.analysis.maths import ( - CubeArithmeticBroadcastingTestMixin, - MathsAddOperationMixin, -) - - -@tests.skip_data -class TestBroadcastingDerived( - tests.IrisTest, - MathsAddOperationMixin, - CubeArithmeticBroadcastingTestMixin, -): - """ - Repeat the broadcasting tests while retaining derived coordinates. - - NOTE: apart from showing that these operations do succeed, this mostly - produces a new set of CML result files, - in "lib/iris/tests/results/unit/analysis/maths/_arith__derived_coords" . - See there to confirm that the results preserve the derived coordinates. - - """ - - def _base_testcube(self): - return super()._base_testcube(include_derived=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py b/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py deleted file mode 100644 index e1255ef9d8..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test__arith__meshcoords.py +++ /dev/null @@ -1,184 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for cube arithmetic involving MeshCoords.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.analysis.maths import add -from iris.coords import AuxCoord, DimCoord -from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube -from iris.tests.unit.analysis.maths import ( - CubeArithmeticBroadcastingTestMixin, - CubeArithmeticCoordsTest, - MathsAddOperationMixin, -) - - -def _convert_to_meshcube(cube): - """Convert a cube based on stock.realistic_4d into a "meshcube".""" - # Replace lat+lon with a small mesh - cube = cube[..., -1] # remove final (X) dim - for name in ("grid_longitude", "grid_latitude"): - cube.remove_coord(name) - i_meshdim = len(cube.shape) - 1 - n_meshpoints = cube.shape[i_meshdim] - mesh = sample_mesh(n_nodes=n_meshpoints, n_faces=n_meshpoints, n_edges=0) - for co in mesh.to_MeshCoords(location="face"): - cube.add_aux_coord(co, i_meshdim) - # also add a dim-coord for the mesh dim, mainly so that - # the 'xxBroadcastingxx.test_collapse_all_dims' tests can do what they say. - mesh_dimcoord = DimCoord(np.arange(n_meshpoints), long_name="i_mesh_face") - cube.add_dim_coord(mesh_dimcoord, i_meshdim) - return cube - - -class MeshLocationsMixin: - # Control allowing us to also include test with derived coordinates. - use_derived_coords = False - - # Modify the inherited data operation, to test with a mesh-cube. - # Also, optionally, test with derived coordinates. - def _base_testcube(self): - cube = super()._base_testcube(include_derived=self.use_derived_coords) - cube = _convert_to_meshcube(cube) - self.cube_xy_dimcoords = ["i_mesh_face"] - self.cube = cube - return self.cube - - -@tests.skip_data -class TestBroadcastingWithMesh( - tests.IrisTest, - MeshLocationsMixin, - MathsAddOperationMixin, - CubeArithmeticBroadcastingTestMixin, -): - """ - Run all the broadcasting tests on cubes with meshes. - - NOTE: there is a fair amount of special-case code to support this, built - into the CubeArithmeticBroadcastingTestMixin baseclass. - - """ - - -@tests.skip_data -class TestBroadcastingWithMeshAndDerived( - tests.IrisTest, - MeshLocationsMixin, - MathsAddOperationMixin, - CubeArithmeticBroadcastingTestMixin, -): - """Run broadcasting tests with meshes *and* derived coords.""" - - use_derived = True - - -class TestCoordMatchWithMesh(CubeArithmeticCoordsTest): - """Run the coordinate-mismatch tests with meshcubes.""" - - def _convert_to_meshcubes(self, cubes, i_dim): - """Add a mesh to one dim of the 'normal case' test-cubes.""" - for cube in cubes: - n_size = cube.shape[i_dim] - mesh = sample_mesh(n_nodes=n_size, n_faces=n_size, n_edges=0) - for co in mesh.to_MeshCoords("face"): - cube.add_aux_coord(co, i_dim) - assert cube.mesh is not None - - def _check_no_match(self, dim): - # Duplicate the basic operation, but convert cubes to meshcubes. - cube1, cube2 = self.SetUpNonMatching() - self._convert_to_meshcubes([cube1, cube2], dim) - with self.assertRaises(ValueError): - add(cube1, cube2) - - def test_no_match_dim0(self): - self._check_no_match(0) - - def test_no_match_dim1(self): - self._check_no_match(1) - - def _check_reversed_points(self, dim): - # Duplicate the basic operation, but convert cubes to meshcubes. - cube1, cube2 = self.SetUpReversed() - self._convert_to_meshcubes([cube1, cube2], dim) - with self.assertRaises(ValueError): - add(cube1, cube2) - - def test_reversed_points_dim0(self): - self._check_reversed_points(0) - - def test_reversed_points_dim1(self): - self._check_reversed_points(1) - - -class TestBasicMeshOperation(tests.IrisTest): - """Some very basic standalone tests, in an easier-to-comprehend form.""" - - def test_meshcube_same_mesh(self): - # Two similar cubes on a common mesh add to a third on the same mesh. - mesh = sample_mesh() - cube1 = sample_mesh_cube(mesh=mesh) - cube2 = sample_mesh_cube(mesh=mesh) - self.assertIs(cube1.mesh, mesh) - self.assertIs(cube2.mesh, mesh) - - result = cube1 + cube2 - self.assertEqual(result.shape, cube1.shape) - self.assertIs(result.mesh, mesh) - - def test_meshcube_different_equal_mesh(self): - # Two similar cubes on identical but different meshes. - cube1 = sample_mesh_cube() - cube2 = sample_mesh_cube() - self.assertEqual(cube1.mesh, cube2.mesh) - self.assertIsNot(cube1.mesh, cube2.mesh) - - result = cube1 + cube2 - self.assertEqual(result.shape, cube1.shape) - self.assertEqual(result.mesh, cube1.mesh) - self.assertTrue(result.mesh is cube1.mesh or result.mesh is cube2.mesh) - - def test_fail_meshcube_nonequal_mesh(self): - # Cubes on similar but different meshes -- should *not* combine. - mesh1 = sample_mesh() - mesh2 = sample_mesh(n_edges=0) - self.assertNotEqual(mesh1, mesh2) - cube1 = sample_mesh_cube(mesh=mesh1) - cube2 = sample_mesh_cube(mesh=mesh2) - - msg = "Mesh coordinate.* does not match" - with self.assertRaisesRegex(ValueError, msg): - cube1 + cube2 - - def test_meshcube_meshcoord(self): - # Combining a meshcube and meshcoord. - cube = sample_mesh_cube() - cube.coord("latitude").units = "s" - cube.units = "m" - - # A separately derived, but matching 'latitude' MeshCoord. - coord = sample_mesh_cube().coord("latitude") - coord.units = "s" # N.B. the units **must also match** - - result = cube / coord - self.assertEqual(result.name(), "unknown") - self.assertEqual(result.units, "m s-1") - - # Moreover : *cannot* do this with the 'equivalent' AuxCoord - # cf. https://github.com/SciTools/iris/issues/4671 - coord = AuxCoord.from_coord(coord) - with self.assertRaises(ValueError): - cube / coord - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test__get_dtype.py b/lib/iris/tests/unit/analysis/maths/test__get_dtype.py deleted file mode 100644 index 220b728b32..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test__get_dtype.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function :func:`iris.analysis.maths._get_dtype`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -from numpy import ma - -from iris.analysis.maths import _get_dtype -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube - - -class Test(tests.IrisTest): - def _check_call(self, obj, expected_dtype): - result = _get_dtype(obj) - self.assertEqual(expected_dtype, result) - - def test_int8(self): - n = -128 - self._check_call(n, np.int8) - - def test_int16(self): - n = -129 - self._check_call(n, np.int16) - - def test_uint8(self): - n = 255 - self._check_call(n, np.uint8) - - def test_uint16(self): - n = 256 - self._check_call(n, np.uint16) - - def test_float16(self): - n = 60000.0 - self._check_call(n, np.float16) - - def test_float32(self): - n = 65000.0 - self._check_call(n, np.float32) - - def test_float64(self): - n = 1e40 - self._check_call(n, np.float64) - - def test_scalar_demote(self): - n = np.int64(10) - self._check_call(n, np.uint8) - - def test_array(self): - a = np.array([1, 2, 3], dtype=np.int16) - self._check_call(a, np.int16) - - def test_scalar_array(self): - dtype = np.int32 - a = np.array(1, dtype=dtype) - self._check_call(a, dtype) - - def test_masked_array(self): - dtype = np.float16 - m = ma.masked_array([1, 2, 3], [1, 0, 1], dtype=dtype) - self._check_call(m, dtype) - - def test_masked_constant(self): - m = ma.masked - self._check_call(m, m.dtype) - - def test_cube(self): - dtype = np.float32 - data = np.array([1, 2, 3], dtype=dtype) - cube = Cube(data) - self._check_call(cube, dtype) - - def test_aux_coord(self): - dtype = np.int64 - points = np.array([1, 2, 3], dtype=dtype) - aux_coord = AuxCoord(points) - self._check_call(aux_coord, dtype) - - def test_dim_coord(self): - dtype = np.float16 - points = np.array([1, 2, 3], dtype=dtype) - dim_coord = DimCoord(points) - self._check_call(dim_coord, dtype) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test__inplace_common_checks.py b/lib/iris/tests/unit/analysis/maths/test__inplace_common_checks.py deleted file mode 100644 index bd81a96fbd..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test__inplace_common_checks.py +++ /dev/null @@ -1,151 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function :func:`iris.analysis.maths._inplace_common_checks`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.analysis.maths import _inplace_common_checks -from iris.cube import Cube - - -class Test(tests.IrisTest): - # `_inplace_common_checks` is a pass-through function that does not return - # anything but will fail iff `cube` and `other` have integer dtype. Thus in - # a sense we only want to test the failing cases. Doing so, however, leaves - # us open to the case where currently known good cases fail silently. - # To avoid this all the known good cases are also tested by relying on the - # fact that functions with no return value implicitly return `None`. If - # these currently known good cases ever changed these tests would start - # failing and indicate something was wrong. - def setUp(self): - self.scalar_int = 5 - self.scalar_float = 5.5 - - self.float_data = np.array([8, 9], dtype=np.float64) - self.int_data = np.array([9, 8], dtype=np.int64) - self.uint_data = np.array([9, 8], dtype=np.uint64) - - self.float_cube = Cube(self.float_data) - self.int_cube = Cube(self.int_data) - self.uint_cube = Cube(self.uint_data) - - self.op = "addition" - self.emsg = "Cannot perform inplace {}".format(self.op) - - def test_float_cubes(self): - result = _inplace_common_checks( - self.float_cube, self.float_cube, self.op - ) - self.assertIsNone(result) - - def test_int_cubes(self): - result = _inplace_common_checks(self.int_cube, self.int_cube, self.op) - self.assertIsNone(result) - - def test_uint_cubes(self): - result = _inplace_common_checks( - self.uint_cube, self.uint_cube, self.op - ) - self.assertIsNone(result) - - def test_float_cube_int_cube(self): - result = _inplace_common_checks( - self.float_cube, self.int_cube, self.op - ) - self.assertIsNone(result) - - def test_float_cube_uint_cube(self): - result = _inplace_common_checks( - self.float_cube, self.uint_cube, self.op - ) - self.assertIsNone(result) - - def test_int_cube_float_cube(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.int_cube, self.float_cube, self.op) - - def test_uint_cube_float_cube(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.uint_cube, self.float_cube, self.op) - - def test_float_cube__scalar_int(self): - result = _inplace_common_checks( - self.float_cube, self.scalar_int, self.op - ) - self.assertIsNone(result) - - def test_float_cube__scalar_float(self): - result = _inplace_common_checks( - self.float_cube, self.scalar_float, self.op - ) - self.assertIsNone(result) - - def test_float_cube__int_array(self): - result = _inplace_common_checks( - self.float_cube, self.int_data, self.op - ) - self.assertIsNone(result) - - def test_float_cube__float_array(self): - result = _inplace_common_checks( - self.float_cube, self.float_data, self.op - ) - self.assertIsNone(result) - - def test_int_cube__scalar_int(self): - result = _inplace_common_checks( - self.int_cube, self.scalar_int, self.op - ) - self.assertIsNone(result) - - def test_int_cube_uint_cube(self): - result = _inplace_common_checks(self.int_cube, self.uint_cube, self.op) - self.assertIsNone(result) - - def test_uint_cube_uint_cube(self): - result = _inplace_common_checks( - self.uint_cube, self.uint_cube, self.op - ) - self.assertIsNone(result) - - def test_uint_cube_int_cube(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.uint_cube, self.int_cube, self.op) - - def test_int_cube__scalar_float(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.int_cube, self.scalar_float, self.op) - - def test_int_cube__int_array(self): - result = _inplace_common_checks(self.int_cube, self.int_cube, self.op) - self.assertIsNone(result) - - def test_int_cube__float_array(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.int_cube, self.float_data, self.op) - - def test_uint_cube__scalar_float(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.uint_cube, self.scalar_float, self.op) - - def test_uint_cube__int_array(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.uint_cube, self.int_cube, self.op) - - def test_uint_cube__float_array(self): - with self.assertRaisesRegex(ArithmeticError, self.emsg): - _inplace_common_checks(self.uint_cube, self.float_data, self.op) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test__output_dtype.py b/lib/iris/tests/unit/analysis/maths/test__output_dtype.py deleted file mode 100644 index c422e366be..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test__output_dtype.py +++ /dev/null @@ -1,235 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function :func:`iris.analysis.maths._output_dtype`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from itertools import product -import operator - -import numpy as np - -from iris.analysis.maths import _output_dtype - - -class Test(tests.IrisTest): - def setUp(self): - # Operators which result in a value of the same dtype as their - # arguments when the arguments' dtypes are the same. - self.same_result_ops = [ - operator.add, - operator.sub, - operator.mul, - operator.pow, - operator.floordiv, - np.add, - np.subtract, - np.multiply, - np.power, - np.floor_divide, - ] - - self.unary_same_result_ops = [np.abs] - - # Operators which always result in a float. - self.float_ops = [operator.truediv, np.true_divide] - - self.unary_float_ops = [np.log, np.log2, np.log10, np.exp] - - self.all_binary_ops = self.same_result_ops + self.float_ops - self.all_unary_ops = self.unary_same_result_ops + self.unary_float_ops - - self.dtypes = [ - np.dtype("i2"), - np.dtype("i4"), - np.dtype("i8"), - np.dtype("f2"), - np.dtype("f4"), - np.dtype("f8"), - ] - - def _binary_error_message( - self, - op, - first_dtype, - second_dtype, - expected_dtype, - result_dtype, - in_place=False, - ): - msg = ( - "Output for {op.__class__.__name__} {op.__name__!r} and " - "arguments ({dt1!r}, {dt2!r}, in_place={in_place}) " - "was {res!r}. Expected {exp!r}." - ) - return msg.format( - op=op, - dt1=first_dtype, - dt2=second_dtype, - exp=expected_dtype, - res=result_dtype, - in_place=in_place, - ) - - def _unary_error_message( - self, op, dtype, expected_dtype, result_dtype, in_place=False - ): - msg = ( - "Output for {op.__class__.__name__} {op.__name__!r} and " - "arguments ({dt!r}, in_place={in_place}) was {res!r}. " - "Expected {exp!r}." - ) - return msg.format( - op=op, - dt=dtype, - exp=expected_dtype, - res=result_dtype, - in_place=in_place, - ) - - def test_same_result(self): - # Check that the result dtype is the same as the input dtypes for - # relevant operators. - for dtype in self.dtypes: - for op in self.same_result_ops: - result_dtype = _output_dtype(op, dtype, dtype) - self.assertEqual( - dtype, - result_dtype, - self._binary_error_message( - op, dtype, dtype, dtype, result_dtype - ), - ) - for op in self.unary_same_result_ops: - result_dtype = _output_dtype(op, dtype) - self.assertEqual( - dtype, - result_dtype, - self._unary_error_message(op, dtype, dtype, result_dtype), - ) - - def test_binary_float(self): - # Check that the result dtype is a float for relevant operators. - # Perform checks for a selection of cases. - cases = [ - (np.dtype("i2"), np.dtype("i2"), np.dtype("f8")), - (np.dtype("i2"), np.dtype("i4"), np.dtype("f8")), - (np.dtype("i4"), np.dtype("i4"), np.dtype("f8")), - (np.dtype("i2"), np.dtype("f2"), np.dtype("f4")), - (np.dtype("i2"), np.dtype("f4"), np.dtype("f4")), - (np.dtype("i8"), np.dtype("f2"), np.dtype("f8")), - (np.dtype("f2"), np.dtype("f2"), np.dtype("f2")), - (np.dtype("f4"), np.dtype("f4"), np.dtype("f4")), - (np.dtype("f2"), np.dtype("f4"), np.dtype("f4")), - ] - for dtype1, dtype2, expected_dtype in cases: - for op in self.float_ops: - result_dtype = _output_dtype(op, dtype1, dtype2) - self.assertEqual( - expected_dtype, - result_dtype, - self._binary_error_message( - op, dtype1, dtype2, expected_dtype, result_dtype - ), - ) - - def test_unary_float(self): - cases = [ - (np.dtype("i2"), np.dtype("f4")), - (np.dtype("i4"), np.dtype("f8")), - (np.dtype("i8"), np.dtype("f8")), - (np.dtype("f2"), np.dtype("f2")), - (np.dtype("f4"), np.dtype("f4")), - (np.dtype("f8"), np.dtype("f8")), - ] - for dtype, expected_dtype in cases: - for op in self.unary_float_ops: - result_dtype = _output_dtype(op, dtype) - self.assertEqual( - expected_dtype, - result_dtype, - self._unary_error_message( - op, dtype, expected_dtype, result_dtype - ), - ) - - def test_binary_float_argument(self): - # Check that when one argument is a float dtype, a float dtype results - # Unary operators are covered by other tests. - dtypes = [ - np.dtype("i2"), - np.dtype("i4"), - np.dtype("i8"), - np.dtype("f2"), - np.dtype("f4"), - np.dtype("f8"), - ] - expected_dtypes = [ - np.dtype("f4"), - np.dtype("f8"), - np.dtype("f8"), - np.dtype("f2"), - np.dtype("f4"), - np.dtype("f8"), - ] - for op in self.all_binary_ops: - for dtype, expected_dtype in zip(dtypes, expected_dtypes): - result_dtype = _output_dtype(op, dtype, np.dtype("f2")) - self.assertEqual( - expected_dtype, - result_dtype, - self._binary_error_message( - op, dtype, np.dtype("f2"), expected_dtype, result_dtype - ), - ) - - def test_in_place(self): - # Check that when the in_place argument is True, the result is always - # the same as first operand. - for dtype1, dtype2 in product(self.dtypes, self.dtypes): - for op in self.all_binary_ops: - result_dtype = _output_dtype(op, dtype1, dtype2, in_place=True) - self.assertEqual( - result_dtype, - dtype1, - self._binary_error_message( - op, dtype1, dtype2, dtype1, result_dtype, in_place=True - ), - ) - for dtype in self.dtypes: - for op in self.all_unary_ops: - result_dtype = _output_dtype(op, dtype, in_place=True) - self.assertEqual( - result_dtype, - dtype, - self._unary_error_message( - op, dtype, dtype, result_dtype, in_place=True - ), - ) - - def test_commuative(self): - # Check that the operation is commutative if in_place is not specified. - for dtype1, dtype2 in product(self.dtypes, self.dtypes): - for op in self.all_binary_ops: - result_dtype1 = _output_dtype(op, dtype1, dtype2) - result_dtype2 = _output_dtype(op, dtype2, dtype1) - self.assertEqual( - result_dtype1, - result_dtype2, - "_output_dtype is not commutative with arguments " - "{!r} and {!r}: {!r} != {!r}".format( - dtype1, dtype2, result_dtype1, result_dtype2 - ), - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test_add.py b/lib/iris/tests/unit/analysis/maths/test_add.py deleted file mode 100644 index 1ca7f7c244..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test_add.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :func:`iris.analysis.maths.add` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import operator - -from iris.analysis.maths import add -from iris.tests.unit.analysis.maths import ( - CubeArithmeticBroadcastingTestMixin, - CubeArithmeticCoordsTest, - CubeArithmeticMaskedConstantTestMixin, - CubeArithmeticMaskingTestMixin, -) - - -@tests.skip_data -class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): - @property - def data_op(self): - return operator.add - - @property - def cube_func(self): - return add - - -class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): - @property - def data_op(self): - return operator.add - - @property - def cube_func(self): - return add - - -class TestCoordMatch(CubeArithmeticCoordsTest): - def test_no_match(self): - cube1, cube2 = self.SetUpNonMatching() - with self.assertRaises(ValueError): - add(cube1, cube2) - - def test_reversed_points(self): - cube1, cube2 = self.SetUpReversed() - with self.assertRaises(ValueError): - add(cube1, cube2) - - -class TestMaskedConstant( - tests.IrisTest, CubeArithmeticMaskedConstantTestMixin -): - @property - def data_op(self): - return operator.add - - @property - def cube_func(self): - return add - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test_divide.py b/lib/iris/tests/unit/analysis/maths/test_divide.py deleted file mode 100644 index 4bd202e037..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test_divide.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :func:`iris.analysis.maths.divide` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import operator - -import numpy as np - -from iris.analysis.maths import divide -from iris.cube import Cube -from iris.tests.unit.analysis.maths import ( - CubeArithmeticBroadcastingTestMixin, - CubeArithmeticCoordsTest, - CubeArithmeticMaskingTestMixin, -) - - -@tests.skip_data -class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): - @property - def data_op(self): - return operator.truediv - - @property - def cube_func(self): - return divide - - -class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): - @property - def data_op(self): - return operator.truediv - - @property - def cube_func(self): - return divide - - def test_unmasked_div_zero(self): - # Ensure cube behaviour matches numpy operator behaviour for the - # handling of arrays containing 0. - dat_a = np.array([0.0, 0.0, 0.0, 0.0]) - dat_b = np.array([2.0, 2.0, 2.0, 2.0]) - - cube_a = Cube(dat_a) - cube_b = Cube(dat_b) - - com = self.data_op(dat_b, dat_a) - res = self.cube_func(cube_b, cube_a).data - - self.assertArrayEqual(com, res) - - def test_masked_div_zero(self): - # Ensure cube behaviour matches numpy operator behaviour for the - # handling of arrays containing 0. - dat_a = np.ma.array([0.0, 0.0, 0.0, 0.0], mask=False) - dat_b = np.ma.array([2.0, 2.0, 2.0, 2.0], mask=False) - - cube_a = Cube(dat_a) - cube_b = Cube(dat_b) - - com = self.data_op(dat_b, dat_a) - res = self.cube_func(cube_b, cube_a).data - - self.assertMaskedArrayEqual(com, res, strict=True) - - -class TestCoordMatch(CubeArithmeticCoordsTest): - def test_no_match(self): - cube1, cube2 = self.SetUpNonMatching() - with self.assertRaises(ValueError): - divide(cube1, cube2) - - def test_reversed_points(self): - cube1, cube2 = self.SetUpReversed() - with self.assertRaises(ValueError): - divide(cube1, cube2) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test_multiply.py b/lib/iris/tests/unit/analysis/maths/test_multiply.py deleted file mode 100644 index 266342605a..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test_multiply.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :func:`iris.analysis.maths.multiply` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import operator - -from iris.analysis.maths import multiply -from iris.tests.unit.analysis.maths import ( - CubeArithmeticBroadcastingTestMixin, - CubeArithmeticCoordsTest, - CubeArithmeticMaskedConstantTestMixin, - CubeArithmeticMaskingTestMixin, -) - - -@tests.skip_data -class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): - @property - def data_op(self): - return operator.mul - - @property - def cube_func(self): - return multiply - - -class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): - @property - def data_op(self): - return operator.mul - - @property - def cube_func(self): - return multiply - - -class TestCoordMatch(CubeArithmeticCoordsTest): - def test_no_match(self): - cube1, cube2 = self.SetUpNonMatching() - with self.assertRaises(ValueError): - multiply(cube1, cube2) - - def test_reversed_points(self): - cube1, cube2 = self.SetUpReversed() - with self.assertRaises(ValueError): - multiply(cube1, cube2) - - -class TestMaskedConstant( - tests.IrisTest, CubeArithmeticMaskedConstantTestMixin -): - @property - def data_op(self): - return operator.mul - - @property - def cube_func(self): - return multiply - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/maths/test_subtract.py b/lib/iris/tests/unit/analysis/maths/test_subtract.py deleted file mode 100644 index f7a9df34d0..0000000000 --- a/lib/iris/tests/unit/analysis/maths/test_subtract.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :func:`iris.analysis.maths.subtract` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import operator - -from iris.analysis.maths import subtract -from iris.tests.unit.analysis.maths import ( - CubeArithmeticBroadcastingTestMixin, - CubeArithmeticCoordsTest, - CubeArithmeticMaskedConstantTestMixin, - CubeArithmeticMaskingTestMixin, -) - - -@tests.skip_data -class TestBroadcasting(tests.IrisTest, CubeArithmeticBroadcastingTestMixin): - @property - def data_op(self): - return operator.sub - - @property - def cube_func(self): - return subtract - - -class TestMasking(tests.IrisTest, CubeArithmeticMaskingTestMixin): - @property - def data_op(self): - return operator.sub - - @property - def cube_func(self): - return subtract - - -class TestCoordMatch(CubeArithmeticCoordsTest): - def test_no_match(self): - cube1, cube2 = self.SetUpNonMatching() - with self.assertRaises(ValueError): - subtract(cube1, cube2) - - def test_reversed_points(self): - cube1, cube2 = self.SetUpReversed() - with self.assertRaises(ValueError): - subtract(cube1, cube2) - - -class TestMaskedConstant( - tests.IrisTest, CubeArithmeticMaskedConstantTestMixin -): - @property - def data_op(self): - return operator.sub - - @property - def cube_func(self): - return subtract - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/regrid/__init__.py b/lib/iris/tests/unit/analysis/regrid/__init__.py deleted file mode 100644 index a0a0fd0a6b..0000000000 --- a/lib/iris/tests/unit/analysis/regrid/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis._regrid` module.""" diff --git a/lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py b/lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py deleted file mode 100644 index a018507fb3..0000000000 --- a/lib/iris/tests/unit/analysis/regrid/test_RectilinearRegridder.py +++ /dev/null @@ -1,1463 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.analysis._regrid.RectilinearRegridder`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import dask.array as da -import numpy as np -import numpy.ma as ma - -from iris.analysis._regrid import RectilinearRegridder as Regridder -from iris.aux_factory import HybridHeightFactory -from iris.coord_systems import OSGB, GeogCS -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube -from iris.tests.stock import global_pp, lat_lon_cube, realistic_4d - -RESULT_DIR = ("analysis", "regrid") - -# Convenience to access Regridder static method. -regrid = Regridder._regrid - - -class Test__regrid__linear(tests.IrisTest): - def setUp(self): - self.x = DimCoord(np.linspace(-2, 57, 60)) - self.y = DimCoord(np.linspace(0, 49, 50)) - self.xs, self.ys = np.meshgrid(self.x.points, self.y.points) - - def transformation(x, y): - return x + y**2 - - # Construct a function which adds dimensions to the 2D data array - # so that we can test higher dimensional functionality. - def dim_extender(arr): - return arr[np.newaxis, ..., np.newaxis] * [1, 2] - - self.data = dim_extender(transformation(self.xs, self.ys)) - - target_x = np.linspace(-3, 60, 4) - target_y = np.linspace(0.5, 51, 3) - self.target_x, self.target_y = np.meshgrid(target_x, target_y) - - #: Expected values, which not quite the analytical value, but - #: representative of the bilinear interpolation scheme. - self.expected = np.array( - [ - [ - [ - [np.nan, np.nan], - [18.5, 37.0], - [39.5, 79.0], - [np.nan, np.nan], - ], - [ - [np.nan, np.nan], - [681.25, 1362.5], - [702.25, 1404.5], - [np.nan, np.nan], - ], - [ - [np.nan, np.nan], - [np.nan, np.nan], - [np.nan, np.nan], - [np.nan, np.nan], - ], - ] - ] - ) - - self.x_dim = 2 - self.y_dim = 1 - - def assert_values(self, values): - # values is a list of [x, y, [val1, val2]] - xs, ys, expecteds = zip(*values) - expecteds = np.array(expecteds)[None, None, ...] - result = regrid( - self.data, - self.x_dim, - self.y_dim, - self.x, - self.y, - np.array([xs]), - np.array([ys]), - ) - self.assertArrayAllClose(result, expecteds, rtol=1e-04, equal_nan=True) - - # Check that transposing the input data results in the same values - ndim = self.data.ndim - result2 = regrid( - self.data.T, - ndim - self.x_dim - 1, - ndim - self.y_dim - 1, - self.x, - self.y, - np.array([xs]), - np.array([ys]), - ) - self.assertArrayEqual(result.T, result2) - - def test_single_values(self): - # Check that the values are sensible e.g. (3 + 4**2 == 19) - self.assert_values( - [ - [3, 4, [19, 38]], - [-2, 0, [-2, -4]], - [-2.01, 0, [np.nan, np.nan]], - [2, -0.01, [np.nan, np.nan]], - [57, 0, [57, 114]], - [57.01, 0, [np.nan, np.nan]], - [57, 49, [2458, 4916]], - [57, 49.01, [np.nan, np.nan]], - ] - ) - - def test_simple_result(self): - result = regrid( - self.data, - self.x_dim, - self.y_dim, - self.x, - self.y, - self.target_x, - self.target_y, - ) - self.assertArrayEqual(result, self.expected) - - def test_simple_masked(self): - data = ma.MaskedArray(self.data, mask=True) - data.mask[:, 1:30, 1:30] = False - result = regrid( - data, - self.x_dim, - self.y_dim, - self.x, - self.y, - self.target_x, - self.target_y, - ) - expected_mask = np.array( - [ - [ - [[True, True], [True, True], [True, True], [True, True]], - [[True, True], [False, False], [True, True], [True, True]], - [[True, True], [True, True], [True, True], [True, True]], - ] - ], - dtype=bool, - ) - expected = ma.MaskedArray(self.expected, mask=expected_mask) - self.assertMaskedArrayEqual(result, expected) - - def test_simple_masked_no_mask(self): - data = ma.MaskedArray(self.data, mask=False) - result = regrid( - data, - self.x_dim, - self.y_dim, - self.x, - self.y, - self.target_x, - self.target_y, - ) - self.assertIsInstance(result, ma.MaskedArray) - - def test_result_transpose_shape(self): - ndim = self.data.ndim - result = regrid( - self.data.T, - ndim - self.x_dim - 1, - ndim - self.y_dim - 1, - self.x, - self.y, - self.target_x, - self.target_y, - ) - self.assertArrayEqual(result, self.expected.T) - - def test_reverse_x_coord(self): - index = [slice(None)] * self.data.ndim - index[self.x_dim] = slice(None, None, -1) - result = regrid( - self.data[tuple(index)], - self.x_dim, - self.y_dim, - self.x[::-1], - self.y, - self.target_x, - self.target_y, - ) - self.assertArrayEqual(result, self.expected) - - def test_circular_x_coord(self): - # Check that interpolation of a circular src coordinate doesn't result - # in an out of bounds value. - self.x.circular = True - self.x.units = "degree" - result = regrid( - self.data, - self.x_dim, - self.y_dim, - self.x, - self.y, - np.array([[58]]), - np.array([[0]]), - ) - self.assertArrayAlmostEqual( - result, np.array([56.80398671, 113.60797342], ndmin=self.data.ndim) - ) - - -# Check what happens to NaN values, extrapolated values, and -# masked values. -class Test__regrid__extrapolation_modes(tests.IrisTest): - values_by_method = { - "linear": [ - [np.nan, np.nan, 2, 3, np.nan], - [np.nan, np.nan, 6, 7, np.nan], - [8, 9, 10, 11, np.nan], - ], - "nearest": [ - [np.nan, 1, 2, 3, np.nan], - [4, 5, 6, 7, np.nan], - [8, 9, 10, 11, np.nan], - ], - } - - extrapolate_values_by_method = { - "linear": [ - [np.nan, np.nan, 2, 3, 4], - [np.nan, np.nan, 6, 7, 8], - [8, 9, 10, 11, 12], - ], - "nearest": [[np.nan, 1, 2, 3, 3], [4, 5, 6, 7, 7], [8, 9, 10, 11, 11]], - } - - def setUp(self): - self.methods = ("linear", "nearest") - self.test_dtypes = [ - np.dtype(spec) - for spec in ("i1", "i2", "i4", "i8", "f2", "f4", "f8") - ] - - def _regrid(self, data, method, extrapolation_mode=None): - x = np.arange(4) - y = np.arange(3) - x_coord = DimCoord(x) - y_coord = DimCoord(y) - x_dim, y_dim = 1, 0 - grid_x, grid_y = np.meshgrid(np.arange(5), y) - kwargs = dict(method=method) - if extrapolation_mode is not None: - kwargs["extrapolation_mode"] = extrapolation_mode - result = regrid( - data, x_dim, y_dim, x_coord, y_coord, grid_x, grid_y, **kwargs - ) - return result - - def test_default_ndarray(self): - # NaN -> NaN - # Extrapolated -> NaN - data = np.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - for method in self.methods: - result = self._regrid(data, method) - self.assertNotIsInstance(result, ma.MaskedArray) - expected = self.values_by_method[method] - self.assertArrayEqual(result, expected) - - def test_default_maskedarray(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> Masked - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - data[2, 3] = ma.masked - for method in self.methods: - result = self._regrid(data, method) - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 1, 1]] - values = self.values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_default_maskedarray_none_masked(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> N/A - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - for method in self.methods: - result = self._regrid(data, method) - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1]] - values = self.values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_default_maskedarray_none_masked_expanded(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> N/A - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - # Make sure the mask has been expanded - data.mask = False - data[0, 0] = np.nan - for method in self.methods: - result = self._regrid(data, method) - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1]] - values = self.values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_method_ndarray(self): - # NaN -> NaN - # Extrapolated -> linear - data = np.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - for method in self.methods: - result = self._regrid(data, method, "extrapolate") - self.assertNotIsInstance(result, ma.MaskedArray) - expected = self.extrapolate_values_by_method[method] - self.assertArrayEqual(result, expected) - - def test_method_maskedarray(self): - # NaN -> NaN - # Extrapolated -> linear - # Masked -> Masked - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - data[2, 3] = ma.masked - for method in self.methods: - result = self._regrid(data, method, "extrapolate") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 1, 1]] - values = self.extrapolate_values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_nan_ndarray(self): - # NaN -> NaN - # Extrapolated -> NaN - data = np.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - for method in self.methods: - result = self._regrid(data, method, "nan") - self.assertNotIsInstance(result, ma.MaskedArray) - expected = self.values_by_method[method] - self.assertArrayEqual(result, expected) - - def test_nan_maskedarray(self): - # NaN -> NaN - # Extrapolated -> NaN - # Masked -> Masked - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - data[2, 3] = ma.masked - for method in self.methods: - result = self._regrid(data, method, "nan") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 1, 0]] - values = self.values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_error_ndarray(self): - # Values irrelevant - the function raises an error. - data = np.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - for method in self.methods: - with self.assertRaisesRegex(ValueError, "out of bounds"): - self._regrid(data, method, "error") - - def test_error_maskedarray(self): - # Values irrelevant - the function raises an error. - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - data[2, 3] = ma.masked - for method in self.methods: - with self.assertRaisesRegex(ValueError, "out of bounds"): - self._regrid(data, method, "error") - - def test_mask_ndarray(self): - # NaN -> NaN - # Extrapolated -> Masked (this is different from all the other - # modes) - data = np.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - for method in self.methods: - result = self._regrid(data, method, "mask") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 0, 1]] - values = self.values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_mask_maskedarray(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> Masked - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - data[2, 3] = ma.masked - for method in self.methods: - result = self._regrid(data, method, "mask") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 1, 1]] - values = self.values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_nanmask_ndarray(self): - # NaN -> NaN - # Extrapolated -> NaN - data = np.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - for method in self.methods: - result = self._regrid(data, method, "nanmask") - self.assertNotIsInstance(result, ma.MaskedArray) - expected = self.values_by_method[method] - self.assertArrayEqual(result, expected) - - def test_nanmask_maskedarray(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> Masked - data = ma.arange(12, dtype=np.float64).reshape(3, 4) - data[0, 0] = np.nan - data[2, 3] = ma.masked - for method in self.methods: - result = self._regrid(data, method, "nanmask") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0, 1], [0, 0, 0, 0, 1], [0, 0, 0, 1, 1]] - values = self.values_by_method[method] - expected = ma.MaskedArray(values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_invalid(self): - data = np.arange(12, dtype=np.float64).reshape(3, 4) - emsg = "Invalid extrapolation mode" - for method in self.methods: - with self.assertRaisesRegex(ValueError, emsg): - self._regrid(data, method, "BOGUS") - - def test_method_result_types(self): - # Check return types from basic calculation on floats and ints. - for method in self.methods: - result_dtypes = {} - for source_dtype in self.test_dtypes: - data = np.arange(12, dtype=source_dtype).reshape(3, 4) - result = self._regrid(data, method) - result_dtypes[source_dtype] = result.dtype - if method == "linear": - # Linear results are promoted to float. - expected_types_mapping = { - test_dtype: np.promote_types(test_dtype, np.float16) - for test_dtype in self.test_dtypes - } - if method == "nearest": - # Nearest results are the same as the original data. - expected_types_mapping = { - test_dtype: test_dtype for test_dtype in self.test_dtypes - } - self.assertEqual(result_dtypes, expected_types_mapping) - - -class Test___call___lazy(tests.IrisTest): - def setUp(self): - self.cube = lat_lon_cube() - # Regridder method and extrapolation-mode. - self.args = ("linear", "mask") - self.regridder = Regridder(self.cube, self.cube, *self.args) - self.lazy_cube = self.cube.copy(da.asarray(self.cube.data)) - self.lazy_regridder = Regridder( - self.lazy_cube, self.lazy_cube, *self.args - ) - - def test_lazy_regrid(self): - result = self.lazy_regridder(self.lazy_cube) - self.assertTrue(result.has_lazy_data()) - expected = self.regridder(self.cube) - self.assertTrue(result == expected) - - -class Test___call____invalid_types(tests.IrisTest): - def setUp(self): - self.cube = lat_lon_cube() - # Regridder method and extrapolation-mode. - self.args = ("linear", "mask") - self.regridder = Regridder(self.cube, self.cube, *self.args) - - def test_src_as_array(self): - arr = np.zeros((3, 4)) - with self.assertRaises(TypeError): - Regridder(arr, self.cube, *self.args) - with self.assertRaises(TypeError): - self.regridder(arr) - - def test_grid_as_array(self): - with self.assertRaises(TypeError): - Regridder(self.cube, np.zeros((3, 4)), *self.args) - - def test_src_as_int(self): - with self.assertRaises(TypeError): - Regridder(42, self.cube, *self.args) - with self.assertRaises(TypeError): - self.regridder(42) - - def test_grid_as_int(self): - with self.assertRaises(TypeError): - Regridder(self.cube, 42, *self.args) - - -class Test___call____missing_coords(tests.IrisTest): - def setUp(self): - self.args = ("linear", "mask") - - def ok_bad(self, coord_names): - # Deletes the named coords from `bad`. - ok = lat_lon_cube() - bad = lat_lon_cube() - for name in coord_names: - bad.remove_coord(name) - return ok, bad - - def test_src_missing_lat(self): - ok, bad = self.ok_bad(["latitude"]) - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - regridder = Regridder(ok, ok, *self.args) - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_missing_lat(self): - ok, bad = self.ok_bad(["latitude"]) - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - def test_src_missing_lon(self): - ok, bad = self.ok_bad(["longitude"]) - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - regridder = Regridder(ok, ok, *self.args) - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_missing_lon(self): - ok, bad = self.ok_bad(["longitude"]) - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - def test_src_missing_lat_lon(self): - ok, bad = self.ok_bad(["latitude", "longitude"]) - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - regridder = Regridder(ok, ok, *self.args) - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_missing_lat_lon(self): - ok, bad = self.ok_bad(["latitude", "longitude"]) - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - -class Test___call____not_dim_coord(tests.IrisTest): - def setUp(self): - self.args = ("linear", "mask") - - def ok_bad(self, coord_name): - # Demotes the named DimCoord on `bad` to an AuxCoord. - ok = lat_lon_cube() - bad = lat_lon_cube() - coord = bad.coord(coord_name) - dims = bad.coord_dims(coord) - bad.remove_coord(coord_name) - aux_coord = AuxCoord.from_coord(coord) - bad.add_aux_coord(aux_coord, dims) - return ok, bad - - def test_src_with_aux_lat(self): - ok, bad = self.ok_bad("latitude") - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - regridder = Regridder(ok, ok, *self.args) - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_with_aux_lat(self): - ok, bad = self.ok_bad("latitude") - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - def test_src_with_aux_lon(self): - ok, bad = self.ok_bad("longitude") - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - regridder = Regridder(ok, ok, *self.args) - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_with_aux_lon(self): - ok, bad = self.ok_bad("longitude") - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - -class Test___call____not_dim_coord_share(tests.IrisTest): - def setUp(self): - self.args = ("linear", "mask") - - def ok_bad(self): - # Make lat/lon share a single dimension on `bad`. - ok = lat_lon_cube() - bad = lat_lon_cube() - lat = bad.coord("latitude") - bad = bad[0, : lat.shape[0]] - bad.remove_coord("latitude") - bad.add_aux_coord(lat, 0) - return ok, bad - - def test_src_shares_dim(self): - ok, bad = self.ok_bad() - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - regridder = Regridder(ok, ok, *self.args) - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_shares_dim(self): - ok, bad = self.ok_bad() - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - -class Test___call____bad_georeference(tests.IrisTest): - def setUp(self): - self.args = ("linear", "mask") - - def ok_bad(self, lat_cs, lon_cs): - # Updates `bad` to use the given coordinate systems. - ok = lat_lon_cube() - bad = lat_lon_cube() - bad.coord("latitude").coord_system = lat_cs - bad.coord("longitude").coord_system = lon_cs - return ok, bad - - def test_src_no_cs(self): - ok, bad = self.ok_bad(None, None) - regridder = Regridder(bad, ok, *self.args) - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_no_cs(self): - ok, bad = self.ok_bad(None, None) - regridder = Regridder(ok, bad, *self.args) - with self.assertRaises(ValueError): - regridder(ok) - - def test_src_one_cs(self): - ok, bad = self.ok_bad(None, GeogCS(6371000)) - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - - def test_grid_one_cs(self): - ok, bad = self.ok_bad(None, GeogCS(6371000)) - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - def test_src_inconsistent_cs(self): - ok, bad = self.ok_bad(GeogCS(6370000), GeogCS(6371000)) - with self.assertRaises(ValueError): - Regridder(bad, ok, *self.args) - - def test_grid_inconsistent_cs(self): - ok, bad = self.ok_bad(GeogCS(6370000), GeogCS(6371000)) - with self.assertRaises(ValueError): - Regridder(ok, bad, *self.args) - - -class Test___call____bad_angular_units(tests.IrisTest): - def ok_bad(self): - # Changes the longitude coord to radians on `bad`. - ok = lat_lon_cube() - bad = lat_lon_cube() - bad.coord("longitude").units = "radians" - return ok, bad - - def test_src_radians(self): - ok, bad = self.ok_bad() - regridder = Regridder(bad, ok, "linear", "mask") - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_radians(self): - ok, bad = self.ok_bad() - with self.assertRaises(ValueError): - Regridder(ok, bad, "linear", "mask") - - -def uk_cube(): - data = np.arange(12, dtype=np.float32).reshape(3, 4) - uk = Cube(data) - cs = OSGB() - y_coord = DimCoord( - np.arange(3), "projection_y_coordinate", units="m", coord_system=cs - ) - x_coord = DimCoord( - np.arange(4), "projection_x_coordinate", units="m", coord_system=cs - ) - uk.add_dim_coord(y_coord, 0) - uk.add_dim_coord(x_coord, 1) - surface = AuxCoord(data * 10, "surface_altitude", units="m") - uk.add_aux_coord(surface, (0, 1)) - uk.add_aux_factory(HybridHeightFactory(orography=surface)) - return uk - - -class Test___call____bad_linear_units(tests.IrisTest): - def ok_bad(self): - # Defines `bad` with an x coordinate in km. - ok = lat_lon_cube() - bad = uk_cube() - bad.coord(axis="x").units = "km" - return ok, bad - - def test_src_km(self): - ok, bad = self.ok_bad() - regridder = Regridder(bad, ok, "linear", "mask") - with self.assertRaises(ValueError): - regridder(bad) - - def test_grid_km(self): - ok, bad = self.ok_bad() - with self.assertRaises(ValueError): - Regridder(ok, bad, "linear", "mask") - - -class Test___call____no_coord_systems(tests.IrisTest): - # Test behaviour in the absence of any coordinate systems. - - def setUp(self): - self.mode = "mask" - self.methods = ("linear", "nearest") - - def remove_coord_systems(self, cube): - for coord in cube.coords(): - coord.coord_system = None - - def test_ok(self): - # Ensure regridding is supported when the coordinate definitions match. - # NB. We change the coordinate *values* to ensure that does not - # prevent the regridding operation. - src = uk_cube() - self.remove_coord_systems(src) - grid = src.copy() - for coord in grid.dim_coords: - coord.points = coord.points + 1 - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - for coord in result.dim_coords: - self.assertEqual(coord, grid.coord(coord)) - expected = ma.arange(12).reshape((3, 4)) + 5 - expected[:, 3] = ma.masked - expected[2, :] = ma.masked - self.assertMaskedArrayEqual(result.data, expected) - - def test_matching_units(self): - # Check we are insensitive to the units provided they match. - # NB. We change the coordinate *values* to ensure that does not - # prevent the regridding operation. - src = uk_cube() - self.remove_coord_systems(src) - # Move to unusual units (i.e. not metres or degrees). - for coord in src.dim_coords: - coord.units = "feet" - grid = src.copy() - for coord in grid.dim_coords: - coord.points = coord.points + 1 - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - for coord in result.dim_coords: - self.assertEqual(coord, grid.coord(coord)) - expected = ma.arange(12).reshape((3, 4)) + 5 - expected[:, 3] = ma.masked - expected[2, :] = ma.masked - self.assertMaskedArrayEqual(result.data, expected) - - def test_different_units(self): - src = uk_cube() - self.remove_coord_systems(src) - # Move to unusual units (i.e. not metres or degrees). - for coord in src.coords(): - coord.units = "feet" - grid = src.copy() - grid.coord("projection_y_coordinate").units = "yards" - # We change the coordinate *values* to ensure that does not - # prevent the regridding operation. - for coord in grid.dim_coords: - coord.points = coord.points + 1 - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - emsg = "matching coordinate metadata" - with self.assertRaisesRegex(ValueError, emsg): - regridder(src) - - def test_coord_metadata_mismatch(self): - # Check for failure when coordinate definitions differ. - uk = uk_cube() - self.remove_coord_systems(uk) - lat_lon = lat_lon_cube() - self.remove_coord_systems(lat_lon) - for method in self.methods: - regridder = Regridder(uk, lat_lon, method, self.mode) - with self.assertRaises(ValueError): - regridder(uk) - - -class Test___call____extrapolation_modes(tests.IrisTest): - values = [ - [np.nan, 6, 7, np.nan], - [9, 10, 11, np.nan], - [np.nan, np.nan, np.nan, np.nan], - ] - - extrapolate_values_by_method = { - "linear": [[np.nan, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]], - "nearest": [[np.nan, 6, 7, 7], [9, 10, 11, 11], [9, 10, 11, 11]], - } - - surface_values = [ - [50, 60, 70, np.nan], - [90, 100, 110, np.nan], - [np.nan, np.nan, np.nan, np.nan], - ] - - def setUp(self): - self.methods = ("linear", "nearest") - - def _ndarray_cube(self, method): - assert method in self.methods - src = uk_cube() - index = (0, 0) if method == "linear" else (1, 1) - src.data[index] = np.nan - return src - - def _masked_cube(self, method): - assert method in self.methods - src = uk_cube() - src.data = ma.asarray(src.data) - nan_index = (0, 0) if method == "linear" else (1, 1) - mask_index = (2, 3) - src.data[nan_index] = np.nan - src.data[mask_index] = ma.masked - return src - - def _regrid(self, src, method, extrapolation_mode="mask"): - grid = src.copy() - for coord in grid.dim_coords: - coord.points = coord.points + 1 - regridder = Regridder(src, grid, method, extrapolation_mode) - result = regridder(src) - - surface = result.coord("surface_altitude").points - self.assertNotIsInstance(surface, ma.MaskedArray) - self.assertArrayEqual(surface, self.surface_values) - - return result.data - - def test_default_ndarray(self): - # NaN -> NaN - # Extrapolated -> Masked - for method in self.methods: - src = self._ndarray_cube(method) - result = self._regrid(src, method) - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 1], [0, 0, 0, 1], [1, 1, 1, 1]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_default_maskedarray(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> Masked - for method in self.methods: - src = self._masked_cube(method) - result = self._regrid(src, method) - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 1], [0, 0, 1, 1], [1, 1, 1, 1]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_default_maskedarray_none_masked(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> N/A - for method in self.methods: - src = uk_cube() - src.data = ma.asarray(src.data) - index = (0, 0) if method == "linear" else (1, 1) - src.data[index] = np.nan - result = self._regrid(src, method) - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 1], [0, 0, 0, 1], [1, 1, 1, 1]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_default_maskedarray_none_masked_expanded(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> N/A - for method in self.methods: - src = uk_cube() - src.data = ma.asarray(src.data) - # Make sure the mask has been expanded - src.data.mask = False - index = (0, 0) if method == "linear" else (1, 1) - src.data[index] = np.nan - result = self._regrid(src, method) - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 1], [0, 0, 0, 1], [1, 1, 1, 1]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_method_ndarray(self): - # NaN -> NaN - # Extrapolated -> linear - for method in self.methods: - src = self._ndarray_cube(method) - result = self._regrid(src, method, "extrapolate") - self.assertNotIsInstance(result, ma.MaskedArray) - expected = self.extrapolate_values_by_method[method] - self.assertArrayEqual(result, expected) - - def test_nan_ndarray(self): - # NaN -> NaN - # Extrapolated -> NaN - for method in self.methods: - src = self._ndarray_cube(method) - result = self._regrid(src, method, "nan") - self.assertNotIsInstance(result, ma.MaskedArray) - self.assertArrayEqual(result, self.values) - - def test_nan_maskedarray(self): - # NaN -> NaN - # Extrapolated -> NaN - # Masked -> Masked - for method in self.methods: - src = self._masked_cube(method) - result = self._regrid(src, method, "nan") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_error_ndarray(self): - # Values irrelevant - the function raises an error. - for method in self.methods: - src = self._ndarray_cube(method) - with self.assertRaisesRegex(ValueError, "out of bounds"): - self._regrid(src, method, "error") - - def test_error_maskedarray(self): - # Values irrelevant - the function raises an error. - for method in self.methods: - src = self._masked_cube(method) - with self.assertRaisesRegex(ValueError, "out of bounds"): - self._regrid(src, method, "error") - - def test_mask_ndarray(self): - # NaN -> NaN - # Extrapolated -> Masked (this is different from all the other - # modes) - for method in self.methods: - src = self._ndarray_cube(method) - result = self._regrid(src, method, "mask") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 1], [0, 0, 0, 1], [1, 1, 1, 1]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_mask_maskedarray(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> Masked - for method in self.methods: - src = self._masked_cube(method) - result = self._regrid(src, method, "mask") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 1], [0, 0, 1, 1], [1, 1, 1, 1]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_nanmask_ndarray(self): - # NaN -> NaN - # Extrapolated -> NaN - for method in self.methods: - src = self._ndarray_cube(method) - result = self._regrid(src, method, "nanmask") - self.assertNotIsInstance(result, ma.MaskedArray) - self.assertArrayEqual(result, self.values) - - def test_nanmask_maskedarray(self): - # NaN -> NaN - # Extrapolated -> Masked - # Masked -> Masked - for method in self.methods: - src = self._masked_cube(method) - result = self._regrid(src, method, "nanmask") - self.assertIsInstance(result, ma.MaskedArray) - mask = [[0, 0, 0, 1], [0, 0, 1, 1], [1, 1, 1, 1]] - expected = ma.MaskedArray(self.values, mask) - self.assertMaskedArrayEqual(result, expected) - - def test_invalid(self): - src = uk_cube() - emsg = "Invalid extrapolation mode" - for method in self.methods: - with self.assertRaisesRegex(ValueError, emsg): - self._regrid(src, method, "BOGUS") - - -@tests.skip_data -class Test___call____rotated_to_lat_lon(tests.IrisTest): - def setUp(self): - self.src = realistic_4d()[:5, :2, ::40, ::30] - self.mode = "mask" - self.methods = ("linear", "nearest") - - def test_single_point(self): - src = self.src[0, 0] - grid = global_pp()[:1, :1] - # These coordinate values have been derived by converting the - # rotated coordinates of src[1, 1] into lat/lon by using cs2cs. - grid.coord("longitude").points = -3.144870 - grid.coord("latitude").points = 52.406444 - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - self.assertEqual(src.data[1, 1], result.data) - - def test_transposed_src(self): - # The source dimensions are in a non-standard order. - src = self.src - src.transpose([3, 1, 2, 0]) - grid = self._grid_subset() - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - result.transpose([3, 1, 2, 0]) - cml = RESULT_DIR + ("{}_subset.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def _grid_subset(self): - # The destination grid points are entirely contained within the - # src grid points. - grid = global_pp()[:4, :5] - grid.coord("longitude").points = np.linspace(-3.182, -3.06, 5) - grid.coord("latitude").points = np.linspace(52.372, 52.44, 4) - return grid - - def test_reversed(self): - src = self.src - grid = self._grid_subset() - - for method in self.methods: - cml = RESULT_DIR + ("{}_subset.cml".format(method),) - regridder = Regridder(src, grid[::-1], method, self.mode) - result = regridder(src) - self.assertCMLApproxData(result[:, :, ::-1], cml) - - sample = src[:, :, ::-1] - regridder = Regridder(sample, grid[::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, ::-1], cml) - - sample = src[:, :, :, ::-1] - regridder = Regridder(sample, grid[::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, ::-1], cml) - - sample = src[:, :, ::-1, ::-1] - regridder = Regridder(sample, grid[::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, ::-1], cml) - - regridder = Regridder(src, grid[:, ::-1], method, self.mode) - result = regridder(src) - self.assertCMLApproxData(result[:, :, :, ::-1], cml) - - sample = src[:, :, ::-1] - regridder = Regridder(sample, grid[:, ::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, :, ::-1], cml) - - sample = src[:, :, :, ::-1] - regridder = Regridder(sample, grid[:, ::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, :, ::-1], cml) - - sample = src[:, :, ::-1, ::-1] - regridder = Regridder(sample, grid[:, ::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, :, ::-1], cml) - - regridder = Regridder(src, grid[::-1, ::-1], method, self.mode) - result = regridder(src) - self.assertCMLApproxData(result[:, :, ::-1, ::-1], cml) - - sample = src[:, :, ::-1] - regridder = Regridder(sample, grid[::-1, ::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, ::-1, ::-1], cml) - - sample = src[:, :, :, ::-1] - regridder = Regridder(sample, grid[::-1, ::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, ::-1, ::-1], cml) - - sample = src[:, :, ::-1, ::-1] - regridder = Regridder(sample, grid[::-1, ::-1], method, self.mode) - result = regridder(sample) - self.assertCMLApproxData(result[:, :, ::-1, ::-1], cml) - - def test_grid_subset(self): - # The destination grid points are entirely contained within the - # src grid points. - grid = self._grid_subset() - for method in self.methods: - regridder = Regridder(self.src, grid, method, self.mode) - result = regridder(self.src) - cml = RESULT_DIR + ("{}_subset.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def _big_grid(self): - grid = self._grid_subset() - big_grid = Cube(np.zeros((5, 10, 3, 4, 5))) - big_grid.add_dim_coord(grid.coord("latitude"), 3) - big_grid.add_dim_coord(grid.coord("longitude"), 4) - return big_grid - - def test_grid_subset_big(self): - # Add some extra dimensions to the destination Cube and - # these should be safely ignored. - big_grid = self._big_grid() - for method in self.methods: - regridder = Regridder(self.src, big_grid, method, self.mode) - result = regridder(self.src) - cml = RESULT_DIR + ("{}_subset.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def test_grid_subset_big_transposed(self): - # The order of the grid's dimensions (including the X and Y - # dimensions) must not affect the result. - big_grid = self._big_grid() - big_grid.transpose([4, 0, 3, 1, 2]) - for method in self.methods: - regridder = Regridder(self.src, big_grid, method, self.mode) - result = regridder(self.src) - cml = RESULT_DIR + ("{}_subset.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def test_grid_subset_anon(self): - # Must cope OK with anonymous source dimensions. - src = self.src - src.remove_coord("time") - grid = self._grid_subset() - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - cml = RESULT_DIR + ("{}_subset_anon.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def test_grid_subset_missing_data_1(self): - # The destination grid points are entirely contained within the - # src grid points AND we have missing data. - src = self.src - src.data = ma.MaskedArray(src.data) - src.data[:, :, 0, 0] = ma.masked - grid = self._grid_subset() - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - cml = RESULT_DIR + ("{}_subset_masked_1.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def test_grid_subset_missing_data_2(self): - # The destination grid points are entirely contained within the - # src grid points AND we have missing data. - src = self.src - src.data = ma.MaskedArray(src.data) - src.data[:, :, 1, 2] = ma.masked - grid = self._grid_subset() - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - cml = RESULT_DIR + ("{}_subset_masked_2.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def test_grid_partial_overlap(self): - # The destination grid points are partially contained within the - # src grid points. - grid = global_pp()[:4, :4] - grid.coord("longitude").points = np.linspace(-3.3, -3.06, 4) - grid.coord("latitude").points = np.linspace(52.377, 52.43, 4) - for method in self.methods: - regridder = Regridder(self.src, grid, method, self.mode) - result = regridder(self.src) - cml = RESULT_DIR + ("{}_partial_overlap.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def test_grid_no_overlap(self): - # The destination grid points are NOT contained within the - # src grid points. - grid = global_pp()[:4, :4] - grid.coord("longitude").points = np.linspace(-3.3, -3.2, 4) - grid.coord("latitude").points = np.linspace(52.377, 52.43, 4) - for method in self.methods: - regridder = Regridder(self.src, grid, method, self.mode) - result = regridder(self.src) - self.assertCMLApproxData(result, RESULT_DIR + ("no_overlap.cml",)) - - def test_grid_subset_missing_data_aux(self): - # The destination grid points are entirely contained within the - # src grid points AND we have missing data on the aux coordinate. - src = self.src - src.coord("surface_altitude").points[1, 2] = ma.masked - grid = self._grid_subset() - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - cml = RESULT_DIR + ("{}_masked_altitude.cml".format(method),) - self.assertCMLApproxData(result, cml) - - -@tests.skip_data -class Test___call____NOP(tests.IrisTest): - def setUp(self): - # The destination grid points are exactly the same as the - # src grid points. - self.src = realistic_4d()[:5, :2, ::40, ::30] - self.lazy_src = self.src.copy( - da.asarray(self.src.data, chunks=(1, 2) + self.src.shape[2:]) - ) - self.grid = self.src.copy() - - def test_nop__linear(self): - regridder = Regridder(self.src, self.grid, "linear", "mask") - result = regridder(self.src) - self.assertEqual(result, self.src) - - def test_nop__nearest(self): - regridder = Regridder(self.src, self.grid, "nearest", "mask") - result = regridder(self.src) - self.assertEqual(result, self.src) - - def test_nop__linear_lazy(self): - regridder = Regridder(self.lazy_src, self.grid, "linear", "mask") - result = regridder(self.lazy_src) - self.assertEqual(result, self.lazy_src) - - def test_nop__nearest_lazy(self): - regridder = Regridder(self.lazy_src, self.grid, "nearest", "mask") - result = regridder(self.lazy_src) - self.assertEqual(result, self.lazy_src) - - -@tests.skip_data -class Test___call____circular(tests.IrisTest): - def setUp(self): - src = global_pp()[::10, ::10] - level_height = AuxCoord( - 0, - long_name="level_height", - units="m", - attributes={"positive": "up"}, - ) - sigma = AuxCoord(1, long_name="sigma", units="1") - surface_altitude = AuxCoord( - (src.data - src.data.min()) * 50, "surface_altitude", units="m" - ) - src.add_aux_coord(level_height) - src.add_aux_coord(sigma) - src.add_aux_coord(surface_altitude, [0, 1]) - hybrid_height = HybridHeightFactory( - level_height, sigma, surface_altitude - ) - src.add_aux_factory(hybrid_height) - self.src = src - - grid = global_pp()[:4, :4] - grid.coord("longitude").points = grid.coord("longitude").points - 5 - self.grid = grid - self.mode = "mask" - self.methods = ("linear", "nearest") - - def test_non_circular(self): - # Non-circular src -> non-circular grid - for method in self.methods: - regridder = Regridder(self.src, self.grid, method, self.mode) - result = regridder(self.src) - self.assertFalse(result.coord("longitude").circular) - cml = RESULT_DIR + ("{}_non_circular.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def _check_circular_results(self, src_cube, missingmask=""): - results = [] - for method in self.methods: - regridder = Regridder(src_cube, self.grid, method, self.mode) - result = regridder(src_cube) - results.append(result) - self.assertFalse(result.coord("longitude").circular) - cml = RESULT_DIR + ( - "{}_circular_src{}.cml".format(method, missingmask), - ) - self.assertCMLApproxData(result, cml) - return results - - def test_circular_src(self): - # Circular src -> non-circular grid, standard test. - src = self.src - src.coord("longitude").circular = True - self._check_circular_results(src) - - def test_circular_src__masked_missingmask(self): - # Test the special case where src_cube.data.mask is just *False*, - # instead of being an array. - src = self.src - src.coord("longitude").circular = True - src.data = ma.MaskedArray(src.data) - self.assertEqual(src.data.mask, False) - method_results = self._check_circular_results(src, "missingmask") - for method_result in method_results: - self.assertIsInstance(method_result.data.mask, np.ndarray) - self.assertTrue(np.all(method_result.data.mask == np.array(False))) - - def test_circular_src__masked(self): - # Test that masked source points produce the expected masked results. - - # Define source + destination sample points. - # Note: these are chosen to avoid any marginal edge-cases, such as - # where a destination value matches a source point (for 'linear'), or a - # half-way point (for 'nearest'). - src_x = [0.0, 60.0, 120.0, 180.0, 240.0, 300.0] - dst_x = [20.0, 80.0, 140.0, 200.0, 260.0, 320.0] - src_y = [100.0, 200.0, 300.0, 400.0, 500.0] - dst_y = [40.0, 140.0, 240.0, 340.0, 440.0, 540.0] - - # Define the expected result masks for the tested methods, - # when just the middle source point is masked... - result_masks = { - "nearest": np.array( - [ - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - ], - dtype=bool, - ), - "linear": np.array( - [ - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 1, 1, 0, 0, 0], - [0, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - ], - dtype=bool, - ), - } - - # Cook up some distinctive data values. - src_nx, src_ny, dst_nx, dst_ny = ( - len(dd) for dd in (src_x, src_y, dst_x, dst_y) - ) - data_x = np.arange(src_nx).reshape((1, src_nx)) - data_y = np.arange(src_ny).reshape((src_ny, 1)) - data = 3.0 + data_x + 20.0 * data_y - - # Make src and dst test cubes. - def make_2d_cube(x_points, y_points, data): - cube = Cube(data) - y_coord = DimCoord( - y_points, standard_name="latitude", units="degrees" - ) - x_coord = DimCoord( - x_points, standard_name="longitude", units="degrees" - ) - x_coord.circular = True - cube.add_dim_coord(y_coord, 0) - cube.add_dim_coord(x_coord, 1) - return cube - - src_cube_full = make_2d_cube(src_x, src_y, data) - dst_cube = make_2d_cube(dst_x, dst_y, np.zeros((dst_ny, dst_nx))) - - src_cube_masked = src_cube_full.copy() - src_cube_masked.data = ma.array( - src_cube_masked.data, mask=np.zeros((src_ny, src_nx)) - ) - - # Mask the middle source point, and give it a huge underlying data - # value to ensure that it does not take any part in the results. - src_cube_masked.data[2, 2] = 1e19 - src_cube_masked.data.mask[2, 2] = True - - # Test results against the unmasked operation, for each method. - for method in self.methods: - regridder = Regridder( - src_cube_full, dst_cube, method, extrapolation_mode="nan" - ) - result_basic = regridder(src_cube_full) - result_masked = regridder(src_cube_masked) - # Check we get a masked result - self.assertIsInstance(result_masked.data, ma.MaskedArray) - # Check that the result matches the basic one, except for being - # masked at the specific expected points. - expected_result_data = ma.array(result_basic.data) - expected_result_data.mask = result_masks[method] - self.assertMaskedArrayEqual( - result_masked.data, expected_result_data - ) - - def test_circular_grid(self): - # Non-circular src -> circular grid - grid = self.grid - grid.coord("longitude").circular = True - for method in self.methods: - regridder = Regridder(self.src, grid, method, self.mode) - result = regridder(self.src) - self.assertTrue(result.coord("longitude").circular) - cml = RESULT_DIR + ("{}_circular_grid.cml".format(method),) - self.assertCMLApproxData(result, cml) - - def test_circular_src_and_grid(self): - # Circular src -> circular grid - src = self.src - src.coord("longitude").circular = True - grid = self.grid - grid.coord("longitude").circular = True - for method in self.methods: - regridder = Regridder(src, grid, method, self.mode) - result = regridder(src) - self.assertTrue(result.coord("longitude").circular) - cml = RESULT_DIR + ("{}_both_circular.cml".format(method),) - self.assertCMLApproxData(result, cml) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py b/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py deleted file mode 100644 index 9b0160aee4..0000000000 --- a/lib/iris/tests/unit/analysis/regrid/test__CurvilinearRegridder.py +++ /dev/null @@ -1,397 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.analysis._regrid.CurvilinearRegridder`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.analysis._regrid import CurvilinearRegridder as Regridder -from iris.analysis.cartography import rotate_pole -from iris.aux_factory import HybridHeightFactory -from iris.coord_systems import GeogCS, RotatedGeogCS -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube -from iris.fileformats.pp import EARTH_RADIUS -from iris.tests.stock import global_pp, lat_lon_cube, realistic_4d - -RESULT_DIR = ("analysis", "regrid") - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.src_grid = lat_lon_cube() - self.bad = np.ones((3, 4)) - self.weights = np.ones(self.src_grid.shape, self.src_grid.dtype) - - def test_bad_src_type(self): - with self.assertRaisesRegex(TypeError, "'src_grid_cube'"): - Regridder(self.bad, self.src_grid, self.weights) - - def test_bad_grid_type(self): - with self.assertRaisesRegex(TypeError, "'target_grid_cube'"): - Regridder(self.src_grid, self.bad, self.weights) - - -@tests.skip_data -class Test___call__(tests.IrisTest): - def setUp(self): - self.func_setup = ( - "iris.analysis._regrid." - "_regrid_weighted_curvilinear_to_rectilinear__prepare" - ) - self.func_operate = ( - "iris.analysis._regrid." - "_regrid_weighted_curvilinear_to_rectilinear__perform" - ) - # Define a test source grid and target grid, basically the same. - self.src_grid = global_pp() - self.tgt_grid = global_pp() - # Modify the names so we can tell them apart. - self.src_grid.rename("src_grid") - self.tgt_grid.rename("TARGET_GRID") - # Replace the source-grid x and y coords with equivalent 2d versions. - x_coord = self.src_grid.coord("longitude") - y_coord = self.src_grid.coord("latitude") - (nx,) = x_coord.shape - (ny,) = y_coord.shape - xx, yy = np.meshgrid(x_coord.points, y_coord.points) - self.src_grid.remove_coord(x_coord) - self.src_grid.remove_coord(y_coord) - x_coord_2d = AuxCoord( - xx, - standard_name=x_coord.standard_name, - units=x_coord.units, - coord_system=x_coord.coord_system, - ) - y_coord_2d = AuxCoord( - yy, - standard_name=y_coord.standard_name, - units=y_coord.units, - coord_system=y_coord.coord_system, - ) - self.src_grid.add_aux_coord(x_coord_2d, (0, 1)) - self.src_grid.add_aux_coord(y_coord_2d, (0, 1)) - self.weights = np.ones(self.src_grid.shape, self.src_grid.dtype) - # Define an actual, dummy cube for the internal partial result, so we - # can do a cubelist merge on it, which is too complicated to mock out. - self.dummy_slice_result = Cube([1]) - - def test_same_src_as_init(self): - # Check the regridder call calls the underlying routines as expected. - src_grid = self.src_grid - target_grid = self.tgt_grid - regridder = Regridder(src_grid, target_grid, self.weights) - with mock.patch( - self.func_setup, return_value=mock.sentinel.regrid_info - ) as patch_setup: - with mock.patch( - self.func_operate, return_value=self.dummy_slice_result - ) as patch_operate: - result = regridder(src_grid) - patch_setup.assert_called_once_with( - src_grid, self.weights, target_grid - ) - patch_operate.assert_called_once_with( - src_grid, mock.sentinel.regrid_info - ) - # The result is a re-merged version of the internal result, so it is - # therefore '==' but not the same object. - self.assertEqual(result, self.dummy_slice_result) - - def test_no_weights(self): - # Check we can use the regridder without weights. - src_grid = self.src_grid - target_grid = self.tgt_grid - regridder = Regridder(src_grid, target_grid) - with mock.patch( - self.func_setup, return_value=mock.sentinel.regrid_info - ) as patch_setup: - with mock.patch( - self.func_operate, return_value=self.dummy_slice_result - ): - _ = regridder(src_grid) - patch_setup.assert_called_once_with(src_grid, None, target_grid) - - def test_diff_src_from_init(self): - # Check we can call the regridder with a different cube from the one we - # built it with. - src_grid = self.src_grid - target_grid = self.tgt_grid - regridder = Regridder(src_grid, target_grid, self.weights) - # Provide a "different" cube for the actual regrid. - different_src_cube = self.src_grid.copy() - # Rename so we can distinguish them. - different_src_cube.rename("Different_source") - with mock.patch( - self.func_setup, return_value=mock.sentinel.regrid_info - ): - with mock.patch( - self.func_operate, return_value=self.dummy_slice_result - ) as patch_operate: - _ = regridder(different_src_cube) - patch_operate.assert_called_once_with( - different_src_cube, mock.sentinel.regrid_info - ) - - def test_caching(self): - # Check that it calculates regrid info just once, and re-uses it in - # subsequent calls. - src_grid = self.src_grid - target_grid = self.tgt_grid - regridder = Regridder(src_grid, target_grid, self.weights) - different_src_cube = self.src_grid.copy() - different_src_cube.rename("Different_source") - with mock.patch( - self.func_setup, return_value=mock.sentinel.regrid_info - ) as patch_setup: - with mock.patch( - self.func_operate, return_value=self.dummy_slice_result - ) as patch_operate: - _ = regridder(src_grid) - _ = regridder(different_src_cube) - patch_setup.assert_called_once_with( - src_grid, self.weights, target_grid - ) - self.assertEqual(len(patch_operate.call_args_list), 2) - self.assertEqual( - patch_operate.call_args_list, - [ - mock.call(src_grid, mock.sentinel.regrid_info), - mock.call(different_src_cube, mock.sentinel.regrid_info), - ], - ) - - -class Test__derived_coord(tests.IrisTest): - def setUp(self): - src = realistic_4d()[0] - tgt = realistic_4d() - new_lon, new_lat = np.meshgrid( - src.coord("grid_longitude").points, - src.coord("grid_latitude").points, - ) - coord_system = src.coord("grid_latitude").coord_system - lat = AuxCoord( - new_lat, standard_name="latitude", coord_system=coord_system - ) - lon = AuxCoord( - new_lon, standard_name="longitude", coord_system=coord_system - ) - lat_t = AuxCoord( - new_lat.T, standard_name="latitude", coord_system=coord_system - ) - lon_t = AuxCoord( - new_lon.T, standard_name="longitude", coord_system=coord_system - ) - - src.remove_coord("grid_latitude") - src.remove_coord("grid_longitude") - src_t = src.copy() - src.add_aux_coord(lat, [1, 2]) - src.add_aux_coord(lon, [1, 2]) - src_t.add_aux_coord(lat_t, [2, 1]) - src_t.add_aux_coord(lon_t, [2, 1]) - self.src = src.copy() - self.src_t = src_t - self.tgt = tgt - self.altitude = src.coord("altitude") - transposed_src = src.copy() - transposed_src.transpose([0, 2, 1]) - self.altitude_transposed = transposed_src.coord("altitude") - - def test_no_transpose(self): - rg = Regridder(self.src, self.tgt) - res = rg(self.src) - - assert len(res.aux_factories) == 1 and isinstance( - res.aux_factories[0], HybridHeightFactory - ) - assert np.allclose(res.coord("altitude").points, self.altitude.points) - - def test_cube_transposed(self): - rg = Regridder(self.src, self.tgt) - transposed_cube = self.src.copy() - transposed_cube.transpose([0, 2, 1]) - res = rg(transposed_cube) - - assert len(res.aux_factories) == 1 and isinstance( - res.aux_factories[0], HybridHeightFactory - ) - assert np.allclose( - res.coord("altitude").points, self.altitude_transposed.points - ) - - def test_coord_transposed(self): - rg = Regridder(self.src_t, self.tgt) - res = rg(self.src_t) - - assert len(res.aux_factories) == 1 and isinstance( - res.aux_factories[0], HybridHeightFactory - ) - assert np.allclose( - res.coord("altitude").points, self.altitude_transposed.points - ) - - def test_both_transposed(self): - rg = Regridder(self.src_t, self.tgt) - transposed_cube = self.src_t.copy() - transposed_cube.transpose([0, 2, 1]) - res = rg(transposed_cube) - - assert len(res.aux_factories) == 1 and isinstance( - res.aux_factories[0], HybridHeightFactory - ) - assert np.allclose(res.coord("altitude").points, self.altitude.points) - - -@tests.skip_data -class Test___call____bad_src(tests.IrisTest): - def setUp(self): - self.src_grid = global_pp() - y = self.src_grid.coord("latitude") - x = self.src_grid.coord("longitude") - self.src_grid.remove_coord("latitude") - self.src_grid.remove_coord("longitude") - self.src_grid.add_aux_coord(y, 0) - self.src_grid.add_aux_coord(x, 1) - weights = np.ones(self.src_grid.shape, self.src_grid.dtype) - self.regridder = Regridder(self.src_grid, self.src_grid, weights) - - def test_bad_src_type(self): - with self.assertRaisesRegex(TypeError, "must be a Cube"): - self.regridder(np.ones((3, 4))) - - def test_bad_src_shape(self): - with self.assertRaisesRegex( - ValueError, "not defined on the same source grid" - ): - self.regridder(self.src_grid[::2, ::2]) - - -class Test__call__multidimensional(tests.IrisTest): - def test_multidim(self): - # Testing with >2D data to demonstrate correct operation over - # additional non-XY dimensions (including data masking), which is - # handled by the PointInCell wrapper class. - - # Define a simple target grid first, in plain latlon coordinates. - plain_latlon_cs = GeogCS(EARTH_RADIUS) - grid_x_coord = DimCoord( - points=[15.0, 25.0, 35.0], - bounds=[[10.0, 20.0], [20.0, 30.0], [30.0, 40.0]], - standard_name="longitude", - units="degrees", - coord_system=plain_latlon_cs, - ) - grid_y_coord = DimCoord( - points=[-30.0, -50.0], - bounds=[[-20.0, -40.0], [-40.0, -60.0]], - standard_name="latitude", - units="degrees", - coord_system=plain_latlon_cs, - ) - grid_cube = Cube(np.zeros((2, 3))) - grid_cube.add_dim_coord(grid_y_coord, 0) - grid_cube.add_dim_coord(grid_x_coord, 1) - - # Define some key points in true-lat/lon that have known positions - # First 3x2 points in the centre of each output cell. - x_centres, y_centres = np.meshgrid( - grid_x_coord.points, grid_y_coord.points - ) - # An extra point also falling in cell 1, 1 - x_in11, y_in11 = 26.3, -48.2 - # An extra point completely outside the target grid - x_out, y_out = 70.0, -40.0 - - # Define a rotated coord system for the source data - pole_lon, pole_lat = -125.3, 53.4 - src_cs = RotatedGeogCS( - grid_north_pole_latitude=pole_lat, - grid_north_pole_longitude=pole_lon, - ellipsoid=plain_latlon_cs, - ) - - # Concatenate all the testpoints in a flat array, and find the rotated - # equivalents. - xx = list(x_centres.flat[:]) + [x_in11, x_out] - yy = list(y_centres.flat[:]) + [y_in11, y_out] - xx, yy = rotate_pole( - lons=np.array(xx), - lats=np.array(yy), - pole_lon=pole_lon, - pole_lat=pole_lat, - ) - # Define handy index numbers for all these. - i00, i01, i02, i10, i11, i12, i_in, i_out = range(8) - - # Build test data in the shape Z,YX = (3, 8) - data = [ - [1, 2, 3, 11, 12, 13, 7, 99], - [1, 2, 3, 11, 12, 13, 7, 99], - [7, 6, 5, 51, 52, 53, 12, 1], - ] - mask = [ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 1, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - ] - src_data = np.ma.array(data, mask=mask, dtype=float) - - # Make the source cube. - src_cube = Cube(src_data) - src_x = AuxCoord( - xx, - standard_name="grid_longitude", - units="degrees", - coord_system=src_cs, - ) - src_y = AuxCoord( - yy, - standard_name="grid_latitude", - units="degrees", - coord_system=src_cs, - ) - src_z = DimCoord(np.arange(3), long_name="z") - src_cube.add_dim_coord(src_z, 0) - src_cube.add_aux_coord(src_x, 1) - src_cube.add_aux_coord(src_y, 1) - # Add in some extra metadata, to ensure it gets copied over. - src_cube.add_aux_coord(DimCoord([0], long_name="extra_scalar_coord")) - src_cube.attributes["extra_attr"] = 12.3 - - # Define what the expected answers should be, shaped (3, 2, 3). - expected_result = [ - [[1.0, 2.0, 3.0], [11.0, 0.5 * (12 + 7), 13.0]], - [[1.0, -999, 3.0], [11.0, 12.0, 13.0]], - [[7.0, 6.0, 5.0], [51.0, 0.5 * (52 + 12), 53.0]], - ] - expected_result = np.ma.masked_less(expected_result, 0) - - # Perform the calculation with the regridder. - regridder = Regridder(src_cube, grid_cube) - - # Check all is as expected. - result = regridder(src_cube) - self.assertEqual(result.coord("z"), src_cube.coord("z")) - self.assertEqual( - result.coord("extra_scalar_coord"), - src_cube.coord("extra_scalar_coord"), - ) - self.assertEqual( - result.coord("longitude"), grid_cube.coord("longitude") - ) - self.assertEqual(result.coord("latitude"), grid_cube.coord("latitude")) - self.assertMaskedArrayAlmostEqual(result.data, expected_result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/scipy_interpolate/__init__.py b/lib/iris/tests/unit/analysis/scipy_interpolate/__init__.py deleted file mode 100644 index 67218194c2..0000000000 --- a/lib/iris/tests/unit/analysis/scipy_interpolate/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis.scipy_interpolate` module.""" diff --git a/lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py b/lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py deleted file mode 100644 index f0aa027baa..0000000000 --- a/lib/iris/tests/unit/analysis/scipy_interpolate/test__RegularGridInterpolator.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the -:func:`iris.analysis._scipy_interpolate._RegularGridInterpolator` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -from scipy.sparse import csr_matrix - -from iris.analysis._scipy_interpolate import _RegularGridInterpolator -import iris.tests.stock as stock - - -class Test(tests.IrisTest): - def setUp(self): - # Load a source cube, then generate an interpolator instance, calculate - # the interpolation weights and set up a target grid. - self.cube = stock.simple_2d() - x_points = self.cube.coord("bar").points - y_points = self.cube.coord("foo").points - self.interpolator = _RegularGridInterpolator( - [x_points, y_points], - self.cube.data, - method="linear", - bounds_error=False, - fill_value=None, - ) - newx = x_points + 0.7 - newy = y_points + 0.7 - - d_0 = self.cube.data[0, 0] - d_1 = self.cube.data[0, 1] - d_2 = self.cube.data[1, 0] - d_3 = self.cube.data[1, 1] - px_0, px_1 = x_points[0], x_points[1] - py_0, py_1 = y_points[0], y_points[1] - px_t = px_0 + 0.7 - py_t = py_0 + 0.7 - dyt_0 = self._interpolate_point(py_t, py_0, py_1, d_0, d_1) - dyt_1 = self._interpolate_point(py_t, py_0, py_1, d_2, d_3) - self.test_increment = self._interpolate_point( - px_t, px_0, px_1, dyt_0, dyt_1 - ) - - xv, yv = np.meshgrid(newy, newx) - self.tgrid = np.dstack((yv, xv)) - self.weights = self.interpolator.compute_interp_weights(self.tgrid) - - @staticmethod - def _interpolate_point(p_t, p_0, p_1, d_0, d_1): - return d_0 + (d_1 - d_0) * ((p_t - p_0) / (p_1 - p_0)) - - def test_compute_interp_weights(self): - weights = self.weights - self.assertIsInstance(weights, tuple) - self.assertEqual(len(weights), 5) - self.assertEqual(weights[0], self.tgrid.shape) - self.assertEqual(weights[1], "linear") - self.assertIsInstance(weights[2], csr_matrix) - - def test__evaluate_linear_sparse(self): - interpolator = self.interpolator - weights = self.weights - output_data = interpolator._evaluate_linear_sparse(weights[2]) - test_data = self.cube.data.reshape(-1) + self.test_increment - self.assertArrayAlmostEqual(output_data, test_data) - - def test_interp_using_pre_computed_weights(self): - interpolator = self.interpolator - weights = self.weights - output_data = interpolator.interp_using_pre_computed_weights(weights) - test_data = self.cube.data + self.test_increment - self.assertEqual(output_data.shape, self.cube.data.shape) - self.assertArrayAlmostEqual(output_data, test_data) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/stats/__init__.py b/lib/iris/tests/unit/analysis/stats/__init__.py deleted file mode 100644 index 0b896d648d..0000000000 --- a/lib/iris/tests/unit/analysis/stats/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis.stats` module.""" diff --git a/lib/iris/tests/unit/analysis/stats/test_pearsonr.py b/lib/iris/tests/unit/analysis/stats/test_pearsonr.py deleted file mode 100644 index 63cf4e2abe..0000000000 --- a/lib/iris/tests/unit/analysis/stats/test_pearsonr.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.analysis.stats.pearsonr` function.""" - -# Import iris tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -import iris -import iris.analysis.stats as stats -from iris.exceptions import CoordinateNotFoundError - - -@tests.skip_data -class Test(tests.IrisTest): - def setUp(self): - # 3D cubes: - cube_temp = iris.load_cube( - tests.get_data_path( - ("NetCDF", "global", "xyt", "SMALL_total_column_co2.nc") - ) - ) - self.cube_a = cube_temp[0:6] - self.cube_b = cube_temp[20:26] - self.cube_b.replace_coord(self.cube_a.coord("time").copy()) - cube_temp = self.cube_a.copy() - cube_temp.coord("latitude").guess_bounds() - cube_temp.coord("longitude").guess_bounds() - self.weights = iris.analysis.cartography.area_weights(cube_temp) - - def test_perfect_corr(self): - r = stats.pearsonr(self.cube_a, self.cube_a, ["latitude", "longitude"]) - self.assertArrayEqual(r.data, np.array([1.0] * 6)) - - def test_perfect_corr_all_dims(self): - r = stats.pearsonr(self.cube_a, self.cube_a) - self.assertArrayEqual(r.data, np.array([1.0])) - - def test_incompatible_cubes(self): - with self.assertRaises(ValueError): - stats.pearsonr( - self.cube_a[:, 0, :], self.cube_b[0, :, :], "longitude" - ) - - def test_compatible_cubes(self): - r = stats.pearsonr(self.cube_a, self.cube_b, ["latitude", "longitude"]) - self.assertArrayAlmostEqual( - r.data, - [ - 0.81114936, - 0.81690538, - 0.79833135, - 0.81118674, - 0.79745386, - 0.81278484, - ], - ) - - def test_broadcast_cubes(self): - r1 = stats.pearsonr( - self.cube_a, self.cube_b[0, :, :], ["latitude", "longitude"] - ) - r2 = stats.pearsonr( - self.cube_b[0, :, :], self.cube_a, ["latitude", "longitude"] - ) - r_by_slice = [ - stats.pearsonr( - self.cube_a[i, :, :], - self.cube_b[0, :, :], - ["latitude", "longitude"], - ).data - for i in range(6) - ] - self.assertArrayEqual(r1.data, np.array(r_by_slice)) - self.assertArrayEqual(r2.data, np.array(r_by_slice)) - - def test_compatible_cubes_weighted(self): - r = stats.pearsonr( - self.cube_a, self.cube_b, ["latitude", "longitude"], self.weights - ) - self.assertArrayAlmostEqual( - r.data, - [ - 0.79105429, - 0.79988078, - 0.78825089, - 0.79925653, - 0.79009810, - 0.80115292, - ], - ) - - def test_broadcast_cubes_weighted(self): - r = stats.pearsonr( - self.cube_a, - self.cube_b[0, :, :], - ["latitude", "longitude"], - weights=self.weights[0, :, :], - ) - r_by_slice = [ - stats.pearsonr( - self.cube_a[i, :, :], - self.cube_b[0, :, :], - ["latitude", "longitude"], - weights=self.weights[0, :, :], - ).data - for i in range(6) - ] - self.assertArrayAlmostEqual(r.data, np.array(r_by_slice)) - - def test_weight_error(self): - with self.assertRaises(ValueError): - stats.pearsonr( - self.cube_a, - self.cube_b[0, :, :], - ["latitude", "longitude"], - weights=self.weights, - ) - - def test_non_existent_coord(self): - with self.assertRaises(CoordinateNotFoundError): - stats.pearsonr(self.cube_a, self.cube_b, "bad_coord") - - def test_mdtol(self): - cube_small = self.cube_a[:, 0, 0] - cube_small_masked = cube_small.copy() - cube_small_masked.data = ma.array( - cube_small.data, mask=np.array([0, 0, 0, 1, 1, 1], dtype=bool) - ) - r1 = stats.pearsonr(cube_small, cube_small_masked) - r2 = stats.pearsonr(cube_small, cube_small_masked, mdtol=0.49) - self.assertArrayAlmostEqual(r1.data, np.array([0.74586593])) - self.assertMaskedArrayEqual(r2.data, ma.array([0], mask=[True])) - - def test_common_mask_simple(self): - cube_small = self.cube_a[:, 0, 0] - cube_small_masked = cube_small.copy() - cube_small_masked.data = ma.array( - cube_small.data, mask=np.array([0, 0, 0, 1, 1, 1], dtype=bool) - ) - r = stats.pearsonr(cube_small, cube_small_masked, common_mask=True) - self.assertArrayAlmostEqual(r.data, np.array([1.0])) - - def test_common_mask_broadcast(self): - cube_small = self.cube_a[:, 0, 0] - cube_small_2d = self.cube_a[:, 0:2, 0] - cube_small.data = ma.array( - cube_small.data, mask=np.array([0, 0, 0, 0, 0, 1], dtype=bool) - ) - cube_small_2d.data = ma.array( - np.tile(cube_small.data[:, np.newaxis], 2), - mask=np.zeros((6, 2), dtype=bool), - ) - # 2d mask varies on unshared coord: - cube_small_2d.data.mask[0, 1] = 1 - r = stats.pearsonr( - cube_small, - cube_small_2d, - weights=self.weights[:, 0, 0], - common_mask=True, - ) - self.assertArrayAlmostEqual(r.data, np.array([1.0, 1.0])) - # 2d mask does not vary on unshared coord: - cube_small_2d.data.mask[0, 0] = 1 - r = stats.pearsonr(cube_small, cube_small_2d, common_mask=True) - self.assertArrayAlmostEqual(r.data, np.array([1.0, 1.0])) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_Aggregator.py b/lib/iris/tests/unit/analysis/test_Aggregator.py deleted file mode 100644 index ec837ea49a..0000000000 --- a/lib/iris/tests/unit/analysis/test_Aggregator.py +++ /dev/null @@ -1,335 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.analysis.Aggregator` class instance.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np -import numpy.ma as ma - -from iris.analysis import Aggregator -from iris.exceptions import LazyAggregatorError - - -class Test_aggregate(tests.IrisTest): - # These unit tests don't call a data aggregation function, they call a - # mocked one i.e. the return values of the mocked data aggregation - # function don't matter, only how these are dealt with by the aggregate - # method. - def setUp(self): - self.TEST = Aggregator("test", None) - self.array = ma.array( - [[1, 2, 3], [4, 5, 6]], - mask=[[False, True, False], [True, False, False]], - dtype=np.float64, - ) - self.expected_result_axis0 = ma.array([1, 2, 3], mask=None) - self.expected_result_axis1 = ma.array([4, 5], mask=None) - - def test_masked_notol(self): - # Providing masked array with no tolerance keyword (mdtol) provided. - axis = 0 - mock_return = self.expected_result_axis0.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis) - self.assertMaskedArrayEqual(result, self.expected_result_axis0) - mock_method.assert_called_once_with(self.array, axis=axis) - - axis = 1 - mock_return = self.expected_result_axis1.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis) - self.assertMaskedArrayEqual(result, self.expected_result_axis1) - mock_method.assert_called_once_with(self.array, axis=axis) - - def test_masked_above_tol(self): - # Providing masked array with a high tolerance (mdtol) provided. - axis = 0 - mock_return = self.expected_result_axis0.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis, mdtol=0.55) - self.assertMaskedArrayEqual(result, self.expected_result_axis0) - mock_method.assert_called_once_with(self.array, axis=axis) - - axis = 1 - mock_return = self.expected_result_axis1.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis, mdtol=0.55) - self.assertMaskedArrayEqual(result, self.expected_result_axis1) - mock_method.assert_called_once_with(self.array, axis=axis) - - def test_masked_below_tol(self): - # Providing masked array with a tolerance on missing values, low - # enough to modify the resulting mask for axis 0. - axis = 0 - result_axis_0 = self.expected_result_axis0.copy() - result_axis_0.mask = np.array([True, True, False]) - mock_return = ma.array([1, 2, 3], mask=None) - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis, mdtol=0.45) - self.assertMaskedArrayAlmostEqual(result, result_axis_0) - mock_method.assert_called_once_with(self.array, axis=axis) - - axis = 1 - mock_return = self.expected_result_axis1.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis, mdtol=0.45) - self.assertMaskedArrayEqual(result, self.expected_result_axis1) - mock_method.assert_called_once_with(self.array, axis=axis) - - def test_masked_below_tol_alt(self): - # Providing masked array with a tolerance on missing values, low - # enough to modify the resulting mask for axis 1. - axis = 1 - result_axis_1 = self.expected_result_axis1.copy() - result_axis_1.mask = np.array([True, True]) - mock_return = self.expected_result_axis1.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis, mdtol=0.1) - self.assertMaskedArrayAlmostEqual(result, result_axis_1) - mock_method.assert_called_once_with(self.array, axis=axis) - - def test_unmasked_with_mdtol(self): - # Providing aggregator with an unmasked array and tolerance specified - # for missing data - ensure that result is unaffected. - data = self.array.data - - axis = 0 - mock_return = self.expected_result_axis0.data.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(data, axis, mdtol=0.5) - self.assertArrayAlmostEqual(result, mock_return.copy()) - mock_method.assert_called_once_with(data, axis=axis) - - axis = 1 - mock_return = self.expected_result_axis1.data.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(data, axis, mdtol=0.5) - self.assertArrayAlmostEqual(result, mock_return.copy()) - mock_method.assert_called_once_with(data, axis=axis) - - def test_unmasked(self): - # Providing aggregator with an unmasked array and no additional keyword - # arguments ensure that result is unaffected. - data = self.array.data - - axis = 0 - mock_return = self.expected_result_axis0.data.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(data, axis) - self.assertArrayAlmostEqual(result, mock_return.copy()) - mock_method.assert_called_once_with(data, axis=axis) - - axis = 1 - mock_return = self.expected_result_axis1.data.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(data, axis) - self.assertArrayAlmostEqual(result, mock_return.copy()) - mock_method.assert_called_once_with(data, axis=axis) - - def test_allmasked_1D_with_mdtol(self): - data = ma.masked_all((3,)) - axis = 0 - mdtol = 0.5 - mock_return = ma.masked - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(data, axis, mdtol=mdtol) - - self.assertIs(result, mock_return) - mock_method.assert_called_once_with(data, axis=axis) - - def test_returning_scalar_mdtol(self): - # Test the case when the data aggregation function returns a scalar and - # turns it into a masked array. - axis = -1 - data = self.array.flatten() - mock_return = 2 - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(data, axis, mdtol=1) - self.assertMaskedArrayEqual(result, ma.array(2, mask=False)) - mock_method.assert_called_once_with(data, axis=axis) - - def test_returning_scalar_mdtol_alt(self): - # Test the case when the data aggregation function returns a scalar - # with no tolerance for missing data values and turns it into a masked - # array. - axis = -1 - data = self.array.flatten() - mock_return = 2 - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(data, axis, mdtol=0) - self.assertMaskedArrayEqual(result, ma.array(2, mask=True)) - mock_method.assert_called_once_with(data, axis=axis) - - def test_returning_non_masked_array_from_masked_array(self): - # Providing a masked array, call_func returning a non-masked array, - # resulting in a masked array output. - axis = 0 - mock_return = self.expected_result_axis0.data.copy() - result_axis_0 = ma.array(mock_return, mask=[True, True, False]) - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis, mdtol=0.45) - self.assertMaskedArrayAlmostEqual(result, result_axis_0) - mock_method.assert_called_once_with(self.array, axis=axis) - - axis = 1 - mock_return = self.expected_result_axis1.data.copy() - with mock.patch.object( - self.TEST, "call_func", return_value=mock_return - ) as mock_method: - result = self.TEST.aggregate(self.array, axis, mdtol=0.45) - self.assertMaskedArrayEqual(result, self.expected_result_axis1) - mock_method.assert_called_once_with(self.array, axis=axis) - - def test_kwarg_pass_through_no_kwargs(self): - call_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - aggregator = Aggregator("", call_func) - aggregator.aggregate(data, axis) - call_func.assert_called_once_with(data, axis=axis) - - def test_kwarg_pass_through_call_kwargs(self): - call_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - kwargs = dict(wibble="wobble", foo="bar") - aggregator = Aggregator("", call_func) - aggregator.aggregate(data, axis, **kwargs) - call_func.assert_called_once_with(data, axis=axis, **kwargs) - - def test_kwarg_pass_through_init_kwargs(self): - call_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - kwargs = dict(wibble="wobble", foo="bar") - aggregator = Aggregator("", call_func, **kwargs) - aggregator.aggregate(data, axis) - call_func.assert_called_once_with(data, axis=axis, **kwargs) - - def test_kwarg_pass_through_combined_kwargs(self): - call_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - init_kwargs = dict(wibble="wobble", var=1.0) - call_kwargs = dict(foo="foo", var=0.5) - aggregator = Aggregator("", call_func, **init_kwargs) - aggregator.aggregate(data, axis, **call_kwargs) - expected_kwargs = init_kwargs.copy() - expected_kwargs.update(call_kwargs) - call_func.assert_called_once_with(data, axis=axis, **expected_kwargs) - - def test_mdtol_intercept(self): - call_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - aggregator = Aggregator("", call_func) - aggregator.aggregate(data, axis, wibble="wobble", mdtol=0.8) - call_func.assert_called_once_with(data, axis=axis, wibble="wobble") - - def test_no_lazy_func(self): - dummy_agg = Aggregator("custom_op", lambda x: 1) - expected = "custom_op aggregator does not support lazy operation" - with self.assertRaisesRegex(LazyAggregatorError, expected): - dummy_agg.lazy_aggregate(np.arange(10), axis=0) - - -class Test_update_metadata(tests.IrisTest): - def test_no_units_change(self): - # If the Aggregator has no units_func then the units should be - # left unchanged. - aggregator = Aggregator("", None) - cube = mock.Mock(units=mock.sentinel.units) - aggregator.update_metadata(cube, []) - self.assertIs(cube.units, mock.sentinel.units) - - def test_units_change(self): - # If the Aggregator has a units_func then the new units should - # be defined by its return value. - units_func = mock.Mock(return_value=mock.sentinel.new_units) - aggregator = Aggregator("", None, units_func) - cube = mock.Mock(units=mock.sentinel.units) - aggregator.update_metadata(cube, []) - units_func.assert_called_once_with(mock.sentinel.units) - self.assertEqual(cube.units, mock.sentinel.new_units) - - -class Test_lazy_aggregate(tests.IrisTest): - def test_kwarg_pass_through_no_kwargs(self): - lazy_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - aggregator = Aggregator("", None, lazy_func=lazy_func) - aggregator.lazy_aggregate(data, axis) - lazy_func.assert_called_once_with(data, axis=axis) - - def test_kwarg_pass_through_call_kwargs(self): - lazy_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - kwargs = dict(wibble="wobble", foo="bar") - aggregator = Aggregator("", None, lazy_func=lazy_func) - aggregator.lazy_aggregate(data, axis, **kwargs) - lazy_func.assert_called_once_with(data, axis=axis, **kwargs) - - def test_kwarg_pass_through_init_kwargs(self): - lazy_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - kwargs = dict(wibble="wobble", foo="bar") - aggregator = Aggregator("", None, lazy_func=lazy_func, **kwargs) - aggregator.lazy_aggregate(data, axis) - lazy_func.assert_called_once_with(data, axis=axis, **kwargs) - - def test_kwarg_pass_through_combined_kwargs(self): - lazy_func = mock.Mock() - data = mock.sentinel.data - axis = mock.sentinel.axis - init_kwargs = dict(wibble="wobble", var=1.0) - call_kwargs = dict(foo="foo", var=0.5) - aggregator = Aggregator("", None, lazy_func=lazy_func, **init_kwargs) - aggregator.lazy_aggregate(data, axis, **call_kwargs) - expected_kwargs = init_kwargs.copy() - expected_kwargs.update(call_kwargs) - lazy_func.assert_called_once_with(data, axis=axis, **expected_kwargs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_AreaWeighted.py b/lib/iris/tests/unit/analysis/test_AreaWeighted.py deleted file mode 100644 index 2454e0817c..0000000000 --- a/lib/iris/tests/unit/analysis/test_AreaWeighted.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.analysis.AreaWeighted`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.analysis import AreaWeighted - - -class Test(tests.IrisTest): - def check_call(self, mdtol=None): - # Check that `iris.analysis.AreaWeighted` correctly calls an - # `iris.analysis._area_weighted.AreaWeightedRegridder` object. - if mdtol is None: - area_weighted = AreaWeighted() - mdtol = 1 - else: - area_weighted = AreaWeighted(mdtol=mdtol) - self.assertEqual(area_weighted.mdtol, mdtol) - - with mock.patch( - "iris.analysis.AreaWeightedRegridder", - return_value=mock.sentinel.regridder, - ) as awr: - regridder = area_weighted.regridder( - mock.sentinel.src, mock.sentinel.target - ) - - awr.assert_called_once_with( - mock.sentinel.src, mock.sentinel.target, mdtol=mdtol - ) - self.assertIs(regridder, mock.sentinel.regridder) - - def test_default(self): - self.check_call() - - def test_specified_mdtol(self): - self.check_call(0.5) - - def test_invalid_high_mdtol(self): - msg = "mdtol must be in range 0 - 1" - with self.assertRaisesRegex(ValueError, msg): - AreaWeighted(mdtol=1.2) - - def test_invalid_low_mdtol(self): - msg = "mdtol must be in range 0 - 1" - with self.assertRaisesRegex(ValueError, msg): - AreaWeighted(mdtol=-0.2) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_COUNT.py b/lib/iris/tests/unit/analysis/test_COUNT.py deleted file mode 100644 index 96274f7cd0..0000000000 --- a/lib/iris/tests/unit/analysis/test_COUNT.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.COUNT` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.analysis import COUNT -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test_basics(tests.IrisTest): - def setUp(self): - data = np.array([1, 2, 3, 4, 5]) - coord = DimCoord([6, 7, 8, 9, 10], long_name="foo") - self.cube = Cube(data) - self.cube.add_dim_coord(coord, 0) - self.lazy_cube = Cube(as_lazy_data(data)) - self.lazy_cube.add_dim_coord(coord, 0) - self.func = lambda x: x >= 3 - - def test_name(self): - self.assertEqual(COUNT.name(), "count") - - def test_no_function(self): - exp_emsg = r"function must be a callable. Got <.* 'NoneType'>" - with self.assertRaisesRegex(TypeError, exp_emsg): - COUNT.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - - def test_not_callable(self): - with self.assertRaisesRegex(TypeError, "function must be a callable"): - COUNT.aggregate(self.cube.data, axis=0, function="wibble") - - def test_lazy_not_callable(self): - with self.assertRaisesRegex(TypeError, "function must be a callable"): - COUNT.lazy_aggregate( - self.lazy_cube.lazy_data(), axis=0, function="wibble" - ) - - def test_collapse(self): - data = COUNT.aggregate(self.cube.data, axis=0, function=self.func) - self.assertArrayEqual(data, [3]) - - def test_lazy(self): - lazy_data = COUNT.lazy_aggregate( - self.lazy_cube.lazy_data(), axis=0, function=self.func - ) - self.assertTrue(is_lazy_data(lazy_data)) - - def test_lazy_collapse(self): - lazy_data = COUNT.lazy_aggregate( - self.lazy_cube.lazy_data(), axis=0, function=self.func - ) - self.assertArrayEqual(lazy_data.compute(), [3]) - - -class Test_units_func(tests.IrisTest): - def test(self): - self.assertIsNotNone(COUNT.units_func) - new_units = COUNT.units_func(None) - self.assertEqual(new_units, 1) - - -class Test_masked(tests.IrisTest): - def setUp(self): - self.cube = Cube(ma.masked_equal([1, 2, 3, 4, 5], 3)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - self.func = lambda x: x >= 3 - - def test_ma(self): - data = COUNT.aggregate(self.cube.data, axis=0, function=self.func) - self.assertArrayEqual(data, [2]) - - -class Test_lazy_masked(tests.IrisTest): - def setUp(self): - lazy_data = as_lazy_data(ma.masked_equal([1, 2, 3, 4, 5], 3)) - self.lazy_cube = Cube(lazy_data) - self.lazy_cube.add_dim_coord( - DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0 - ) - self.func = lambda x: x >= 3 - - def test_ma(self): - lazy_data = COUNT.lazy_aggregate( - self.lazy_cube.lazy_data(), axis=0, function=self.func - ) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [2]) - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(COUNT.aggregate_shape(**kwargs), shape) - kwargs = dict(wibble="wobble") - self.assertTupleEqual(COUNT.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_Linear.py b/lib/iris/tests/unit/analysis/test_Linear.py deleted file mode 100644 index 27565f8c51..0000000000 --- a/lib/iris/tests/unit/analysis/test_Linear.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.analysis.Linear`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.analysis import Linear - - -def create_scheme(mode=None): - kwargs = {} - if mode is not None: - kwargs["extrapolation_mode"] = mode - return Linear(**kwargs) - - -class Test_extrapolation_mode(tests.IrisTest): - def check_mode(self, mode): - linear = create_scheme(mode) - self.assertEqual(linear.extrapolation_mode, mode) - - def test_default(self): - linear = Linear() - self.assertEqual(linear.extrapolation_mode, "linear") - - def test_extrapolate(self): - self.check_mode("extrapolate") - - def test_linear(self): - self.check_mode("linear") - - def test_nan(self): - self.check_mode("nan") - - def test_error(self): - self.check_mode("error") - - def test_mask(self): - self.check_mode("mask") - - def test_nanmask(self): - self.check_mode("nanmask") - - def test_invalid(self): - with self.assertRaisesRegex(ValueError, "Extrapolation mode"): - Linear("bogus") - - -class Test_interpolator(tests.IrisTest): - def check_mode(self, mode=None): - linear = create_scheme(mode) - - # Check that calling `linear.interpolator(...)` returns an - # instance of RectilinearInterpolator which has been created - # using the correct arguments. - with mock.patch( - "iris.analysis.RectilinearInterpolator", - return_value=mock.sentinel.interpolator, - ) as ri: - interpolator = linear.interpolator( - mock.sentinel.cube, mock.sentinel.coords - ) - if mode is None or mode == "linear": - expected_mode = "extrapolate" - else: - expected_mode = mode - ri.assert_called_once_with( - mock.sentinel.cube, mock.sentinel.coords, "linear", expected_mode - ) - self.assertIs(interpolator, mock.sentinel.interpolator) - - def test_default(self): - self.check_mode() - - def test_extrapolate(self): - self.check_mode("extrapolate") - - def test_linear(self): - self.check_mode("linear") - - def test_nan(self): - self.check_mode("nan") - - def test_error(self): - self.check_mode("error") - - def test_mask(self): - self.check_mode("mask") - - def test_nanmask(self): - self.check_mode("nanmask") - - -class Test_regridder(tests.IrisTest): - def check_mode(self, mode=None): - linear = create_scheme(mode) - - # Check that calling `linear.regridder(...)` returns an instance - # of RectilinearRegridder which has been created using the correct - # arguments. - with mock.patch( - "iris.analysis.RectilinearRegridder", - return_value=mock.sentinel.regridder, - ) as lr: - regridder = linear.regridder( - mock.sentinel.src, mock.sentinel.target - ) - if mode is None or mode == "linear": - expected_mode = "extrapolate" - else: - expected_mode = mode - lr.assert_called_once_with( - mock.sentinel.src, mock.sentinel.target, "linear", expected_mode - ) - self.assertIs(regridder, mock.sentinel.regridder) - - def test_default(self): - self.check_mode() - - def test_extrapolate(self): - self.check_mode("extrapolate") - - def test_linear(self): - self.check_mode("linear") - - def test_nan(self): - self.check_mode("nan") - - def test_error(self): - self.check_mode("error") - - def test_mask(self): - self.check_mode("mask") - - def test_nanmask(self): - self.check_mode("nanmask") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_MAX.py b/lib/iris/tests/unit/analysis/test_MAX.py deleted file mode 100644 index 91d4daf1f0..0000000000 --- a/lib/iris/tests/unit/analysis/test_MAX.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.MAX` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.analysis import MAX -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test_basics(tests.IrisTest): - def setUp(self): - data = np.array([1, 2, 3, 4, 5]) - coord = DimCoord([6, 7, 8, 9, 10], long_name="foo") - self.cube = Cube(data) - self.cube.add_dim_coord(coord, 0) - self.lazy_cube = Cube(as_lazy_data(data)) - self.lazy_cube.add_dim_coord(coord, 0) - - def test_name(self): - self.assertEqual(MAX.name(), "maximum") - - def test_collapse(self): - data = MAX.aggregate(self.cube.data, axis=0) - self.assertArrayEqual(data, [5]) - - def test_lazy(self): - lazy_data = MAX.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertTrue(is_lazy_data(lazy_data)) - - def test_lazy_collapse(self): - lazy_data = MAX.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertArrayEqual(lazy_data.compute(), [5]) - - -class Test_masked(tests.IrisTest): - def setUp(self): - self.cube = Cube(ma.masked_greater([1, 2, 3, 4, 5], 3)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - - def test_ma(self): - data = MAX.aggregate(self.cube.data, axis=0) - self.assertArrayEqual(data, [3]) - - -class Test_lazy_masked(tests.IrisTest): - def setUp(self): - masked_data = ma.masked_greater([1, 2, 3, 4, 5], 3) - self.cube = Cube(as_lazy_data(masked_data)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - - def test_lazy_ma(self): - lazy_data = MAX.lazy_aggregate(self.cube.lazy_data(), axis=0) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [3]) - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(MAX.aggregate_shape(**kwargs), shape) - kwargs = dict(wibble="wobble") - self.assertTupleEqual(MAX.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_MAX_RUN.py b/lib/iris/tests/unit/analysis/test_MAX_RUN.py deleted file mode 100755 index 00de383f7a..0000000000 --- a/lib/iris/tests/unit/analysis/test_MAX_RUN.py +++ /dev/null @@ -1,313 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.MAX_RUN` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import dask.array as da -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_concrete_data, is_lazy_data -from iris.analysis import MAX_RUN - - -def bool_func(x): - return x == 1 - - -class UnmaskedTest(tests.IrisTest): - def setUp(self): - """ - Set up 1d and 2d unmasked data arrays for max run testing. - - Uses 1 and 3 rather than 1 and 0 to check that lambda is being applied. - """ - - self.data_1ds = [ - (np.array([3, 1, 1, 3, 3, 3]), 2), # One run - (np.array([3, 1, 1, 3, 1, 3]), 2), # Two runs - (np.array([3, 3, 3, 3, 3, 3]), 0), # No run - (np.array([3, 3, 1, 3, 3, 3]), 1), # Max run of 1 - (np.array([1, 1, 1, 3, 1, 3]), 3), # Run to start - (np.array([3, 1, 3, 1, 1, 1]), 3), # Run to end - (np.array([1, 1, 1, 1, 1, 1]), 6), # All run - ] - - self.data_2d_axis0 = np.array( - [ - [3, 1, 1, 3, 3, 3], # One run - [3, 1, 1, 3, 1, 3], # Two runs - [3, 3, 3, 3, 3, 3], # No run - [3, 3, 1, 3, 3, 3], # Max run of 1 - [1, 1, 1, 3, 1, 3], # Run to start - [3, 1, 3, 1, 1, 1], # Run to end - [1, 1, 1, 1, 1, 1], # All run - ] - ).T - self.expected_2d_axis0 = np.array([2, 2, 0, 1, 3, 3, 6]) - - self.data_2d_axis1 = self.data_2d_axis0.T - self.expected_2d_axis1 = self.expected_2d_axis0 - - -class MaskedTest(tests.IrisTest): - def setUp(self): - """ - Set up 1d and 2d unmasked data arrays for max run testing. - - Uses 1 and 3 rather than 1 and 0 to check that lambda is being applied. - """ - - self.data_1ds = [ - ( - ma.masked_array( - np.array([1, 1, 1, 3, 1, 3]), np.array([0, 0, 0, 0, 0, 0]) - ), - 3, - ), # No mask - ( - ma.masked_array( - np.array([1, 1, 1, 3, 1, 3]), np.array([0, 0, 0, 0, 1, 1]) - ), - 3, - ), # Mask misses run - ( - ma.masked_array( - np.array([1, 1, 1, 3, 1, 3]), np.array([1, 1, 1, 0, 0, 0]) - ), - 1, - ), # Mask max run - ( - ma.masked_array( - np.array([1, 1, 1, 3, 1, 3]), np.array([0, 0, 1, 0, 0, 0]) - ), - 2, - ), # Partially mask run - ( - ma.masked_array( - np.array([3, 1, 1, 1, 1, 3]), np.array([0, 0, 1, 0, 0, 0]) - ), - 2, - ), # Mask interrupts run - ( - ma.masked_array( - np.array([1, 1, 1, 3, 1, 3]), np.array([1, 1, 1, 1, 1, 1]) - ), - 0, - ), # All mask - ( - ma.masked_array( - np.array([1, 1, 1, 3, 1, 3]), np.array([1, 1, 1, 1, 0, 1]) - ), - 1, - ), # All mask or run - ] - - self.data_2d_axis0 = ma.masked_array( - np.array( - [ - [1, 1, 1, 3, 1, 3], - [1, 1, 1, 3, 1, 3], - [1, 1, 1, 3, 1, 3], - [1, 1, 1, 3, 1, 3], - [1, 1, 1, 3, 1, 3], - [1, 1, 1, 3, 1, 3], - ] - ), - np.array( - [ - [0, 0, 0, 0, 0, 0], # No mask - [0, 0, 0, 0, 1, 1], # Mask misses run - [1, 1, 1, 0, 0, 0], # Mask max run - [0, 0, 1, 0, 0, 0], # Partially mask run - [1, 1, 1, 1, 1, 1], # All mask - [1, 1, 1, 1, 0, 1], # All mask or run - ] - ), - ).T - - self.expected_2d_axis0 = np.array([3, 3, 1, 2, 0, 1]) - - self.data_2d_axis1 = self.data_2d_axis0.T - self.expected_2d_axis1 = self.expected_2d_axis0 - - -class RealMixin: - def run_func(self, *args, **kwargs): - return MAX_RUN.call_func(*args, **kwargs) - - def check_array(self, result, expected): - self.assertArrayEqual(result, expected) - - -class LazyMixin: - def run_func(self, *args, **kwargs): - return MAX_RUN.lazy_func(*args, **kwargs) - - def check_array(self, result, expected, expected_chunks): - self.assertTrue(is_lazy_data(result)) - self.assertTupleEqual(result.chunks, expected_chunks) - result = as_concrete_data(result) - self.assertArrayEqual(result, expected) - - -class TestBasic(UnmaskedTest, RealMixin): - def test_1d(self): - for data, expected in self.data_1ds: - result = self.run_func( - data, - axis=0, - function=bool_func, - ) - self.check_array(result, expected) - - def test_2d_axis0(self): - result = self.run_func( - self.data_2d_axis0, - axis=0, - function=bool_func, - ) - self.check_array(result, self.expected_2d_axis0) - - def test_2d_axis1(self): - result = self.run_func( - self.data_2d_axis1, - axis=1, - function=bool_func, - ) - self.check_array(result, self.expected_2d_axis1) - - -class TestLazy(UnmaskedTest, LazyMixin): - def test_1d(self): - for data, expected in self.data_1ds: - data = da.from_array(data) - result = self.run_func( - data, - axis=0, - function=bool_func, - ) - self.check_array(result, expected, ()) - - def test_2d_axis0(self): - data = da.from_array(self.data_2d_axis0) - result = self.run_func( - data, - axis=0, - function=bool_func, - ) - self.check_array( - result, self.expected_2d_axis0, ((len(self.expected_2d_axis0),),) - ) - - def test_2d_axis1(self): - data = da.from_array(self.data_2d_axis1) - result = self.run_func( - data, - axis=1, - function=bool_func, - ) - self.check_array( - result, self.expected_2d_axis1, ((len(self.expected_2d_axis1),),) - ) - - -class TestLazyChunked(UnmaskedTest, LazyMixin): - def test_1d(self): - for data, expected in self.data_1ds: - data = da.from_array(data, chunks=(1,)) - result = self.run_func( - data, - axis=0, - function=bool_func, - ) - self.check_array(result, expected, ()) - - def test_2d_axis0_chunk0(self): - data = da.from_array(self.data_2d_axis0, chunks=(1, -1)) - result = self.run_func( - data, - axis=0, - function=bool_func, - ) - self.check_array( - result, self.expected_2d_axis0, ((len(self.expected_2d_axis0),),) - ) - - def test_2d_axis0_chunk1(self): - data = da.from_array(self.data_2d_axis0, chunks=(-1, 1)) - result = self.run_func( - data, - axis=0, - function=bool_func, - ) - expected_chunks = (tuple([1] * len(self.expected_2d_axis0)),) - self.check_array(result, self.expected_2d_axis0, expected_chunks) - - def test_2d_axis1_chunk0(self): - data = da.from_array(self.data_2d_axis1, chunks=(1, -1)) - result = self.run_func( - data, - axis=1, - function=bool_func, - ) - expected_chunks = (tuple([1] * len(self.expected_2d_axis1)),) - self.check_array(result, self.expected_2d_axis1, expected_chunks) - - def test_2d_axis1_chunk1(self): - data = da.from_array(self.data_2d_axis1, chunks=(-1, 1)) - result = self.run_func( - data, - axis=1, - function=bool_func, - ) - self.check_array( - result, self.expected_2d_axis1, ((len(self.expected_2d_axis1),),) - ) - - -class TestMasked(MaskedTest, RealMixin): - def test_1d(self): - for data, expected in self.data_1ds: - result = self.run_func( - data, - axis=0, - function=bool_func, - ) - self.check_array(result, expected) - - def test_2d_axis0(self): - result = self.run_func( - self.data_2d_axis0, - axis=0, - function=bool_func, - ) - self.check_array(result, self.expected_2d_axis0) - - def test_2d_axis1(self): - result = self.run_func( - self.data_2d_axis1, - axis=1, - function=bool_func, - ) - self.check_array(result, self.expected_2d_axis1) - - -class Test_name(tests.IrisTest): - def test(self): - self.assertEqual(MAX_RUN.name(), "max_run") - - -class Test_cell_method(tests.IrisTest): - def test(self): - self.assertIsNone(MAX_RUN.cell_method) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_MEAN.py b/lib/iris/tests/unit/analysis/test_MEAN.py deleted file mode 100644 index 18e2b4ca6c..0000000000 --- a/lib/iris/tests/unit/analysis/test_MEAN.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.MEAN` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_concrete_data, as_lazy_data -from iris.analysis import MEAN - - -class Test_lazy_aggregate(tests.IrisTest): - def setUp(self): - self.data = ma.arange(12).reshape(3, 4) - self.data.mask = [[0, 0, 0, 1], [0, 0, 1, 1], [0, 1, 1, 1]] - # --> fractions of masked-points in columns = [0, 1/3, 2/3, 1] - self.array = as_lazy_data(self.data) - self.axis = 0 - self.expected_masked = ma.mean(self.data, axis=self.axis) - - def test_mdtol_default(self): - # Default operation is "mdtol=1" --> unmasked if *any* valid points. - # --> output column masks = [0, 0, 0, 1] - agg = MEAN.lazy_aggregate(self.array, axis=self.axis) - masked_result = as_concrete_data(agg) - self.assertMaskedArrayAlmostEqual(masked_result, self.expected_masked) - - def test_mdtol_belowall(self): - # Mdtol=0.25 --> masked columns = [0, 1, 1, 1] - agg = MEAN.lazy_aggregate(self.array, axis=self.axis, mdtol=0.25) - masked_result = as_concrete_data(agg) - expected_masked = self.expected_masked - expected_masked.mask = [False, True, True, True] - self.assertMaskedArrayAlmostEqual(masked_result, expected_masked) - - def test_mdtol_intermediate(self): - # mdtol=0.5 --> masked columns = [0, 0, 1, 1] - agg = MEAN.lazy_aggregate(self.array, axis=self.axis, mdtol=0.5) - masked_result = as_concrete_data(agg) - expected_masked = self.expected_masked - expected_masked.mask = [False, False, True, True] - self.assertMaskedArrayAlmostEqual(masked_result, expected_masked) - - def test_mdtol_aboveall(self): - # mdtol=0.75 --> masked columns = [0, 0, 0, 1] - # In this case, effectively the same as mdtol=None. - agg = MEAN.lazy_aggregate(self.array, axis=self.axis, mdtol=0.75) - masked_result = as_concrete_data(agg) - self.assertMaskedArrayAlmostEqual(masked_result, self.expected_masked) - - def test_multi_axis(self): - data = np.arange(24.0).reshape((2, 3, 4)) - collapse_axes = (0, 2) - lazy_data = as_lazy_data(data) - agg = MEAN.lazy_aggregate(lazy_data, axis=collapse_axes) - result = as_concrete_data(agg) - expected = np.mean(data, axis=collapse_axes) - self.assertArrayAllClose(result, expected) - - def test_last_axis(self): - # From setUp: - # self.data.mask = [[0, 0, 0, 1], - # [0, 0, 1, 1], - # [0, 1, 1, 1]] - # --> fractions of masked-points in ROWS = [1/4, 1/2, 3/4] - axis = -1 - agg = MEAN.lazy_aggregate(self.array, axis=axis, mdtol=0.51) - expected_masked = ma.mean(self.data, axis=-1) - expected_masked = np.ma.masked_array(expected_masked, [0, 0, 1]) - masked_result = as_concrete_data(agg) - self.assertMaskedArrayAlmostEqual(masked_result, expected_masked) - - def test_all_axes_belowtol(self): - agg = MEAN.lazy_aggregate(self.array, axis=None, mdtol=0.75) - expected_masked = ma.mean(self.data) - masked_result = as_concrete_data(agg) - self.assertMaskedArrayAlmostEqual(masked_result, expected_masked) - - def test_all_axes_abovetol(self): - agg = MEAN.lazy_aggregate(self.array, axis=None, mdtol=0.45) - expected_masked = ma.masked_less([0.0], 1) - masked_result = as_concrete_data(agg) - self.assertMaskedArrayAlmostEqual(masked_result, expected_masked) - - -class Test_name(tests.IrisTest): - def test(self): - self.assertEqual(MEAN.name(), "mean") - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(MEAN.aggregate_shape(**kwargs), shape) - kwargs = dict(one=1, two=2) - self.assertTupleEqual(MEAN.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_MIN.py b/lib/iris/tests/unit/analysis/test_MIN.py deleted file mode 100644 index f12790f0f1..0000000000 --- a/lib/iris/tests/unit/analysis/test_MIN.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.MIN` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.analysis import MIN -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test_basics(tests.IrisTest): - def setUp(self): - data = np.array([1, 2, 3, 4, 5]) - coord = DimCoord([6, 7, 8, 9, 10], long_name="foo") - self.cube = Cube(data) - self.cube.add_dim_coord(coord, 0) - self.lazy_cube = Cube(as_lazy_data(data)) - self.lazy_cube.add_dim_coord(coord, 0) - - def test_name(self): - self.assertEqual(MIN.name(), "minimum") - - def test_collapse(self): - data = MIN.aggregate(self.cube.data, axis=0) - self.assertArrayEqual(data, [1]) - - def test_lazy(self): - lazy_data = MIN.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertTrue(is_lazy_data(lazy_data)) - - def test_lazy_collapse(self): - lazy_data = MIN.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertArrayEqual(lazy_data.compute(), [1]) - - -class Test_masked(tests.IrisTest): - def setUp(self): - self.cube = Cube(ma.masked_less([1, 2, 3, 4, 5], 3)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - - def test_ma(self): - data = MIN.aggregate(self.cube.data, axis=0) - self.assertArrayEqual(data, [3]) - - -class Test_lazy_masked(tests.IrisTest): - def setUp(self): - masked_data = ma.masked_less([1, 2, 3, 4, 5], 3) - self.cube = Cube(as_lazy_data(masked_data)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - - def test_lazy_ma(self): - lazy_data = MIN.lazy_aggregate(self.cube.lazy_data(), axis=0) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [3]) - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(MIN.aggregate_shape(**kwargs), shape) - kwargs = dict(wibble="wobble") - self.assertTupleEqual(MIN.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_Nearest.py b/lib/iris/tests/unit/analysis/test_Nearest.py deleted file mode 100644 index f3736d2cf3..0000000000 --- a/lib/iris/tests/unit/analysis/test_Nearest.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.analysis.Nearest`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.analysis import Nearest - - -def create_scheme(mode=None): - kwargs = {} - if mode is not None: - kwargs["extrapolation_mode"] = mode - return Nearest(**kwargs) - - -class Test___init__(tests.IrisTest): - def test_invalid(self): - with self.assertRaisesRegex(ValueError, "Extrapolation mode"): - Nearest("bogus") - - -class Test_extrapolation_mode(tests.IrisTest): - def check_mode(self, mode): - scheme = create_scheme(mode) - self.assertEqual(scheme.extrapolation_mode, mode) - - def test_default(self): - scheme = Nearest() - self.assertEqual(scheme.extrapolation_mode, "extrapolate") - - def test_extrapolate(self): - self.check_mode("extrapolate") - - def test_nan(self): - self.check_mode("nan") - - def test_error(self): - self.check_mode("error") - - def test_mask(self): - self.check_mode("mask") - - def test_nanmask(self): - self.check_mode("nanmask") - - -class Test_interpolator(tests.IrisTest): - def check_mode(self, mode=None): - scheme = create_scheme(mode) - - # Check that calling `scheme.interpolator(...)` returns an - # instance of RectilinearInterpolator which has been created - # using the correct arguments. - with mock.patch( - "iris.analysis.RectilinearInterpolator", - return_value=mock.sentinel.interpolator, - ) as ri: - interpolator = scheme.interpolator( - mock.sentinel.cube, mock.sentinel.coords - ) - if mode is None: - expected_mode = "extrapolate" - else: - expected_mode = mode - ri.assert_called_once_with( - mock.sentinel.cube, mock.sentinel.coords, "nearest", expected_mode - ) - self.assertIs(interpolator, mock.sentinel.interpolator) - - def test_default(self): - self.check_mode() - - def test_extrapolate(self): - self.check_mode("extrapolate") - - def test_nan(self): - self.check_mode("nan") - - def test_error(self): - self.check_mode("error") - - def test_mask(self): - self.check_mode("mask") - - def test_nanmask(self): - self.check_mode("nanmask") - - -class Test_regridder(tests.IrisTest): - def check_mode(self, mode=None): - scheme = create_scheme(mode) - - # Ensure that calling the regridder results in an instance of - # RectilinearRegridder being returned, which has been created with - # the expected arguments. - with mock.patch( - "iris.analysis.RectilinearRegridder", - return_value=mock.sentinel.regridder, - ) as rr: - regridder = scheme.regridder( - mock.sentinel.src_grid, mock.sentinel.tgt_grid - ) - - expected_mode = "extrapolate" if mode is None else mode - rr.assert_called_once_with( - mock.sentinel.src_grid, - mock.sentinel.tgt_grid, - "nearest", - expected_mode, - ) - self.assertIs(regridder, mock.sentinel.regridder) - - def test_default(self): - self.check_mode() - - def test_extrapolate(self): - self.check_mode("extrapolate") - - def test_nan(self): - self.check_mode("nan") - - def test_error(self): - self.check_mode("error") - - def test_mask(self): - self.check_mode("mask") - - def test_nanmask(self): - self.check_mode("nanmask") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_PERCENTILE.py b/lib/iris/tests/unit/analysis/test_PERCENTILE.py deleted file mode 100644 index bfd3234d26..0000000000 --- a/lib/iris/tests/unit/analysis/test_PERCENTILE.py +++ /dev/null @@ -1,437 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.PERCENTILE` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import dask.array as da -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data -from iris.analysis import PERCENTILE - - -class AggregateMixin: - """ - Percentile aggregation tests for both numpy and scipy methods within lazy - and real percentile aggregation. - - """ - - def check_percentile_calc( - self, data, axis, percent, expected, approx=False, **kwargs - ): - if self.lazy: - data = as_lazy_data(data) - - expected = ma.array(expected) - - actual = self.agg_method( - data, - axis=axis, - percent=percent, - fast_percentile_method=self.fast, - **kwargs, - ) - - self.assertTupleEqual(actual.shape, expected.shape) - is_lazy = is_lazy_data(actual) - - if self.lazy: - self.assertTrue(is_lazy) - actual = as_concrete_data(actual) - else: - self.assertFalse(is_lazy) - - if approx: - self.assertMaskedArrayAlmostEqual(actual, expected) - else: - self.assertMaskedArrayEqual(actual, expected) - - def test_1d_single(self): - data = np.arange(11) - axis = 0 - percent = 50 - expected = 5 - self.check_percentile_calc(data, axis, percent, expected) - - def test_1d_multi(self): - data = np.arange(11) - percent = np.array([20, 50, 90]) - axis = 0 - expected = [2, 5, 9] - self.check_percentile_calc(data, axis, percent, expected) - - def test_2d_single(self): - shape = (2, 11) - data = np.arange(np.prod(shape)).reshape(shape) - axis = 0 - percent = 50 - expected = np.arange(shape[-1]) + 5.5 - self.check_percentile_calc(data, axis, percent, expected) - - def test_2d_multi(self): - shape = (2, 10) - data = np.arange(np.prod(shape)).reshape(shape) - axis = 0 - percent = np.array([10, 50, 90, 100]) - expected = np.tile(np.arange(shape[-1]), percent.size) - expected = expected.reshape(percent.size, shape[-1]).T + 1 - expected = expected + (percent / 10 - 1) - self.check_percentile_calc(data, axis, percent, expected, approx=True) - - -class ScipyAggregateMixin: - """ - Tests for calculations specific to the default (scipy) function. Includes - tests on masked data and tests to verify that the function is called with - the expected keywords. Needs to be used with AggregateMixin, as some of - these tests re-use its method. - - """ - - def test_masked_1d_single(self): - data = ma.arange(11) - data[3:7] = ma.masked - axis = 0 - percent = 50 - expected = 7 - self.check_percentile_calc(data, axis, percent, expected) - - def test_masked_1d_multi(self): - data = ma.arange(11) - data[3:9] = ma.masked - percent = np.array([25, 50, 75]) - axis = 0 - expected = [1, 2, 9] - self.check_percentile_calc(data, axis, percent, expected) - - def test_masked_2d_single(self): - shape = (2, 11) - data = ma.arange(np.prod(shape)).reshape(shape) - data[0, ::2] = ma.masked - data[1, 1::2] = ma.masked - axis = 0 - percent = 50 - # data has only one value for each column being aggregated, so result - # should be that value. - expected = np.empty(shape[-1:]) - expected[1::2] = data[0, 1::2] - expected[::2] = data[1, ::2] - self.check_percentile_calc(data, axis, percent, expected) - - def test_masked_2d_multi(self): - shape = (3, 10) - data = ma.arange(np.prod(shape)).reshape(shape) - data[1, ::2] = ma.masked - percent = np.array([10, 50, 70, 80]) - axis = 0 - mdtol = 0.1 - - # First column is just 0 and 20. Percentiles of these can be calculated as - # linear interpolation. - expected = percent / 100 * 20 - # Other columns are first column plus column number. - expected = ma.array( - np.broadcast_to(expected, (shape[-1], percent.size)) - + np.arange(shape[-1])[:, np.newaxis] - ) - expected[::2] = ma.masked - - self.check_percentile_calc( - data, axis, percent, expected, mdtol=mdtol, approx=True - ) - - @mock.patch("scipy.stats.mstats.mquantiles", return_value=[2, 4]) - def test_default_kwargs_passed(self, mocked_mquantiles): - data = np.arange(5) - percent = [42, 75] - axis = 0 - if self.lazy: - data = as_lazy_data(data) - - self.agg_method(data, axis=axis, percent=percent) - - # Trigger calculation for lazy case. - as_concrete_data(data) - for key in ["alphap", "betap"]: - self.assertEqual(mocked_mquantiles.call_args.kwargs[key], 1) - - @mock.patch("scipy.stats.mstats.mquantiles") - def test_chosen_kwargs_passed(self, mocked_mquantiles): - data = np.arange(5) - percent = [42, 75] - axis = 0 - if self.lazy: - data = as_lazy_data(data) - - self.agg_method( - data, axis=axis, percent=percent, alphap=0.6, betap=0.5 - ) - - # Trigger calculation for lazy case. - as_concrete_data(data) - for key, val in zip(["alphap", "betap"], [0.6, 0.5]): - self.assertEqual(mocked_mquantiles.call_args.kwargs[key], val) - - -class Test_aggregate(tests.IrisTest, AggregateMixin, ScipyAggregateMixin): - """Tests for standard aggregation method on real data.""" - - def setUp(self): - self.fast = False - self.lazy = False - self.agg_method = PERCENTILE.aggregate - - def test_missing_mandatory_kwarg(self): - emsg = "percentile aggregator requires .* keyword argument 'percent'" - with self.assertRaisesRegex(ValueError, emsg): - PERCENTILE.aggregate("dummy", axis=0) - - def test_wrong_kwarg(self): - # Test we get an error out of scipy if we pass the numpy keyword. - data = range(5) - emsg = "unexpected keyword argument" - with self.assertRaisesRegex(TypeError, emsg): - PERCENTILE.aggregate(data, percent=50, axis=0, method="nearest") - - -class Test_fast_aggregate(tests.IrisTest, AggregateMixin): - """Tests for fast percentile method on real data.""" - - def setUp(self): - self.fast = True - self.lazy = False - self.agg_method = PERCENTILE.aggregate - - def test_masked(self): - # Using (3,11) because np.percentile returns a masked array anyway with - # (2, 11) - shape = (3, 11) - data = ma.arange(np.prod(shape)).reshape(shape) - data[0, ::2] = ma.masked - emsg = ( - "Cannot use fast np.percentile method with masked array unless " - "mdtol is 0." - ) - with self.assertRaisesRegex(TypeError, emsg): - PERCENTILE.aggregate( - data, axis=0, percent=50, fast_percentile_method=True - ) - - def test_masked_mdtol_0(self): - # Using (3,11) because np.percentile returns a masked array anyway with - # (2, 11) - shape = (3, 11) - axis = 0 - percent = 50 - data = ma.arange(np.prod(shape)).reshape(shape) - data[0, ::2] = ma.masked - expected = ma.arange(shape[-1]) + 11 - expected[::2] = ma.masked - self.check_percentile_calc(data, axis, percent, expected, mdtol=0) - - @mock.patch("numpy.percentile") - def test_numpy_percentile_called(self, mocked_percentile): - # Basic check that numpy.percentile is called. - data = np.arange(5) - self.agg_method(data, axis=0, percent=42, fast_percentile_method=True) - mocked_percentile.assert_called_once() - - # Check that we left "method" keyword to numpy's default. - self.assertNotIn("method", mocked_percentile.call_args.kwargs) - - @mock.patch("numpy.percentile") - def test_chosen_kwarg_passed(self, mocked_percentile): - data = np.arange(5) - percent = [42, 75] - axis = 0 - - self.agg_method( - data, - axis=axis, - percent=percent, - fast_percentile_method=True, - method="nearest", - ) - self.assertEqual( - mocked_percentile.call_args.kwargs["method"], "nearest" - ) - - -class MultiAxisMixin: - """ - Tests for axis passed as a tuple. Only relevant for lazy aggregation since - axis is always specified as int for real aggregation. - - """ - - def test_multi_axis(self): - data = np.arange(24).reshape((2, 3, 4)) - collapse_axes = (0, 2) - lazy_data = as_lazy_data(data) - percent = 30 - actual = PERCENTILE.lazy_aggregate( - lazy_data, - axis=collapse_axes, - percent=percent, - fast_percentile_method=self.fast, - ) - self.assertTrue(is_lazy_data(actual)) - result = as_concrete_data(actual) - self.assertTupleEqual(result.shape, (3,)) - for num, sub_result in enumerate(result): - # results should be the same as percentiles calculated from slices. - self.assertArrayAlmostEqual( - sub_result, np.percentile(data[:, num, :], percent) - ) - - def test_multi_axis_multi_percent(self): - data = np.arange(24).reshape((2, 3, 4)) - collapse_axes = (0, 2) - lazy_data = as_lazy_data(data) - percent = [20, 30, 50, 70, 80] - actual = PERCENTILE.lazy_aggregate( - lazy_data, - axis=collapse_axes, - percent=percent, - fast_percentile_method=self.fast, - ) - self.assertTrue(is_lazy_data(actual)) - result = as_concrete_data(actual) - self.assertTupleEqual(result.shape, (3, 5)) - for num, sub_result in enumerate(result): - # results should be the same as percentiles calculated from slices. - self.assertArrayAlmostEqual( - sub_result, np.percentile(data[:, num, :], percent) - ) - - -class Test_lazy_fast_aggregate(tests.IrisTest, AggregateMixin, MultiAxisMixin): - """Tests for fast aggregation on lazy data.""" - - def setUp(self): - self.fast = True - self.lazy = True - self.agg_method = PERCENTILE.lazy_aggregate - - def test_masked(self): - shape = (2, 11) - data = ma.arange(np.prod(shape)).reshape(shape) - data[0, ::2] = ma.masked - data = as_lazy_data(data) - actual = PERCENTILE.lazy_aggregate( - data, axis=0, percent=50, fast_percentile_method=True - ) - emsg = ( - "Cannot use fast np.percentile method with masked array unless " - "mdtol is 0." - ) - with self.assertRaisesRegex(TypeError, emsg): - as_concrete_data(actual) - - def test_masked_mdtol_0(self): - # Using (3,11) because np.percentile returns a masked array anyway with - # (2, 11) - shape = (3, 11) - axis = 0 - percent = 50 - data = ma.arange(np.prod(shape)).reshape(shape) - data[0, ::2] = ma.masked - data = as_lazy_data(data) - expected = ma.arange(shape[-1]) + 11 - expected[::2] = ma.masked - self.check_percentile_calc(data, axis, percent, expected, mdtol=0) - - @mock.patch("numpy.percentile", return_value=np.array([2, 4])) - def test_numpy_percentile_called(self, mocked_percentile): - # Basic check that numpy.percentile is called. - data = da.arange(5) - result = self.agg_method( - data, axis=0, percent=[42, 75], fast_percentile_method=True - ) - - self.assertTrue(is_lazy_data(result)) - as_concrete_data(result) - mocked_percentile.assert_called() - - # Check we have left "method" keyword to numpy's default. - self.assertNotIn("method", mocked_percentile.call_args.kwargs) - - @mock.patch("numpy.percentile") - def test_chosen_method_kwarg_passed(self, mocked_percentile): - data = da.arange(5) - percent = [42, 75] - axis = 0 - - result = self.agg_method( - data, - axis=axis, - percent=percent, - fast_percentile_method=True, - method="nearest", - ) - - self.assertTrue(is_lazy_data(result)) - as_concrete_data(result) - self.assertEqual( - mocked_percentile.call_args.kwargs["method"], "nearest" - ) - - -class Test_lazy_aggregate( - tests.IrisTest, AggregateMixin, ScipyAggregateMixin, MultiAxisMixin -): - """Tests for standard aggregation on lazy data.""" - - def setUp(self): - self.fast = False - self.lazy = True - self.agg_method = PERCENTILE.lazy_aggregate - - -class Test_name(tests.IrisTest): - def test(self): - self.assertEqual(PERCENTILE.name(), "percentile") - - -class Test_aggregate_shape(tests.IrisTest): - def test_missing_mandatory_kwarg(self): - emsg = "percentile aggregator requires .* keyword argument 'percent'" - with self.assertRaisesRegex(ValueError, emsg): - PERCENTILE.aggregate_shape() - with self.assertRaisesRegex(ValueError, emsg): - kwargs = dict() - PERCENTILE.aggregate_shape(**kwargs) - with self.assertRaisesRegex(ValueError, emsg): - kwargs = dict(point=10) - PERCENTILE.aggregate_shape(**kwargs) - - def test_mandatory_kwarg_no_shape(self): - kwargs = dict(percent=50) - self.assertTupleEqual(PERCENTILE.aggregate_shape(**kwargs), ()) - kwargs = dict(percent=[50]) - self.assertTupleEqual(PERCENTILE.aggregate_shape(**kwargs), ()) - - def test_mandatory_kwarg_shape(self): - kwargs = dict(percent=(10, 20)) - self.assertTupleEqual(PERCENTILE.aggregate_shape(**kwargs), (2,)) - kwargs = dict(percent=list(range(13))) - self.assertTupleEqual(PERCENTILE.aggregate_shape(**kwargs), (13,)) - - -class Test_cell_method(tests.IrisTest): - def test(self): - self.assertIsNone(PERCENTILE.cell_method) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_PROPORTION.py b/lib/iris/tests/unit/analysis/test_PROPORTION.py deleted file mode 100644 index b7118241af..0000000000 --- a/lib/iris/tests/unit/analysis/test_PROPORTION.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.PROPORTION` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy.ma as ma - -from iris.analysis import PROPORTION -from iris.coords import DimCoord -import iris.cube - - -class Test_units_func(tests.IrisTest): - def test(self): - self.assertIsNotNone(PROPORTION.units_func) - new_units = PROPORTION.units_func(None) - self.assertEqual(new_units, 1) - - -class Test_masked(tests.IrisTest): - def setUp(self): - self.cube = iris.cube.Cube(ma.masked_equal([1, 2, 3, 4, 5], 3)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - self.func = lambda x: x >= 3 - - def test_ma(self): - cube = self.cube.collapsed("foo", PROPORTION, function=self.func) - self.assertArrayEqual(cube.data, [0.5]) - - def test_false_mask(self): - # Test corner case where mask is returned as boolean value rather - # than boolean array when the mask is unspecified on construction. - masked_cube = iris.cube.Cube(ma.array([1, 2, 3, 4, 5])) - masked_cube.add_dim_coord( - DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0 - ) - cube = masked_cube.collapsed("foo", PROPORTION, function=self.func) - self.assertArrayEqual(cube.data, ma.array([0.6])) - - -class Test_name(tests.IrisTest): - def test(self): - self.assertEqual(PROPORTION.name(), "proportion") - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(PROPORTION.aggregate_shape(**kwargs), shape) - kwargs = dict(captain="caveman", penelope="pitstop") - self.assertTupleEqual(PROPORTION.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_PercentileAggregator.py b/lib/iris/tests/unit/analysis/test_PercentileAggregator.py deleted file mode 100644 index f11cd7a8d3..0000000000 --- a/lib/iris/tests/unit/analysis/test_PercentileAggregator.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.analysis.PercentileAggregator` class instance. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import dask.array as da -import numpy as np - -from iris._lazy_data import as_concrete_data -from iris.analysis import PercentileAggregator -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube - - -class Test(tests.IrisTest): - def test_init(self): - name = "percentile" - units_func = mock.sentinel.units_func - aggregator = PercentileAggregator(units_func=units_func) - self.assertEqual(aggregator.name(), name) - self.assertIs(aggregator.units_func, units_func) - self.assertIsNone(aggregator.cell_method) - - -class Test_post_process(tests.IrisTest): - def setUp(self): - shape = (2, 5) - data = np.arange(np.prod(shape)) - - self.coord_simple = DimCoord(data, "time") - self.cube_simple = Cube(data) - self.cube_simple.add_dim_coord(self.coord_simple, 0) - - self.coord_multi_0 = DimCoord(np.arange(shape[0]), "time") - self.coord_multi_1 = DimCoord(np.arange(shape[1]), "height") - self.cube_multi = Cube(data.reshape(shape)) - self.cube_multi.add_dim_coord(self.coord_multi_0, 0) - self.cube_multi.add_dim_coord(self.coord_multi_1, 1) - - def test_missing_mandatory_kwarg(self): - aggregator = PercentileAggregator() - emsg = "percentile aggregator requires .* keyword argument 'percent'" - with self.assertRaisesRegex(ValueError, emsg): - aggregator.aggregate("dummy", axis=0) - - def test_simple_single_point(self): - aggregator = PercentileAggregator() - percent = 50 - kwargs = dict(percent=percent) - data = np.empty(self.cube_simple.shape) - coords = [self.coord_simple] - actual = aggregator.post_process( - self.cube_simple, data, coords, **kwargs - ) - self.assertEqual(actual.shape, self.cube_simple.shape) - self.assertIs(actual.data, data) - name = "percentile_over_time" - coord = actual.coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - def test_simple_multiple_points(self): - aggregator = PercentileAggregator() - percent = np.array([10, 20, 50, 90]) - kwargs = dict(percent=percent) - shape = self.cube_simple.shape + percent.shape - data = np.empty(shape) - coords = [self.coord_simple] - actual = aggregator.post_process( - self.cube_simple, data, coords, **kwargs - ) - self.assertEqual(actual.shape, percent.shape + self.cube_simple.shape) - expected = data.T - self.assertArrayEqual(actual.data, expected) - name = "percentile_over_time" - coord = actual.coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - def test_multi_single_point(self): - aggregator = PercentileAggregator() - percent = 70 - kwargs = dict(percent=percent) - data = np.empty(self.cube_multi.shape) - coords = [self.coord_multi_0] - actual = aggregator.post_process( - self.cube_multi, data, coords, **kwargs - ) - self.assertEqual(actual.shape, self.cube_multi.shape) - self.assertIs(actual.data, data) - name = "percentile_over_time" - coord = actual.coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - def test_multi_multiple_points(self): - aggregator = PercentileAggregator() - percent = np.array([17, 29, 81]) - kwargs = dict(percent=percent) - shape = self.cube_multi.shape + percent.shape - data = np.empty(shape) - coords = [self.coord_multi_0] - actual = aggregator.post_process( - self.cube_multi, data, coords, **kwargs - ) - self.assertEqual(actual.shape, percent.shape + self.cube_multi.shape) - expected = np.moveaxis(data, -1, 0) - self.assertArrayEqual(actual.data, expected) - name = "percentile_over_time" - coord = actual.coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - def test_multi_multiple_points_lazy(self): - # Check that lazy data is preserved. - aggregator = PercentileAggregator() - percent = np.array([17, 29, 81]) - kwargs = dict(percent=percent) - shape = self.cube_multi.shape + percent.shape - data = da.arange(np.prod(shape)).reshape(shape) - coords = [self.coord_multi_0] - actual = aggregator.post_process( - self.cube_multi, data, coords, **kwargs - ) - self.assertEqual(actual.shape, percent.shape + self.cube_multi.shape) - self.assertTrue(actual.has_lazy_data()) - expected = np.moveaxis(as_concrete_data(data), -1, 0) - self.assertArrayEqual(actual.data, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_PointInCell.py b/lib/iris/tests/unit/analysis/test_PointInCell.py deleted file mode 100644 index 2570465245..0000000000 --- a/lib/iris/tests/unit/analysis/test_PointInCell.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.analysis.PointInCell`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.analysis import PointInCell - - -class Test_regridder(tests.IrisTest): - def test(self): - point_in_cell = PointInCell(mock.sentinel.weights) - - with mock.patch( - "iris.analysis.CurvilinearRegridder", - return_value=mock.sentinel.regridder, - ) as ecr: - regridder = point_in_cell.regridder( - mock.sentinel.src, mock.sentinel.target - ) - - ecr.assert_called_once_with( - mock.sentinel.src, mock.sentinel.target, mock.sentinel.weights - ) - self.assertIs(regridder, mock.sentinel.regridder) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_RMS.py b/lib/iris/tests/unit/analysis/test_RMS.py deleted file mode 100644 index 141b3e262b..0000000000 --- a/lib/iris/tests/unit/analysis/test_RMS.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.RMS` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_lazy_data -from iris.analysis import RMS - - -class Test_aggregate(tests.IrisTest): - def test_1d(self): - # 1-dimensional input - data = np.array([5, 2, 6, 4], dtype=np.float64) - rms = RMS.aggregate(data, 0) - expected_rms = 4.5 - self.assertAlmostEqual(rms, expected_rms) - - def test_2d(self): - # 2-dimensional input - data = np.array([[5, 2, 6, 4], [12, 4, 10, 8]], dtype=np.float64) - expected_rms = np.array([4.5, 9.0], dtype=np.float64) - rms = RMS.aggregate(data, 1) - self.assertArrayAlmostEqual(rms, expected_rms) - - def test_1d_weighted(self): - # 1-dimensional input with weights - data = np.array([4, 7, 10, 8], dtype=np.float64) - weights = np.array([1, 4, 3, 2], dtype=np.float64) - expected_rms = 8.0 - rms = RMS.aggregate(data, 0, weights=weights) - self.assertAlmostEqual(rms, expected_rms) - - def test_2d_weighted(self): - # 2-dimensional input with weights - data = np.array([[4, 7, 10, 8], [14, 16, 20, 8]], dtype=np.float64) - weights = np.array([[1, 4, 3, 2], [2, 1, 1.5, 0.5]], dtype=np.float64) - expected_rms = np.array([8.0, 16.0], dtype=np.float64) - rms = RMS.aggregate(data, 1, weights=weights) - self.assertArrayAlmostEqual(rms, expected_rms) - - def test_unit_weighted(self): - # unit weights should be the same as no weights - data = np.array([5, 2, 6, 4], dtype=np.float64) - weights = np.ones_like(data) - rms = RMS.aggregate(data, 0, weights=weights) - expected_rms = 4.5 - self.assertAlmostEqual(rms, expected_rms) - - def test_masked(self): - # masked entries should be completely ignored - data = ma.array( - [5, 10, 2, 11, 6, 4], - mask=[False, True, False, True, False, False], - dtype=np.float64, - ) - expected_rms = 4.5 - rms = RMS.aggregate(data, 0) - self.assertAlmostEqual(rms, expected_rms) - - def test_masked_weighted(self): - # weights should work properly with masked arrays - data = ma.array( - [4, 7, 18, 10, 11, 8], - mask=[False, False, True, False, True, False], - dtype=np.float64, - ) - weights = np.array([1, 4, 5, 3, 8, 2], dtype=np.float64) - expected_rms = 8.0 - rms = RMS.aggregate(data, 0, weights=weights) - self.assertAlmostEqual(rms, expected_rms) - - -class Test_lazy_aggregate(tests.IrisTest): - def test_1d(self): - # 1-dimensional input. - data = as_lazy_data(np.array([5, 2, 6, 4], dtype=np.float64)) - rms = RMS.lazy_aggregate(data, 0) - expected_rms = 4.5 - self.assertAlmostEqual(rms, expected_rms) - - def test_2d(self): - # 2-dimensional input. - data = as_lazy_data( - np.array([[5, 2, 6, 4], [12, 4, 10, 8]], dtype=np.float64) - ) - expected_rms = np.array([4.5, 9.0], dtype=np.float64) - rms = RMS.lazy_aggregate(data, 1) - self.assertArrayAlmostEqual(rms, expected_rms) - - def test_1d_weighted(self): - # 1-dimensional input with weights. - data = as_lazy_data(np.array([4, 7, 10, 8], dtype=np.float64)) - weights = np.array([1, 4, 3, 2], dtype=np.float64) - expected_rms = 8.0 - # https://github.com/dask/dask/issues/3846. - with self.assertRaisesRegex(TypeError, "unexpected keyword argument"): - rms = RMS.lazy_aggregate(data, 0, weights=weights) - self.assertAlmostEqual(rms, expected_rms) - - def test_1d_lazy_weighted(self): - # 1-dimensional input with lazy weights. - data = as_lazy_data(np.array([4, 7, 10, 8], dtype=np.float64)) - weights = as_lazy_data(np.array([1, 4, 3, 2], dtype=np.float64)) - expected_rms = 8.0 - # https://github.com/dask/dask/issues/3846. - with self.assertRaisesRegex(TypeError, "unexpected keyword argument"): - rms = RMS.lazy_aggregate(data, 0, weights=weights) - self.assertAlmostEqual(rms, expected_rms) - - def test_2d_weighted(self): - # 2-dimensional input with weights. - data = as_lazy_data( - np.array([[4, 7, 10, 8], [14, 16, 20, 8]], dtype=np.float64) - ) - weights = np.array([[1, 4, 3, 2], [2, 1, 1.5, 0.5]], dtype=np.float64) - expected_rms = np.array([8.0, 16.0], dtype=np.float64) - # https://github.com/dask/dask/issues/3846. - with self.assertRaisesRegex(TypeError, "unexpected keyword argument"): - rms = RMS.lazy_aggregate(data, 1, weights=weights) - self.assertArrayAlmostEqual(rms, expected_rms) - - def test_unit_weighted(self): - # Unit weights should be the same as no weights. - data = as_lazy_data(np.array([5, 2, 6, 4], dtype=np.float64)) - weights = np.ones_like(data) - expected_rms = 4.5 - # https://github.com/dask/dask/issues/3846. - with self.assertRaisesRegex(TypeError, "unexpected keyword argument"): - rms = RMS.lazy_aggregate(data, 0, weights=weights) - self.assertAlmostEqual(rms, expected_rms) - - def test_masked(self): - # Masked entries should be completely ignored. - data = as_lazy_data( - ma.array( - [5, 10, 2, 11, 6, 4], - mask=[False, True, False, True, False, False], - dtype=np.float64, - ) - ) - expected_rms = 4.5 - rms = RMS.lazy_aggregate(data, 0) - self.assertAlmostEqual(rms, expected_rms) - - def test_masked_weighted(self): - # Weights should work properly with masked arrays, but currently don't - # (see https://github.com/dask/dask/issues/3846). - # For now, masked weights are simply not supported. - data = as_lazy_data( - ma.array( - [4, 7, 18, 10, 11, 8], - mask=[False, False, True, False, True, False], - dtype=np.float64, - ) - ) - weights = np.array([1, 4, 5, 3, 8, 2]) - expected_rms = 8.0 - with self.assertRaisesRegex(TypeError, "unexpected keyword argument"): - rms = RMS.lazy_aggregate(data, 0, weights=weights) - self.assertAlmostEqual(rms, expected_rms) - - -class Test_name(tests.IrisTest): - def test(self): - self.assertEqual(RMS.name(), "root_mean_square") - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(RMS.aggregate_shape(**kwargs), shape) - kwargs = dict(tom="jerry", calvin="hobbes") - self.assertTupleEqual(RMS.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_STD_DEV.py b/lib/iris/tests/unit/analysis/test_STD_DEV.py deleted file mode 100644 index 978bdb4ddf..0000000000 --- a/lib/iris/tests/unit/analysis/test_STD_DEV.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.STD_DEV` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data -from iris.analysis import STD_DEV -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test_basics(tests.IrisTest): - def setUp(self): - data = np.array([1, 2, 3, 4, 5]) - coord = DimCoord([6, 7, 8, 9, 10], long_name="foo") - self.cube = Cube(data) - self.cube.add_dim_coord(coord, 0) - self.lazy_cube = Cube(as_lazy_data(data)) - self.lazy_cube.add_dim_coord(coord, 0) - - def test_name(self): - self.assertEqual(STD_DEV.name(), "standard_deviation") - - def test_collapse(self): - data = STD_DEV.aggregate(self.cube.data, axis=0) - self.assertArrayAlmostEqual(data, [1.58113883]) - - def test_lazy(self): - lazy_data = STD_DEV.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertTrue(is_lazy_data(lazy_data)) - - def test_lazy_collapse(self): - lazy_data = STD_DEV.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertArrayAlmostEqual(lazy_data.compute(), [1.58113883]) - - -class Test_lazy_aggregate(tests.IrisTest): - def test_mdtol(self): - na = -999.888 - array = np.ma.masked_equal( - [[1.0, 2.0, 1.0, 2.0], [1.0, 2.0, 3.0, na], [1.0, 2.0, na, na]], na - ) - array = as_lazy_data(array) - var = STD_DEV.lazy_aggregate(array, axis=1, mdtol=0.3) - masked_result = as_concrete_data(var) - masked_expected = np.ma.masked_array( - [0.57735, 1.0, 0.707107], mask=[0, 0, 1] - ) - self.assertMaskedArrayAlmostEqual(masked_result, masked_expected) - - def test_ddof_one(self): - array = as_lazy_data(np.arange(8)) - var = STD_DEV.lazy_aggregate(array, axis=0, ddof=1) - result = as_concrete_data(var) - self.assertArrayAlmostEqual(result, np.array(2.449489)) - - def test_ddof_zero(self): - array = as_lazy_data(np.arange(8)) - var = STD_DEV.lazy_aggregate(array, axis=0, ddof=0) - result = as_concrete_data(var) - self.assertArrayAlmostEqual(result, np.array(2.291287)) - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(STD_DEV.aggregate_shape(**kwargs), shape) - kwargs = dict(forfar=5, fife=4) - self.assertTupleEqual(STD_DEV.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_SUM.py b/lib/iris/tests/unit/analysis/test_SUM.py deleted file mode 100644 index 64699b442f..0000000000 --- a/lib/iris/tests/unit/analysis/test_SUM.py +++ /dev/null @@ -1,164 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.SUM` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import dask.array as da -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.analysis import SUM -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test_basics(tests.IrisTest): - def setUp(self): - data = np.array([1, 2, 3, 4, 5]) - coord = DimCoord([6, 7, 8, 9, 10], long_name="foo") - self.cube = Cube(data) - self.cube.add_dim_coord(coord, 0) - self.lazy_cube = Cube(as_lazy_data(data)) - self.lazy_cube.add_dim_coord(coord, 0) - - def test_name(self): - self.assertEqual(SUM.name(), "sum") - - def test_collapse(self): - data = SUM.aggregate(self.cube.data, axis=0) - self.assertArrayEqual(data, [15]) - - def test_lazy(self): - lazy_data = SUM.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertTrue(is_lazy_data(lazy_data)) - - def test_lazy_collapse(self): - lazy_data = SUM.lazy_aggregate(self.lazy_cube.lazy_data(), axis=0) - self.assertArrayEqual(lazy_data.compute(), [15]) - - -class Test_masked(tests.IrisTest): - def setUp(self): - self.cube = Cube(ma.masked_equal([1, 2, 3, 4, 5], 3)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - - def test_ma(self): - data = SUM.aggregate(self.cube.data, axis=0) - self.assertArrayEqual(data, [12]) - - -class Test_lazy_masked(tests.IrisTest): - def setUp(self): - masked_data = ma.masked_equal([1, 2, 3, 4, 5], 3) - self.cube = Cube(as_lazy_data(masked_data)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - - def test_lazy_ma(self): - lazy_data = SUM.lazy_aggregate(self.cube.lazy_data(), axis=0) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [12]) - - -class Test_weights_and_returned(tests.IrisTest): - def setUp(self): - data_2d = np.arange(1, 11).reshape(2, 5) - coord_0 = DimCoord([11, 12], long_name="bar") - coord_1 = DimCoord([6, 7, 8, 9, 10], long_name="foo") - self.cube_2d = Cube(data_2d) - self.cube_2d.add_dim_coord(coord_0, 0) - self.cube_2d.add_dim_coord(coord_1, 1) - self.weights = np.array([2, 1, 1, 1, 1] * 2).reshape(2, 5) - - def test_weights(self): - data = SUM.aggregate(self.cube_2d.data, axis=0, weights=self.weights) - self.assertArrayEqual(data, [14, 9, 11, 13, 15]) - - def test_returned(self): - data, weights = SUM.aggregate(self.cube_2d.data, axis=0, returned=True) - self.assertArrayEqual(data, [7, 9, 11, 13, 15]) - self.assertArrayEqual(weights, [2, 2, 2, 2, 2]) - - def test_weights_and_returned(self): - data, weights = SUM.aggregate( - self.cube_2d.data, axis=0, weights=self.weights, returned=True - ) - self.assertArrayEqual(data, [14, 9, 11, 13, 15]) - self.assertArrayEqual(weights, [4, 2, 2, 2, 2]) - - def test_masked_weights_and_returned(self): - array = ma.array( - self.cube_2d.data, mask=[[0, 0, 1, 0, 0], [0, 0, 0, 1, 0]] - ) - data, weights = SUM.aggregate( - array, axis=0, weights=self.weights, returned=True - ) - self.assertArrayEqual(data, [14, 9, 8, 4, 15]) - self.assertArrayEqual(weights, [4, 2, 1, 1, 2]) - - -class Test_lazy_weights_and_returned(tests.IrisTest): - def setUp(self): - data_2d = np.arange(1, 11).reshape(2, 5) - coord_0 = DimCoord([11, 12], long_name="bar") - coord_1 = DimCoord([6, 7, 8, 9, 10], long_name="foo") - self.cube_2d = Cube(as_lazy_data(data_2d)) - self.cube_2d.add_dim_coord(coord_0, 0) - self.cube_2d.add_dim_coord(coord_1, 1) - self.weights = np.array([2, 1, 1, 1, 1] * 2).reshape(2, 5) - - def test_weights(self): - lazy_data = SUM.lazy_aggregate( - self.cube_2d.lazy_data(), axis=0, weights=self.weights - ) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [14, 9, 11, 13, 15]) - - def test_returned(self): - lazy_data, weights = SUM.lazy_aggregate( - self.cube_2d.lazy_data(), axis=0, returned=True - ) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [7, 9, 11, 13, 15]) - self.assertArrayEqual(weights, [2, 2, 2, 2, 2]) - - def test_weights_and_returned(self): - lazy_data, weights = SUM.lazy_aggregate( - self.cube_2d.lazy_data(), - axis=0, - weights=self.weights, - returned=True, - ) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [14, 9, 11, 13, 15]) - self.assertArrayEqual(weights, [4, 2, 2, 2, 2]) - - def test_masked_weights_and_returned(self): - array = da.ma.masked_array( - self.cube_2d.lazy_data(), mask=[[0, 0, 1, 0, 0], [0, 0, 0, 1, 0]] - ) - lazy_data, weights = SUM.lazy_aggregate( - array, axis=0, weights=self.weights, returned=True - ) - self.assertTrue(is_lazy_data(lazy_data)) - self.assertArrayEqual(lazy_data.compute(), [14, 9, 8, 4, 15]) - self.assertArrayEqual(weights, [4, 2, 1, 1, 2]) - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(SUM.aggregate_shape(**kwargs), shape) - kwargs = dict(wibble="wobble") - self.assertTupleEqual(SUM.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_VARIANCE.py b/lib/iris/tests/unit/analysis/test_VARIANCE.py deleted file mode 100644 index 857bc7e1d2..0000000000 --- a/lib/iris/tests/unit/analysis/test_VARIANCE.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.VARIANCE` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_concrete_data, as_lazy_data -from iris.analysis import VARIANCE -from iris.coords import DimCoord -import iris.cube - - -class Test_units_func(tests.IrisTest): - def test(self): - self.assertIsNotNone(VARIANCE.units_func) - mul = mock.Mock(return_value=mock.sentinel.new_unit) - units = mock.Mock(__mul__=mul) - new_units = VARIANCE.units_func(units) - # Make sure the VARIANCE units_func tries to square the units. - mul.assert_called_once_with(units) - self.assertEqual(new_units, mock.sentinel.new_unit) - - -class Test_masked(tests.IrisTest): - def setUp(self): - self.cube = iris.cube.Cube(ma.masked_equal([1, 2, 3, 4, 5], 3)) - self.cube.add_dim_coord(DimCoord([6, 7, 8, 9, 10], long_name="foo"), 0) - - def test_ma_ddof0(self): - cube = self.cube.collapsed("foo", VARIANCE, ddof=0) - expected = 10 / 4.0 - self.assertArrayEqual(np.var(self.cube.data, ddof=0), expected) - self.assertArrayAlmostEqual(cube.data, expected) - - def test_ma_ddof1(self): - cube = self.cube.collapsed("foo", VARIANCE, ddof=1) - expected = 10 / 3.0 - self.assertArrayEqual(np.var(self.cube.data, ddof=1), expected) - self.assertArrayEqual(cube.data, expected) - - # test that the default ddof is 1 - default_cube = self.cube.collapsed("foo", VARIANCE) - self.assertArrayEqual(cube.data, default_cube.data) - - -class Test_lazy_aggregate(tests.IrisTest): - def test_ddof_one(self): - array = as_lazy_data(np.arange(8)) - var = VARIANCE.lazy_aggregate(array, axis=0, ddof=1) - result = as_concrete_data(var) - self.assertArrayAlmostEqual(result, np.array(6.0)) - - def test_ddof_zero(self): - array = as_lazy_data(np.arange(8)) - var = VARIANCE.lazy_aggregate(array, axis=0, ddof=0) - result = as_concrete_data(var) - self.assertArrayAlmostEqual(result, np.array(5.25)) - - -class Test_name(tests.IrisTest): - def test(self): - self.assertEqual(VARIANCE.name(), "variance") - - -class Test_aggregate_shape(tests.IrisTest): - def test(self): - shape = () - kwargs = dict() - self.assertTupleEqual(VARIANCE.aggregate_shape(**kwargs), shape) - kwargs = dict(bat="man", wonder="woman") - self.assertTupleEqual(VARIANCE.aggregate_shape(**kwargs), shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_WPERCENTILE.py b/lib/iris/tests/unit/analysis/test_WPERCENTILE.py deleted file mode 100644 index a59bf4ce9c..0000000000 --- a/lib/iris/tests/unit/analysis/test_WPERCENTILE.py +++ /dev/null @@ -1,238 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis.PERCENTILE` aggregator.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris.analysis import WPERCENTILE - - -class Test_aggregate(tests.IrisTest): - def test_missing_mandatory_kwargs(self): - emsg = ( - "weighted_percentile aggregator requires " - ".* keyword argument 'percent'" - ) - with self.assertRaisesRegex(ValueError, emsg): - WPERCENTILE.aggregate("dummy", axis=0, weights=None) - emsg = ( - "weighted_percentile aggregator requires " - ".* keyword argument 'weights'" - ) - with self.assertRaisesRegex(ValueError, emsg): - WPERCENTILE.aggregate("dummy", axis=0, percent=50) - - def test_wrong_weights_shape(self): - data = np.arange(11) - weights = np.ones(10) - emsg = "_weighted_percentile: weights wrong shape." - with self.assertRaisesRegex(ValueError, emsg): - WPERCENTILE.aggregate(data, axis=0, percent=50, weights=weights) - - def test_1d_single(self): - data = np.arange(11) - weights = np.ones(data.shape) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=50, weights=weights - ) - expected = 5 - self.assertTupleEqual(actual.shape, ()) - self.assertEqual(actual, expected) - - def test_1d_single_unequal(self): - data = np.arange(12) - weights = np.ones(data.shape) - weights[0:3] = 3 - actual, weight_total = WPERCENTILE.aggregate( - data, axis=0, percent=50, weights=weights, returned=True - ) - expected = 2.75 - self.assertTupleEqual(actual.shape, ()) - self.assertEqual(actual, expected) - self.assertEqual(weight_total, 18) - - def test_masked_1d_single(self): - data = ma.arange(11) - weights = np.ones(data.shape) - data[3:7] = ma.masked - actual = WPERCENTILE.aggregate( - data, axis=0, percent=50, weights=weights - ) - expected = 7 - self.assertTupleEqual(actual.shape, ()) - self.assertEqual(actual, expected) - - def test_1d_multi(self): - data = np.arange(11) - weights = np.ones(data.shape) - percent = np.array([20, 50, 90]) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=percent, weights=weights - ) - expected = [1.7, 5, 9.4] - self.assertTupleEqual(actual.shape, percent.shape) - self.assertArrayAlmostEqual(actual, expected) - - def test_1d_multi_unequal(self): - data = np.arange(13) - weights = np.ones(data.shape) - weights[1::2] = 3 - percent = np.array([20, 50, 96]) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=percent, weights=weights - ) - expected = [2.25, 6, 11.75] - self.assertTupleEqual(actual.shape, percent.shape) - self.assertArrayAlmostEqual(actual, expected) - - def test_masked_1d_multi(self): - data = ma.arange(11) - weights = np.ones(data.shape) - data[3:9] = ma.masked - percent = np.array([25, 50, 75]) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=percent, weights=weights - ) - expected = [0.75, 2, 9.25] - self.assertTupleEqual(actual.shape, percent.shape) - self.assertArrayAlmostEqual(actual, expected) - - def test_2d_single(self): - shape = (2, 11) - data = np.arange(np.prod(shape)).reshape(shape) - weights = np.ones(shape) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=50, weights=weights - ) - self.assertTupleEqual(actual.shape, shape[-1:]) - expected = np.arange(shape[-1]) + 5.5 - self.assertArrayEqual(actual, expected) - - def test_masked_2d_single(self): - shape = (2, 11) - data = ma.arange(np.prod(shape)).reshape(shape) - data[0, ::2] = ma.masked - data[1, 1::2] = ma.masked - weights = np.ones(shape) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=50, weights=weights - ) - self.assertTupleEqual(actual.shape, shape[-1:]) - expected = np.empty(shape[-1:]) - expected[1::2] = data[0, 1::2] - expected[::2] = data[1, ::2] - self.assertArrayEqual(actual, expected) - - def test_2d_multi(self): - shape = (2, 10) - data = np.arange(np.prod(shape)).reshape(shape) - weights = np.ones(shape) - percent = np.array([10, 50, 70, 100]) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=percent, weights=weights - ) - self.assertTupleEqual(actual.shape, (shape[-1], percent.size)) - expected = np.tile(np.arange(shape[-1]), percent.size).astype("f8") - expected = expected.reshape(percent.size, shape[-1]).T - expected[:, 1:-1] += (percent[1:-1] - 25) * 0.2 - expected[:, -1] += 10.0 - self.assertArrayAlmostEqual(actual, expected) - - def test_masked_2d_multi(self): - shape = (3, 10) - data = ma.arange(np.prod(shape)).reshape(shape) - weights = np.ones(shape) - data[1] = ma.masked - percent = np.array([10, 50, 70, 80]) - actual = WPERCENTILE.aggregate( - data, axis=0, percent=percent, weights=weights - ) - self.assertTupleEqual(actual.shape, (shape[-1], percent.size)) - expected = np.tile(np.arange(shape[-1]), percent.size).astype("f8") - expected = expected.reshape(percent.size, shape[-1]).T - expected[:, 1:-1] += (percent[1:-1] - 25) * 0.4 - expected[:, -1] += 20.0 - self.assertArrayAlmostEqual(actual, expected) - - def test_masked_2d_multi_unequal(self): - shape = (3, 10) - data = ma.arange(np.prod(shape)).reshape(shape) - weights = np.ones(shape) - weights[0] = 3 - data[1] = ma.masked - percent = np.array([30, 50, 75, 80]) - actual, weight_total = WPERCENTILE.aggregate( - data, axis=0, percent=percent, weights=weights, returned=True - ) - self.assertTupleEqual(actual.shape, (shape[-1], percent.size)) - expected = np.tile(np.arange(shape[-1]), percent.size) - expected = expected.reshape(percent.size, shape[-1]).T - expected[:, 1:] = 2.0 * ( - (0.875 - percent[1:] / 100.0) * data[0, np.newaxis].T - + (percent[1:] / 100.0 - 0.375) * data[-1, np.newaxis].T - ) - self.assertArrayAlmostEqual(actual, expected) - self.assertTupleEqual(weight_total.shape, (shape[-1],)) - self.assertArrayEqual(weight_total, np.repeat(4, shape[-1])) - - -class Test_name(tests.IrisTest): - def test(self): - self.assertEqual(WPERCENTILE.name(), "weighted_percentile") - - -class Test_aggregate_shape(tests.IrisTest): - def test_missing_mandatory_kwarg(self): - emsg_pc = ( - "weighted_percentile aggregator requires " - ".* keyword argument 'percent'" - ) - emsg_wt = ( - "weighted_percentile aggregator requires " - ".* keyword argument 'weights'" - ) - with self.assertRaisesRegex(ValueError, emsg_pc): - WPERCENTILE.aggregate_shape(weights=None) - with self.assertRaisesRegex(ValueError, emsg_pc): - kwargs = dict(weights=None) - WPERCENTILE.aggregate_shape(**kwargs) - with self.assertRaisesRegex(ValueError, emsg_pc): - kwargs = dict(point=10) - WPERCENTILE.aggregate_shape(**kwargs) - with self.assertRaisesRegex(ValueError, emsg_wt): - WPERCENTILE.aggregate_shape(percent=50) - with self.assertRaisesRegex(ValueError, emsg_wt): - kwargs = dict(percent=50) - WPERCENTILE.aggregate_shape(**kwargs) - with self.assertRaisesRegex(ValueError, emsg_wt): - kwargs = dict(percent=50, weight=None) - WPERCENTILE.aggregate_shape(**kwargs) - - def test_mandatory_kwarg_no_shape(self): - kwargs = dict(percent=50, weights=None) - self.assertTupleEqual(WPERCENTILE.aggregate_shape(**kwargs), ()) - kwargs = dict(percent=[50], weights=None) - self.assertTupleEqual(WPERCENTILE.aggregate_shape(**kwargs), ()) - - def test_mandatory_kwarg_shape(self): - kwargs = dict(percent=(10, 20), weights=None) - self.assertTupleEqual(WPERCENTILE.aggregate_shape(**kwargs), (2,)) - kwargs = dict(percent=range(13), weights=None) - self.assertTupleEqual(WPERCENTILE.aggregate_shape(**kwargs), (13,)) - - -class Test_cell_method(tests.IrisTest): - def test(self): - self.assertIsNone(WPERCENTILE.cell_method) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test_WeightedPercentileAggregator.py b/lib/iris/tests/unit/analysis/test_WeightedPercentileAggregator.py deleted file mode 100644 index 0cd808d1c7..0000000000 --- a/lib/iris/tests/unit/analysis/test_WeightedPercentileAggregator.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.analysis.PercentileAggregator` class instance. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.analysis import WeightedPercentileAggregator, _weighted_percentile -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube - - -class Test(tests.IrisTest): - def test_init(self): - name = "weighted_percentile" - call_func = _weighted_percentile - units_func = mock.sentinel.units_func - lazy_func = mock.sentinel.lazy_func - aggregator = WeightedPercentileAggregator( - units_func=units_func, lazy_func=lazy_func - ) - self.assertEqual(aggregator.name(), name) - self.assertIs(aggregator.call_func, call_func) - self.assertIs(aggregator.units_func, units_func) - self.assertIs(aggregator.lazy_func, lazy_func) - self.assertIsNone(aggregator.cell_method) - - -class Test_post_process(tests.IrisTest): - def setUp(self): - shape = (2, 5) - data = np.arange(np.prod(shape)) - - self.coord_simple = DimCoord(data, "time") - self.cube_simple = Cube(data) - self.cube_simple.add_dim_coord(self.coord_simple, 0) - self.weights_simple = np.ones_like(data, dtype=float) - - self.coord_multi_0 = DimCoord(np.arange(shape[0]), "time") - self.coord_multi_1 = DimCoord(np.arange(shape[1]), "height") - self.cube_multi = Cube(data.reshape(shape)) - self.cube_multi.add_dim_coord(self.coord_multi_0, 0) - self.cube_multi.add_dim_coord(self.coord_multi_1, 1) - self.weights_multi = np.ones(shape, dtype=float) - - def test_missing_mandatory_kwarg(self): - aggregator = WeightedPercentileAggregator() - emsg = ( - "weighted_percentile aggregator requires " - ".* keyword argument 'percent'" - ) - with self.assertRaisesRegex(ValueError, emsg): - aggregator.aggregate("dummy", axis=0, weights=None) - emsg = ( - "weighted_percentile aggregator requires " - ".* keyword argument 'weights'" - ) - with self.assertRaisesRegex(ValueError, emsg): - aggregator.aggregate("dummy", axis=0, percent=50) - - def test_simple_single_point(self): - aggregator = WeightedPercentileAggregator() - percent = 50 - kwargs = dict(percent=percent, weights=self.weights_simple) - data = np.empty(self.cube_simple.shape) - coords = [self.coord_simple] - actual = aggregator.post_process( - self.cube_simple, data, coords, **kwargs - ) - self.assertEqual(actual.shape, self.cube_simple.shape) - self.assertIs(actual.data, data) - name = "weighted_percentile_over_time" - coord = actual.coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - def test_simple_multiple_points(self): - aggregator = WeightedPercentileAggregator() - percent = np.array([10, 20, 50, 90]) - kwargs = dict( - percent=percent, weights=self.weights_simple, returned=True - ) - shape = self.cube_simple.shape + percent.shape - data = np.empty(shape) - total_weights = 1.0 - coords = [self.coord_simple] - actual = aggregator.post_process( - self.cube_simple, (data, total_weights), coords, **kwargs - ) - self.assertEqual(len(actual), 2) - self.assertEqual( - actual[0].shape, percent.shape + self.cube_simple.shape - ) - expected = np.rollaxis(data, -1) - self.assertArrayEqual(actual[0].data, expected) - self.assertIs(actual[1], total_weights) - name = "weighted_percentile_over_time" - coord = actual[0].coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - def test_multi_single_point(self): - aggregator = WeightedPercentileAggregator() - percent = 70 - kwargs = dict(percent=percent, weights=self.weights_multi) - data = np.empty(self.cube_multi.shape) - coords = [self.coord_multi_0] - actual = aggregator.post_process( - self.cube_multi, data, coords, **kwargs - ) - self.assertEqual(actual.shape, self.cube_multi.shape) - self.assertIs(actual.data, data) - name = "weighted_percentile_over_time" - coord = actual.coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - def test_multi_multiple_points(self): - aggregator = WeightedPercentileAggregator() - percent = np.array([17, 29, 81]) - kwargs = dict(percent=percent, weights=self.weights_multi) - shape = self.cube_multi.shape + percent.shape - data = np.empty(shape) - coords = [self.coord_multi_0] - actual = aggregator.post_process( - self.cube_multi, data, coords, **kwargs - ) - self.assertEqual(actual.shape, percent.shape + self.cube_multi.shape) - expected = np.rollaxis(data, -1) - self.assertArrayEqual(actual.data, expected) - name = "weighted_percentile_over_time" - coord = actual.coord(name) - expected = AuxCoord(percent, long_name=name, units="percent") - self.assertEqual(coord, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py b/lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py deleted file mode 100644 index 505a00df78..0000000000 --- a/lib/iris/tests/unit/analysis/test__axis_to_single_trailing.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :data:`iris.analysis._axis_to_single_trailing` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import dask.array as da -import numpy as np - -from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data -from iris.analysis import _axis_to_single_trailing - - -class TestInputReshape(tests.IrisTest): - """Tests to make sure correct array is passed into stat function.""" - - def setUp(self): - self.stat_func = mock.Mock() - - def check_input(self, data, axis, expected): - """ - Given data and axis passed to the wrapped function, check that expected - array is passed to the inner function. - - """ - wrapped_stat_func = _axis_to_single_trailing(self.stat_func) - wrapped_stat_func(data, axis=axis) - # Can't use Mock.assert_called_with because array equality is ambiguous - # get hold of the first arg instead. - self.assertArrayEqual(self.stat_func.call_args.args[0], expected) - - def test_1d_input(self): - # Trailing axis chosen, so array should be unchanged. - data = np.arange(5) - axis = 0 - self.check_input(data, axis, data) - - def test_2d_input_trailing(self): - # Trailing axis chosen, so array should be unchanged. - data = np.arange(6).reshape(2, 3) - axis = 1 - self.stat_func.return_value = np.empty(2) - self.check_input(data, axis, data) - - def test_2d_input_transpose(self): - # Leading axis chosen, so array should be transposed. - data = np.arange(6).reshape(2, 3) - axis = 0 - self.stat_func.return_value = np.empty(3) - self.check_input(data, axis, data.T) - - def test_3d_input_middle(self): - # Middle axis is chosen, should be moved to end. Other dims should be - # flattened. - data = np.arange(24).reshape(2, 3, 4) - axis = 1 - self.stat_func.return_value = np.empty(8) - expected = np.moveaxis(data, 1, 2).reshape(8, 3) - self.check_input(data, axis, expected) - - def test_3d_input_leading_multiple(self): - # First 2 axis chosen, should be flattened and moved to end. - data = np.arange(24).reshape(2, 3, 4) - axis = (0, 1) - self.stat_func.return_value = np.empty(4) - expected = np.moveaxis(data, 2, 0).reshape(4, 6) - self.check_input(data, axis, expected) - - def test_4d_first_and_last(self): - data = np.arange(120).reshape(2, 3, 4, 5) - axis = (0, -1) - self.stat_func.return_value = np.empty(12) - expected = np.moveaxis(data, 0, 2).reshape(12, 10) - self.check_input(data, axis, expected) - - def test_3d_input_leading_multiple_lazy(self): - # First 2 axis chosen, should be flattened and moved to end. Lazy data - # should be preserved. - data = np.arange(24).reshape(2, 3, 4) - lazy_data = as_lazy_data(data) - axis = (0, 1) - self.stat_func.return_value = np.empty(4) - expected = np.moveaxis(data, 2, 0).reshape(4, 6) - - wrapped_stat_func = _axis_to_single_trailing(self.stat_func) - wrapped_stat_func(lazy_data, axis=axis) - self.assertTrue(is_lazy_data(self.stat_func.call_args.args[0])) - self.assertArrayEqual( - as_concrete_data(self.stat_func.call_args.args[0]), expected - ) - - -class TestOutputReshape(tests.IrisTest): - """Tests to make sure array from stat function is handled correctly.""" - - def setUp(self): - self.stat_func = mock.Mock() - - def test_1d_input_1d_output(self): - # If array is fully aggregated, result should be same as returned by stat - # function. - data = np.arange(3) - self.stat_func.return_value = np.arange(2) - wrapped_stat_func = _axis_to_single_trailing(self.stat_func) - result = wrapped_stat_func(data, axis=0) - self.assertArrayEqual(result, self.stat_func.return_value) - - def test_3d_input_middle_single_stat(self): - # result shape should match non-aggregated input dims. - data = np.empty((2, 3, 4)) - axis = 1 - self.stat_func.return_value = np.arange(8) - expected = np.arange(8).reshape(2, 4) - wrapped_stat_func = _axis_to_single_trailing(self.stat_func) - result = wrapped_stat_func(data, axis=axis) - self.assertArrayEqual(result, expected) - - def test_3d_input_middle_single_stat_lazy(self): - # result shape should match non-aggregated input dims. Lazy data should - # be preserved. - data = np.empty((2, 3, 4)) - axis = 1 - self.stat_func.return_value = da.arange(8) - expected = np.arange(8).reshape(2, 4) - wrapped_stat_func = _axis_to_single_trailing(self.stat_func) - result = wrapped_stat_func(data, axis=axis) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(as_concrete_data(result), expected) - - def test_3d_input_middle_multiple_stat(self): - # result shape should match non-aggregated input dims, plus trailing dim - # with size determined by the stat function. - data = np.empty((2, 3, 4)) - axis = 1 - self.stat_func.return_value = np.arange(8 * 5).reshape(8, 5) - expected = np.arange(40).reshape(2, 4, 5) - wrapped_stat_func = _axis_to_single_trailing(self.stat_func) - result = wrapped_stat_func(data, axis=axis) - self.assertArrayEqual(result, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/trajectory/__init__.py b/lib/iris/tests/unit/analysis/trajectory/__init__.py deleted file mode 100644 index 55d3ebd8bc..0000000000 --- a/lib/iris/tests/unit/analysis/trajectory/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.analysis.trajectory` module.""" diff --git a/lib/iris/tests/unit/analysis/trajectory/test_Trajectory.py b/lib/iris/tests/unit/analysis/trajectory/test_Trajectory.py deleted file mode 100644 index 32c41b78db..0000000000 --- a/lib/iris/tests/unit/analysis/trajectory/test_Trajectory.py +++ /dev/null @@ -1,197 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :class:`iris.analysis.trajectory.Trajectory`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.analysis.trajectory import Trajectory -from iris.tests.stock import simple_3d, simple_4d_with_hybrid_height - - -class Test___init__(tests.IrisTest): - def test_2_points(self): - # basic 2-seg line along x - waypoints = [{"lat": 0, "lon": 0}, {"lat": 1, "lon": 2}] - trajectory = Trajectory(waypoints, sample_count=5) - - self.assertEqual(trajectory.length, np.sqrt(5)) - self.assertEqual(trajectory.sample_count, 5) - self.assertEqual( - trajectory.sampled_points, - [ - {"lat": 0.0, "lon": 0.0}, - {"lat": 0.25, "lon": 0.5}, - {"lat": 0.5, "lon": 1.0}, - {"lat": 0.75, "lon": 1.5}, - {"lat": 1.0, "lon": 2.0}, - ], - ) - - def test_3_points(self): - # basic 2-seg line along x - waypoints = [ - {"lat": 0, "lon": 0}, - {"lat": 0, "lon": 1}, - {"lat": 0, "lon": 2}, - ] - trajectory = Trajectory(waypoints, sample_count=21) - - self.assertEqual(trajectory.length, 2.0) - self.assertEqual(trajectory.sample_count, 21) - self.assertEqual( - trajectory.sampled_points[19], - {"lat": 0.0, "lon": 1.9000000000000001}, - ) - - def test_zigzag(self): - # 4-seg m-shape - waypoints = [ - {"lat": 0, "lon": 0}, - {"lat": 1, "lon": 1}, - {"lat": 0, "lon": 2}, - {"lat": 1, "lon": 3}, - {"lat": 0, "lon": 4}, - ] - trajectory = Trajectory(waypoints, sample_count=33) - - self.assertEqual(trajectory.length, 5.6568542494923806) - self.assertEqual(trajectory.sample_count, 33) - self.assertEqual( - trajectory.sampled_points[31], - {"lat": 0.12499999999999989, "lon": 3.875}, - ) - - -class Test__get_interp_points(tests.IrisTest): - def test_basic(self): - dim_names = "lat" - waypoints = [{dim_names: 0}, {dim_names: 1}] - sample_count = 5 - trajectory = Trajectory(waypoints, sample_count=sample_count) - result = trajectory._get_interp_points() - expected_points = list(np.linspace(0, 1, sample_count)) - - self.assertEqual(len(result), len(waypoints[0])) - self.assertEqual(len(result[0][1]), sample_count) - self.assertEqual(result[0][1], expected_points) - self.assertEqual(result[0][0], dim_names) - - def test_2d(self): - dim_names = ["lat", "lon"] - waypoints = [ - {dim_names[0]: 0, dim_names[1]: 0}, - {dim_names[0]: 1, dim_names[1]: 2}, - ] - sample_count = 5 - trajectory = Trajectory(waypoints, sample_count=sample_count) - result = trajectory._get_interp_points() - - self.assertEqual(len(result), len(waypoints[0])) - self.assertEqual(len(result[0][1]), sample_count) - self.assertEqual(len(result[1][1]), sample_count) - self.assertIn(result[0][0], dim_names) - self.assertIn(result[1][0], dim_names) - - def test_3d(self): - dim_names = ["y", "x", "z"] - waypoints = [ - {dim_names[0]: 0, dim_names[1]: 0, dim_names[2]: 2}, - {dim_names[0]: 1, dim_names[1]: 2, dim_names[2]: 10}, - ] - sample_count = 5 - trajectory = Trajectory(waypoints, sample_count=sample_count) - result = trajectory._get_interp_points() - - self.assertEqual(len(result), len(waypoints[0])) - self.assertEqual(len(result[0][1]), sample_count) - self.assertEqual(len(result[1][1]), sample_count) - self.assertEqual(len(result[2][1]), sample_count) - self.assertIn(result[0][0], dim_names) - self.assertIn(result[1][0], dim_names) - self.assertIn(result[2][0], dim_names) - - -class Test_interpolate(tests.IrisTest): - def _result_cube_metadata(self, res_cube): - dim_names = [c.name() for c in res_cube.dim_coords] - named_dims = [res_cube.coord_dims(c)[0] for c in res_cube.dim_coords] - anon_dims = list(set(range(res_cube.ndim)) - set(named_dims)) - anon_dims = None if not len(anon_dims) else anon_dims - return dim_names, named_dims, anon_dims - - def test_cube__simple_3d(self): - # Test that an 'index' coord is added to the resultant cube. - cube = simple_3d() - waypoints = [ - {"latitude": 40, "longitude": 40}, - {"latitude": 0, "longitude": 0}, - ] - sample_count = 3 - new_coord_name = "index" - trajectory = Trajectory(waypoints, sample_count=sample_count) - result = trajectory.interpolate(cube) - - dim_names, named_dims, anon_dims = self._result_cube_metadata(result) - new_coord = result.coord(new_coord_name) - exp_named_dims = [0, 1] - - self.assertEqual(result.ndim, cube.ndim - 1) - self.assertIn(new_coord_name, dim_names) - self.assertEqual(named_dims, exp_named_dims) - self.assertIsNone(anon_dims) - self.assertEqual(len(new_coord.points), sample_count) - - def test_cube__anon_dim(self): - cube = simple_4d_with_hybrid_height() - cube.remove_coord("model_level_number") # Make cube dim 1 anonymous. - waypoints = [ - {"grid_latitude": 21, "grid_longitude": 31}, - {"grid_latitude": 23, "grid_longitude": 33}, - ] - sample_count = 4 - new_coord_name = "index" - trajectory = Trajectory(waypoints, sample_count=sample_count) - result = trajectory.interpolate(cube) - - dim_names, named_dims, anon_dims = self._result_cube_metadata(result) - new_coord = result.coord(new_coord_name) - exp_named_dims = [0, 2] - exp_anon_dims = [1] - - self.assertEqual(result.ndim, cube.ndim - 1) - self.assertIn(new_coord_name, dim_names) - self.assertEqual(named_dims, exp_named_dims) - self.assertEqual(anon_dims, exp_anon_dims) - self.assertEqual(len(new_coord.points), sample_count) - - def test_call(self): - # Test that :func:`iris.analysis.trajectory.interpolate` is called by - # `Trajectory.interpolate`. - cube = simple_3d() - to_patch = "iris.analysis.trajectory.interpolate" - waypoints = [ - {"latitude": 40, "longitude": 40}, - {"latitude": 0, "longitude": 0}, - ] - sample_count = 3 - trajectory = Trajectory(waypoints, sample_count=sample_count) - - with mock.patch(to_patch, return_value=cube) as mock_interpolate: - trajectory.interpolate(cube) - mock_interpolate.assert_called_once() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/trajectory/test_UnstructuredNearestNeighbourRegridder.py b/lib/iris/tests/unit/analysis/trajectory/test_UnstructuredNearestNeighbourRegridder.py deleted file mode 100644 index a652ceb72e..0000000000 --- a/lib/iris/tests/unit/analysis/trajectory/test_UnstructuredNearestNeighbourRegridder.py +++ /dev/null @@ -1,328 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:class:`iris.analysis.trajectory.UnstructuredNearestNeigbourRegridder`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.analysis.trajectory import ( - UnstructuredNearestNeigbourRegridder as unn_gridder, -) -from iris.coord_systems import GeogCS, RotatedGeogCS -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube, CubeList - - -class MixinExampleSetup: - # Common code for regridder test classes. - - def setUp(self): - # Basic test values. - src_x_y_value = np.array( - [ - [20.12, 11.73, 0.01], - [120.23, -20.73, 1.12], - [290.34, 33.88, 2.23], - [-310.45, 57.8, 3.34], - ] - ) - tgt_grid_x = np.array([-173.2, -100.3, -32.5, 1.4, 46.6, 150.7]) - tgt_grid_y = np.array([-80.1, -30.2, 0.3, 47.4, 75.5]) - - # Make sample 1-D source cube. - src = Cube(src_x_y_value[:, 2]) - src.add_aux_coord( - AuxCoord( - src_x_y_value[:, 0], standard_name="longitude", units="degrees" - ), - 0, - ) - src.add_aux_coord( - AuxCoord( - src_x_y_value[:, 1], standard_name="latitude", units="degrees" - ), - 0, - ) - self.src_cube = src - - # Make sample grid cube. - grid = Cube(np.zeros(tgt_grid_y.shape + tgt_grid_x.shape)) - grid.add_dim_coord( - DimCoord(tgt_grid_y, standard_name="latitude", units="degrees"), 0 - ) - grid.add_dim_coord( - DimCoord(tgt_grid_x, standard_name="longitude", units="degrees"), 1 - ) - self.grid_cube = grid - - # Make expected-result, from the expected source-index at each point. - expected_result_indices = np.array( - [ - [1, 1, 1, 1, 1, 1], - [1, 2, 0, 0, 0, 1], - [1, 2, 2, 0, 0, 1], - [3, 2, 2, 3, 3, 3], - [3, 2, 3, 3, 3, 3], - ] - ) - self.expected_data = self.src_cube.data[expected_result_indices] - - # Make a 3D source cube, based on the existing 2d test data. - z_cubes = [src.copy() for _ in range(3)] - for i_z, z_cube in enumerate(z_cubes): - z_cube.add_aux_coord(DimCoord([i_z], long_name="z")) - z_cube.data = z_cube.data + 100.0 * i_z - self.src_z_cube = CubeList(z_cubes).merge_cube() - - # Make a corresponding 3d expected result. - self.expected_data_zxy = self.src_z_cube.data[ - :, expected_result_indices - ] - - def _check_expected( - self, - src_cube=None, - grid_cube=None, - expected_data=None, - expected_coord_names=None, - ): - # Test regridder creation + operation against expected results. - if src_cube is None: - src_cube = self.src_cube - if grid_cube is None: - grid_cube = self.grid_cube - gridder = unn_gridder(src_cube, grid_cube) - result = gridder(src_cube) - if expected_coord_names is not None: - # Check result coordinate identities. - self.assertEqual( - [coord.name() for coord in result.coords()], - expected_coord_names, - ) - if expected_data is None: - # By default, check against the 'standard' data result. - expected_data = self.expected_data - self.assertArrayEqual(result.data, expected_data) - return result - - -class Test__init__(MixinExampleSetup, tests.IrisTest): - # Exercise all the constructor argument checks. - - def test_fail_no_src_x(self): - self.src_cube.remove_coord("longitude") - msg_re = "Source cube must have X- and Y-axis coordinates" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_no_src_y(self): - self.src_cube.remove_coord("latitude") - msg_re = "Source cube must have X- and Y-axis coordinates" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_bad_src_dims(self): - self.src_cube = self.grid_cube - msg_re = "Source.*same cube dimensions" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_mixed_latlons(self): - self.src_cube.coord("longitude").rename("projection_x_coordinate") - msg_re = "any.*latitudes/longitudes.*all must be" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_bad_latlon_units(self): - self.grid_cube.coord("longitude").units = "m" - msg_re = 'does not convert to "degrees"' - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_non_latlon_units_mismatch(self): - # Convert all to non-latlon system (does work: see in "Test__call__"). - for cube in (self.src_cube, self.grid_cube): - for axis_name in ("x", "y"): - coord = cube.coord(axis=axis_name) - coord_name = "projection_{}_coordinate".format(axis_name) - coord.rename(coord_name) - coord.units = "m" - # Change one of the output units. - self.grid_cube.coord(axis="x").units = "1" - msg_re = "Source and target.*must have the same units" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_no_tgt_x(self): - self.grid_cube.remove_coord("longitude") - msg_re = "must contain a single 1D x coordinate" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_no_tgt_y(self): - self.grid_cube.remove_coord("latitude") - msg_re = "must contain a single 1D y coordinate" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_src_cs_mismatch(self): - cs = GeogCS(1000.0) - self.src_cube.coord("latitude").coord_system = cs - msg_re = "must all have the same coordinate system" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_tgt_cs_mismatch(self): - cs = GeogCS(1000.0) - self.grid_cube.coord("latitude").coord_system = cs - msg_re = "x.*and y.*must have the same coordinate system" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - def test_fail_src_tgt_cs_mismatch(self): - cs = GeogCS(1000.0) - self.src_cube.coord("latitude").coord_system = cs - self.src_cube.coord("longitude").coord_system = cs - msg_re = "Source and target.*same coordinate system" - with self.assertRaisesRegex(ValueError, msg_re): - unn_gridder(self.src_cube, self.grid_cube) - - -class Test__call__(MixinExampleSetup, tests.IrisTest): - # Test regridder operation and results. - - def test_basic_latlon(self): - # Check a test operation. - self._check_expected( - expected_coord_names=["latitude", "longitude"], - expected_data=self.expected_data, - ) - - def test_non_latlon(self): - # Check different answer in cartesian coordinates (no wrapping, etc). - # Convert to non-latlon system, with the same coord values. - for cube in (self.src_cube, self.grid_cube): - for axis_name in ("x", "y"): - coord = cube.coord(axis=axis_name) - coord_name = "projection_{}_coordinate".format(axis_name) - coord.rename(coord_name) - coord.units = "m" - # Check for a somewhat different result. - non_latlon_indices = np.array( - [ - [3, 0, 0, 0, 1, 1], - [3, 0, 0, 0, 0, 1], - [3, 0, 0, 0, 0, 1], - [3, 0, 0, 0, 0, 1], - [3, 0, 0, 0, 0, 1], - ] - ) - expected_data = self.src_cube.data[non_latlon_indices] - self._check_expected(expected_data=expected_data) - - def test_multidimensional_xy(self): - # Recast the 4-point source cube as 2*2 : should yield the same result. - co_x = self.src_cube.coord(axis="x") - co_y = self.src_cube.coord(axis="y") - new_src = Cube(self.src_cube.data.reshape((2, 2))) - new_x_co = AuxCoord( - co_x.points.reshape((2, 2)), - standard_name="longitude", - units="degrees", - ) - new_y_co = AuxCoord( - co_y.points.reshape((2, 2)), - standard_name="latitude", - units="degrees", - ) - new_src.add_aux_coord(new_x_co, (0, 1)) - new_src.add_aux_coord(new_y_co, (0, 1)) - self._check_expected(src_cube=new_src) - - def test_transposed_grid(self): - # Show that changing the order of the grid X and Y has no effect. - new_grid_cube = self.grid_cube.copy() - new_grid_cube.transpose((1, 0)) - # Check that the new grid is in (X, Y) order. - self.assertEqual( - [coord.name() for coord in new_grid_cube.coords()], - ["longitude", "latitude"], - ) - # Check that the result is the same, dimension order is still Y,X. - self._check_expected( - grid_cube=new_grid_cube, - expected_coord_names=["latitude", "longitude"], - ) - - def test_compatible_source(self): - # Check operation on data with different dimensions to the original - # source cube for the regridder creation. - gridder = unn_gridder(self.src_cube, self.grid_cube) - result = gridder(self.src_z_cube) - self.assertEqual( - [coord.name() for coord in result.coords()], - ["z", "latitude", "longitude"], - ) - self.assertArrayEqual(result.data, self.expected_data_zxy) - - def test_fail_incompatible_source(self): - # Check that a slightly modified source cube is *not* acceptable. - modified_src_cube = self.src_cube.copy() - points = modified_src_cube.coord(axis="x").points - points[0] += 0.01 - modified_src_cube.coord(axis="x").points = points - gridder = unn_gridder(self.src_cube, self.grid_cube) - msg = "not defined on the same source grid" - with self.assertRaisesRegex(ValueError, msg): - gridder(modified_src_cube) - - def test_transposed_source(self): - # Check operation on data where the 'trajectory' dimension is not the - # last one. - src_z_cube = self.src_z_cube - src_z_cube.transpose((1, 0)) - self._check_expected( - src_cube=src_z_cube, expected_data=self.expected_data_zxy - ) - - def test_radians_degrees(self): - # Check source + target unit conversions, grid and result in degrees. - for axis_name in ("x", "y"): - self.src_cube.coord(axis=axis_name).convert_units("radians") - self.grid_cube.coord(axis=axis_name).convert_units("degrees") - result = self._check_expected() - self.assertEqual(result.coord(axis="x").units, "degrees") - - def test_degrees_radians(self): - # Check source + target unit conversions, grid and result in radians. - for axis_name in ("x", "y"): - self.src_cube.coord(axis=axis_name).convert_units("degrees") - self.grid_cube.coord(axis=axis_name).convert_units("radians") - result = self._check_expected() - self.assertEqual(result.coord(axis="x").units, "radians") - - def test_alternative_cs(self): - # Check the result is just the same in a different coordinate system. - cs = RotatedGeogCS( - grid_north_pole_latitude=75.3, - grid_north_pole_longitude=102.5, - ellipsoid=GeogCS(100.0), - ) - for cube in (self.src_cube, self.grid_cube): - for coord_name in ("longitude", "latitude"): - cube.coord(coord_name).coord_system = cs - self._check_expected() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/trajectory/test__nearest_neighbour_indices_ndcoords.py b/lib/iris/tests/unit/analysis/trajectory/test__nearest_neighbour_indices_ndcoords.py deleted file mode 100644 index 8b9e4cafa4..0000000000 --- a/lib/iris/tests/unit/analysis/trajectory/test__nearest_neighbour_indices_ndcoords.py +++ /dev/null @@ -1,228 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:meth:`iris.analysis.trajectory._nearest_neighbour_indices_ndcoords`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.analysis.trajectory import ( - _nearest_neighbour_indices_ndcoords as nn_ndinds, -) -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube - - -class Test2d(tests.IrisTest): - def test_nonlatlon_simple_2d(self): - co_y = DimCoord([10.0, 20.0], long_name="y") - co_x = DimCoord([1.0, 2.0, 3.0], long_name="x") - cube = Cube(np.zeros((2, 3))) - cube.add_dim_coord(co_y, 0) - cube.add_dim_coord(co_x, 1) - sample_point = [("x", 2.8), ("y", 18.5)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(1, 2)]) - - def test_nonlatlon_multiple_2d(self): - co_y = DimCoord([10.0, 20.0], long_name="y") - co_x = DimCoord([1.0, 2.0, 3.0], long_name="x") - cube = Cube(np.zeros((2, 3))) - cube.add_dim_coord(co_y, 0) - cube.add_dim_coord(co_x, 1) - sample_points = [("x", [2.8, -350.0, 1.7]), ("y", [18.5, 8.7, 12.2])] - result = nn_ndinds(cube, sample_points) - self.assertEqual(result, [(1, 2), (0, 0), (0, 1)]) - - def test_latlon_simple_2d(self): - co_y = DimCoord( - [10.0, 20.0], standard_name="latitude", units="degrees" - ) - co_x = DimCoord( - [1.0, 2.0, 3.0], standard_name="longitude", units="degrees" - ) - cube = Cube(np.zeros((2, 3))) - cube.add_dim_coord(co_y, 0) - cube.add_dim_coord(co_x, 1) - sample_point = [("longitude", 2.8), ("latitude", 18.5)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(1, 2)]) - - def test_latlon_multiple_2d(self): - co_y = DimCoord( - [10.0, 20.0], standard_name="latitude", units="degrees" - ) - co_x = DimCoord( - [1.0, 2.0, 3.0], standard_name="longitude", units="degrees" - ) - cube = Cube(np.zeros((2, 3))) - cube.add_dim_coord(co_y, 0) - cube.add_dim_coord(co_x, 1) - sample_points = [ - ("longitude", [2.8, -350.0, 1.7]), - ("latitude", [18.5, 8.7, 12.2]), - ] - result = nn_ndinds(cube, sample_points) - # Note slight difference from non-latlon version. - self.assertEqual(result, [(1, 2), (0, 2), (0, 1)]) - - -class Test1d(tests.IrisTest): - def test_nonlatlon_simple_1d(self): - co_x = AuxCoord([1.0, 2.0, 3.0, 1.0, 2.0, 3.0], long_name="x") - co_y = AuxCoord([10.0, 10.0, 10.0, 20.0, 20.0, 20.0], long_name="y") - cube = Cube(np.zeros(6)) - cube.add_aux_coord(co_y, 0) - cube.add_aux_coord(co_x, 0) - sample_point = [("x", 2.8), ("y", 18.5)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(5,)]) - - def test_latlon_simple_1d(self): - cube = Cube([11.0, 12.0, 13.0, 21.0, 22.0, 23.0]) - co_x = AuxCoord( - [1.0, 2.0, 3.0, 1.0, 2.0, 3.0], - standard_name="longitude", - units="degrees", - ) - co_y = AuxCoord( - [10.0, 10.0, 10.0, 20.0, 20.0, 20.0], - standard_name="latitude", - units="degrees", - ) - cube.add_aux_coord(co_y, 0) - cube.add_aux_coord(co_x, 0) - sample_point = [("longitude", 2.8), ("latitude", 18.5)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(5,)]) - - -class TestApiExtras(tests.IrisTest): - # Check operation with alternative calling setups. - def test_no_y_dim(self): - # Operate in X only, returned slice should be [:, ix]. - co_x = DimCoord([1.0, 2.0, 3.0], long_name="x") - co_y = DimCoord([10.0, 20.0], long_name="y") - cube = Cube(np.zeros((2, 3))) - cube.add_dim_coord(co_y, 0) - cube.add_dim_coord(co_x, 1) - sample_point = [("x", 2.8)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(slice(None), 2)]) - - def test_no_x_dim(self): - # Operate in Y only, returned slice should be [iy, :]. - co_x = DimCoord([1.0, 2.0, 3.0], long_name="x") - co_y = DimCoord([10.0, 20.0], long_name="y") - cube = Cube(np.zeros((2, 3))) - cube.add_dim_coord(co_y, 0) - cube.add_dim_coord(co_x, 1) - sample_point = [("y", 18.5)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(1, slice(None))]) - - def test_sample_dictionary(self): - # Pass sample_point arg as a dictionary: this usage mode is deprecated. - co_x = AuxCoord([1.0, 2.0, 3.0], long_name="x") - co_y = AuxCoord([10.0, 20.0], long_name="y") - cube = Cube(np.zeros((2, 3))) - cube.add_aux_coord(co_y, 0) - cube.add_aux_coord(co_x, 1) - sample_point = {"x": 2.8, "y": 18.5} - exp_emsg = r"must be a list of \(coordinate, value\) pairs" - with self.assertRaisesRegex(TypeError, exp_emsg): - nn_ndinds(cube, sample_point) - - -class TestLatlon(tests.IrisTest): - # Check correct calculations on lat-lon points. - def _testcube_latlon_1d(self, lats, lons): - cube = Cube(np.zeros(len(lons))) - co_x = AuxCoord(lons, standard_name="longitude", units="degrees") - co_y = AuxCoord(lats, standard_name="latitude", units="degrees") - cube.add_aux_coord(co_y, 0) - cube.add_aux_coord(co_x, 0) - return cube - - def _check_latlon_1d(self, lats, lons, sample_point, expect): - cube = self._testcube_latlon_1d(lats, lons) - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(expect,)]) - - def test_lat_scaling(self): - # Check that (88, 25) is closer to (88, 0) than to (87, 25) - self._check_latlon_1d( - lats=[88, 87], - lons=[0, 25], - sample_point=[("latitude", 88), ("longitude", 25)], - expect=0, - ) - - def test_alternate_latlon_names_okay(self): - # Check that (88, 25) is **STILL** closer to (88, 0) than to (87, 25) - # ... when coords have odd, but still recognisable, latlon names. - cube = self._testcube_latlon_1d(lats=[88, 87], lons=[0, 25]) - cube.coord("latitude").rename("y_latitude_y") - cube.coord("longitude").rename("x_longitude_x") - sample_point = [("y_latitude_y", 88), ("x_longitude_x", 25)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(0,)]) - - def test_alternate_nonlatlon_names_different(self): - # Check that (88, 25) is **NOT** closer to (88, 0) than to (87, 25) - # ... by plain XY euclidean-distance, if coords have non-latlon names. - cube = self._testcube_latlon_1d(lats=[88, 87], lons=[0, 25]) - cube.coord("latitude").rename("y") - cube.coord("longitude").rename("x") - sample_point = [("y", 88), ("x", 25)] - result = nn_ndinds(cube, sample_point) - self.assertEqual(result, [(1,)]) - - def test_lons_wrap_359_0(self): - # Check that (0, 359) is closer to (0, 0) than to (0, 350) - self._check_latlon_1d( - lats=[0, 0], - lons=[0, 350], - sample_point=[("latitude", 0), ("longitude", 359)], - expect=0, - ) - - def test_lons_wrap_359_neg1(self): - # Check that (0, 359) is closer to (0, -1) than to (0, 350) - self._check_latlon_1d( - lats=[0, 0], - lons=[350, -1], - sample_point=[("latitude", 0), ("longitude", 359)], - expect=1, - ) - - def test_lons_wrap_neg179_plus179(self): - # Check that (0, -179) is closer to (0, 179) than to (0, -170) - self._check_latlon_1d( - lats=[0, 0], - lons=[-170, 179], - sample_point=[("latitude", 0), ("longitude", -179)], - expect=1, - ) - - def test_lons_over_pole(self): - # Check that (89, 0) is closer to (89, 180) than to (85, 0) - self._check_latlon_1d( - lats=[85, 89], - lons=[0, 180], - sample_point=[("latitude", 89), ("longitude", 0)], - expect=1, - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py b/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py deleted file mode 100644 index f1b9711068..0000000000 --- a/lib/iris/tests/unit/analysis/trajectory/test_interpolate.py +++ /dev/null @@ -1,364 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :meth:`iris.analysis.trajectory.interpolate`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from collections import namedtuple - -import numpy as np -import pytest - -from iris.analysis.trajectory import interpolate -from iris.coords import AuxCoord, DimCoord -import iris.tests.stock - - -class TestFailCases(tests.IrisTest): - @tests.skip_data - def test_derived_coord(self): - cube = iris.tests.stock.realistic_4d() - sample_pts = [("altitude", [0, 10, 50])] - msg = "'altitude'.*derived coordinates are not allowed" - with self.assertRaisesRegex(ValueError, msg): - interpolate(cube, sample_pts, "nearest") - - # Try to request unknown interpolation method. - - def test_unknown_method(self): - cube = iris.tests.stock.simple_2d() - sample_point = [("x", 2.8)] - msg = "Unhandled interpolation.*linekar" - with self.assertRaisesRegex(ValueError, msg): - interpolate(cube, sample_point, method="linekar") - - -class TestNearest: - # Test interpolation with 'nearest' method. - # This is basically a wrapper to the routine: - # 'analysis._interpolate_private._nearest_neighbour_indices_ndcoords'. - # That has its own test, so we don't test the basic calculation - # exhaustively here. Instead we check the way it handles the source and - # result cubes (especially coordinates). - @pytest.fixture - def src_cube(self): - cube = iris.tests.stock.simple_3d() - # Actually, this cube *isn't* terribly realistic, as the lat+lon coords - # have integer type, which in this case produces some peculiar results. - # Let's fix that (and not bother to test the peculiar behaviour). - for coord_name in ("longitude", "latitude"): - coord = cube.coord(coord_name) - coord.points = coord.points.astype(float) - return cube - - @pytest.fixture - def single_point(self, src_cube): - # Define coordinates for a single-point testcase. - y_val, x_val = 0, -90 - - # Use slightly-different values to test nearest-neighbour operation. - sample_point = [ - ("latitude", [y_val + 19.23]), - ("longitude", [x_val - 17.54]), - ] - - # Work out cube indices of the testpoint. - single_point_iy = np.where(src_cube.coord("latitude").points == y_val)[ - 0 - ][0] - single_point_ix = np.where( - src_cube.coord("longitude").points == x_val - )[0][0] - - point = namedtuple("point", "ix iy sample_point") - return point(single_point_ix, single_point_iy, sample_point) - - @pytest.fixture - def multi_sample_points(self): - # Use latitude selection to recreate a whole row of the original cube. - return [ - ("longitude", [-180, -90, 0, 90]), - ("latitude", [0, 0, 0, 0]), - ] - - @pytest.fixture - def expected_multipoint_cube(self, src_cube): - # The result should be identical to a single latitude section of the - # original, but with modified coords (latitude has 4 repeated zeros). - expected = src_cube[:, 1, :] - # Result 'longitude' is now an aux coord. - co_x = expected.coord("longitude") - expected.remove_coord(co_x) - expected.add_aux_coord(co_x, 1) - # Result 'latitude' is now an aux coord containing 4*[0]. - expected.remove_coord("latitude") - co_y = AuxCoord( - [0, 0, 0, 0], standard_name="latitude", units="degrees" - ) - expected.add_aux_coord(co_y, 1) - - return expected - - def test_single_point_same_cube(self, src_cube, single_point): - # Check exact result matching for a single point. - result = interpolate( - src_cube, single_point.sample_point, method="nearest" - ) - # Check that the result is a single trajectory point, exactly equal to - # the expected part of the original data. - assert result.shape[-1] == 1 - result = result[..., 0] - expected = src_cube[:, single_point.iy, single_point.ix] - assert result == expected - - def test_multi_point_same_cube( - self, src_cube, multi_sample_points, expected_multipoint_cube - ): - # Check an exact result for multiple points. - result = interpolate(src_cube, multi_sample_points, method="nearest") - assert result == expected_multipoint_cube - - def test_mask_preserved( - self, src_cube, multi_sample_points, expected_multipoint_cube - ): - mask = np.zeros_like(src_cube.data) - mask[:, :, 1] = 1 - src_cube.data = np.ma.array(src_cube.data, mask=mask) - - expected_multipoint_cube.data = np.ma.array( - expected_multipoint_cube.data, mask=mask[:, 0] - ) - - result = interpolate(src_cube, multi_sample_points, method="nearest") - assert result == expected_multipoint_cube - assert np.allclose( - result.data.mask, expected_multipoint_cube.data.mask - ) - - def test_dtype_preserved( - self, src_cube, multi_sample_points, expected_multipoint_cube - ): - src_cube.data = src_cube.data.astype(np.int16) - - result = interpolate(src_cube, multi_sample_points, method="nearest") - assert result == expected_multipoint_cube - assert np.allclose(result.data, expected_multipoint_cube.data) - assert result.data.dtype == np.int16 - - def test_aux_coord_noninterpolation_dim(self, src_cube, single_point): - # Check exact result with an aux-coord mapped to an uninterpolated dim. - src_cube.add_aux_coord(DimCoord([17, 19], long_name="aux0"), 0) - - # The result cube should exactly equal a single source point. - result = interpolate( - src_cube, single_point.sample_point, method="nearest" - ) - assert result.shape[-1] == 1 - result = result[..., 0] - expected = src_cube[:, single_point.iy, single_point.ix] - assert result == expected - - def test_aux_coord_one_interp_dim(self, src_cube, single_point): - # Check exact result with an aux-coord over one interpolation dims. - src_cube.add_aux_coord( - AuxCoord([11, 12, 13, 14], long_name="aux_x"), 2 - ) - - # The result cube should exactly equal a single source point. - result = interpolate( - src_cube, single_point.sample_point, method="nearest" - ) - assert result.shape[-1] == 1 - result = result[..., 0] - expected = src_cube[:, single_point.iy, single_point.ix] - assert result == expected - - def test_aux_coord_both_interp_dims(self, src_cube, single_point): - # Check exact result with an aux-coord over both interpolation dims. - src_cube.add_aux_coord( - AuxCoord( - [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]], - long_name="aux_xy", - ), - (1, 2), - ) - - # The result cube should exactly equal a single source point. - result = interpolate( - src_cube, single_point.sample_point, method="nearest" - ) - assert result.shape[-1] == 1 - result = result[..., 0] - expected = src_cube[:, single_point.iy, single_point.ix] - assert result == expected - - def test_aux_coord_fail_mixed_dims(self, src_cube, single_point): - # Check behaviour with an aux-coord mapped over both interpolation and - # non-interpolation dims : not supported. - src_cube.add_aux_coord( - AuxCoord( - [[111, 112, 113, 114], [211, 212, 213, 214]], - long_name="aux_0x", - ), - (0, 2), - ) - msg = ( - "Coord aux_0x at one x-y position has the shape.*" - "instead of being a single point" - ) - with pytest.raises(ValueError, match=msg): - interpolate(src_cube, single_point.sample_point, method="nearest") - - def test_metadata(self, src_cube, single_point): - # Check exact result matching for a single point, with additional - # attributes and cell-methods. - src_cube.attributes["ODD_ATTR"] = "string-value-example" - src_cube.add_cell_method(iris.coords.CellMethod("mean", "area")) - result = interpolate( - src_cube, single_point.sample_point, method="nearest" - ) - # Check that the result is a single trajectory point, exactly equal to - # the expected part of the original data. - assert result.shape[-1] == 1 - result = result[..., 0] - expected = src_cube[:, single_point.iy, single_point.ix] - assert result == expected - - -class TestLinear(tests.IrisTest): - # Test interpolation with 'linear' method. - # This is basically a wrapper to 'analysis._scipy_interpolate''s - # _RegulardGridInterpolator. That has its own test, so we don't test the - # basic calculation exhaustively here. Instead we check the way it - # handles the source and result cubes (especially coordinates). - - def setUp(self): - cube = iris.tests.stock.simple_3d() - # Actually, this cube *isn't* terribly realistic, as the lat+lon coords - # have integer type, which in this case produces some peculiar results. - # Let's fix that (and not bother to test the peculiar behaviour). - for coord_name in ("longitude", "latitude"): - coord = cube.coord(coord_name) - coord.points = coord.points.astype(float) - self.test_cube = cube - # Set sample point to test single-point linear interpolation operation. - self.single_sample_point = [ - ("latitude", [9]), - ("longitude", [-120]), - ] - # Set expected results of single-point linear interpolation operation. - self.single_sample_result = np.array( - [ - 64 / 15, - 244 / 15, - ] - )[:, np.newaxis] - - def test_single_point_same_cube(self): - # Check exact result matching for a single point. - cube = self.test_cube - result = interpolate(cube, self.single_sample_point, method="linear") - # Check that the result is a single trajectory point, exactly equal to - # the expected part of the original data. - self.assertEqual(result.shape[-1], 1) - self.assertArrayAllClose(result.data, self.single_sample_result) - - def test_multi_point_same_cube(self): - # Check an exact result for multiple points. - cube = self.test_cube - # Use latitude selection to recreate a whole row of the original cube. - sample_points = [ - ("longitude", [-180, -90, 0, 90]), - ("latitude", [0, 0, 0, 0]), - ] - result = interpolate(cube, sample_points, method="linear") - - # The result should be identical to a single latitude section of the - # original, but with modified coords (latitude has 4 repeated zeros). - expected = cube[:, 1, :] - # Result 'longitude' is now an aux coord. - co_x = expected.coord("longitude") - expected.remove_coord(co_x) - expected.add_aux_coord(co_x, 1) - # Result 'latitude' is now an aux coord containing 4*[0]. - expected.remove_coord("latitude") - co_y = AuxCoord( - [0, 0, 0, 0], standard_name="latitude", units="degrees" - ) - expected.add_aux_coord(co_y, 1) - self.assertEqual(result, expected) - - def test_aux_coord_noninterpolation_dim(self): - # Check exact result with an aux-coord mapped to an uninterpolated dim. - cube = self.test_cube - cube.add_aux_coord(DimCoord([17, 19], long_name="aux0"), 0) - - # The result cube should exactly equal a single source point. - result = interpolate(cube, self.single_sample_point, method="linear") - self.assertEqual(result.shape[-1], 1) - self.assertArrayAllClose(result.data, self.single_sample_result) - - def test_aux_coord_one_interp_dim(self): - # Check exact result with an aux-coord over one interpolation dims. - cube = self.test_cube - cube.add_aux_coord(AuxCoord([11, 12, 13, 14], long_name="aux_x"), 2) - - # The result cube should exactly equal a single source point. - result = interpolate(cube, self.single_sample_point, method="linear") - self.assertEqual(result.shape[-1], 1) - self.assertArrayAllClose(result.data, self.single_sample_result) - - def test_aux_coord_both_interp_dims(self): - # Check exact result with an aux-coord over both interpolation dims. - cube = self.test_cube - cube.add_aux_coord( - AuxCoord( - [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]], - long_name="aux_xy", - ), - (1, 2), - ) - - # The result cube should exactly equal a single source point. - result = interpolate(cube, self.single_sample_point, method="linear") - self.assertEqual(result.shape[-1], 1) - self.assertArrayAllClose(result.data, self.single_sample_result) - - def test_aux_coord_fail_mixed_dims(self): - # Check behaviour with an aux-coord mapped over both interpolation and - # non-interpolation dims : not supported. - cube = self.test_cube - cube.add_aux_coord( - AuxCoord( - [[111, 112, 113, 114], [211, 212, 213, 214]], - long_name="aux_0x", - ), - (0, 2), - ) - msg = "Coord aux_0x was expected to have new points of shape .*\\. Found shape of .*\\." - with self.assertRaisesRegex(ValueError, msg): - interpolate(cube, self.single_sample_point, method="linear") - - def test_metadata(self): - # Check exact result matching for a single point, with additional - # attributes and cell-methods. - cube = self.test_cube - cube.attributes["ODD_ATTR"] = "string-value-example" - cube.add_cell_method(iris.coords.CellMethod("mean", "area")) - result = interpolate(cube, self.single_sample_point, method="linear") - # Check that the result is a single trajectory point, exactly equal to - # the expected part of the original data. - self.assertEqual(result.shape[-1], 1) - self.assertArrayAllClose(result.data, self.single_sample_result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/__init__.py b/lib/iris/tests/unit/aux_factory/__init__.py deleted file mode 100644 index 00b9f1a3bd..0000000000 --- a/lib/iris/tests/unit/aux_factory/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.aux_factory` module.""" diff --git a/lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py b/lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py deleted file mode 100644 index 6e417a3b38..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_AtmosphereSigmaFactory.py +++ /dev/null @@ -1,296 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -`iris.aux_factory.AtmosphereSigmaFactory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris.aux_factory import AtmosphereSigmaFactory -from iris.coords import AuxCoord, DimCoord - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) - self.kwargs = dict( - pressure_at_top=self.pressure_at_top, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_insufficient_coordinates_no_args(self): - with self.assertRaises(ValueError): - AtmosphereSigmaFactory() - - def test_insufficient_coordinates_no_ptop(self): - with self.assertRaises(ValueError): - AtmosphereSigmaFactory( - pressure_at_top=None, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_insufficient_coordinates_no_sigma(self): - with self.assertRaises(ValueError): - AtmosphereSigmaFactory( - pressure_at_top=self.pressure_at_top, - sigma=None, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_insufficient_coordinates_no_ps(self): - with self.assertRaises(ValueError): - AtmosphereSigmaFactory( - pressure_at_top=self.pressure_at_top, - sigma=self.sigma, - surface_air_pressure=None, - ) - - def test_ptop_shapes(self): - for shape in [(), (1,)]: - self.pressure_at_top.shape = shape - AtmosphereSigmaFactory(**self.kwargs) - - def test_ptop_invalid_shapes(self): - for shape in [(2,), (1, 1)]: - self.pressure_at_top.shape = shape - with self.assertRaises(ValueError): - AtmosphereSigmaFactory(**self.kwargs) - - def test_sigma_bounds(self): - for n_bounds in [0, 2]: - self.sigma.nbounds = n_bounds - AtmosphereSigmaFactory(**self.kwargs) - - def test_sigma_invalid_bounds(self): - for n_bounds in [-1, 1, 3]: - self.sigma.nbounds = n_bounds - with self.assertRaises(ValueError): - AtmosphereSigmaFactory(**self.kwargs) - - def test_sigma_units(self): - for units in ["1", "unknown", None]: - self.sigma.units = Unit(units) - AtmosphereSigmaFactory(**self.kwargs) - - def test_sigma_invalid_units(self): - for units in ["Pa", "m"]: - self.sigma.units = Unit(units) - with self.assertRaises(ValueError): - AtmosphereSigmaFactory(**self.kwargs) - - def test_ptop_ps_units(self): - for units in [("Pa", "Pa")]: - self.pressure_at_top.units = Unit(units[0]) - self.surface_air_pressure.units = Unit(units[1]) - AtmosphereSigmaFactory(**self.kwargs) - - def test_ptop_ps_invalid_units(self): - for units in [("Pa", "1"), ("1", "Pa"), ("bar", "Pa"), ("Pa", "hPa")]: - self.pressure_at_top.units = Unit(units[0]) - self.surface_air_pressure.units = Unit(units[1]) - with self.assertRaises(ValueError): - AtmosphereSigmaFactory(**self.kwargs) - - def test_ptop_units(self): - for units in ["Pa", "bar", "mbar", "hPa"]: - self.pressure_at_top.units = Unit(units) - self.surface_air_pressure.units = Unit(units) - AtmosphereSigmaFactory(**self.kwargs) - - def test_ptop_invalid_units(self): - for units in ["1", "m", "kg", None]: - self.pressure_at_top.units = Unit(units) - self.surface_air_pressure.units = Unit(units) - with self.assertRaises(ValueError): - AtmosphereSigmaFactory(**self.kwargs) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) - self.kwargs = dict( - pressure_at_top=self.pressure_at_top, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_values(self): - factory = AtmosphereSigmaFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) - - -class Test__derive(tests.IrisTest): - def test_function_scalar(self): - assert AtmosphereSigmaFactory._derive(0, 0, 0) == 0 - assert AtmosphereSigmaFactory._derive(3, 0, 0) == 3 - assert AtmosphereSigmaFactory._derive(0, 5, 0) == 0 - assert AtmosphereSigmaFactory._derive(0, 0, 7) == 0 - assert AtmosphereSigmaFactory._derive(3, 5, 0) == -12 - assert AtmosphereSigmaFactory._derive(3, 0, 7) == 3 - assert AtmosphereSigmaFactory._derive(0, 5, 7) == 35 - assert AtmosphereSigmaFactory._derive(3, 5, 7) == 23 - - def test_function_array(self): - ptop = 3 - sigma = np.array([2, 4]) - ps = np.arange(4).reshape(2, 2) - np.testing.assert_equal( - AtmosphereSigmaFactory._derive(ptop, sigma, ps), - [[-3, -5], [1, 3]], - ) - - -class Test_make_coord(tests.IrisTest): - @staticmethod - def coord_dims(coord): - mapping = dict( - pressure_at_top=(), - sigma=(0,), - surface_air_pressure=(1, 2), - ) - return mapping[coord.name()] - - @staticmethod - def derive(pressure_at_top, sigma, surface_air_pressure, coord=True): - result = pressure_at_top + sigma * ( - surface_air_pressure - pressure_at_top - ) - if coord: - name = "air_pressure" - result = AuxCoord( - result, - standard_name=name, - units="Pa", - ) - return result - - def setUp(self): - self.pressure_at_top = AuxCoord( - [3.0], - long_name="pressure_at_top", - units="Pa", - ) - self.sigma = DimCoord( - [1.0, 0.4, 0.1], - bounds=[[1.0, 0.6], [0.6, 0.2], [0.2, 0.0]], - long_name="sigma", - units="1", - ) - self.surface_air_pressure = AuxCoord( - [[-1.0, 2.0], [1.0, 3.0]], - long_name="surface_air_pressure", - units="Pa", - ) - self.kwargs = dict( - pressure_at_top=self.pressure_at_top, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_derived_coord(self): - # Broadcast expected points given the known dimensional mapping - pressure_at_top = self.pressure_at_top.points[0] - sigma = self.sigma.points[..., np.newaxis, np.newaxis] - surface_air_pressure = self.surface_air_pressure.points[ - np.newaxis, ... - ] - - # Calculate the expected result - - expected_coord = self.derive( - pressure_at_top, sigma, surface_air_pressure - ) - - # Calculate the actual result - factory = AtmosphereSigmaFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - - # Check bounds - expected_bounds = [ - [[[-1.0, 0.6], [2.0, 2.4]], [[1.0, 1.8], [3.0, 3.0]]], - [[[0.6, 2.2], [2.4, 2.8]], [[1.8, 2.6], [3.0, 3.0]]], - [[[2.2, 3.0], [2.8, 3.0]], [[2.6, 3.0], [3.0, 3.0]]], - ] - np.testing.assert_allclose(coord.bounds, expected_bounds) - coord.bounds = None - - # Check points and metadata - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) - self.kwargs = dict( - pressure_at_top=self.pressure_at_top, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - self.factory = AtmosphereSigmaFactory(**self.kwargs) - - def test_pressure_at_top(self): - new_pressure_at_top = mock.Mock(units=Unit("Pa"), nbounds=0, shape=()) - self.factory.update(self.pressure_at_top, new_pressure_at_top) - self.assertIs(self.factory.pressure_at_top, new_pressure_at_top) - - def test_pressure_at_top_wrong_shape(self): - new_pressure_at_top = mock.Mock( - units=Unit("Pa"), nbounds=0, shape=(2,) - ) - with self.assertRaises(ValueError): - self.factory.update(self.pressure_at_top, new_pressure_at_top) - - def test_sigma(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.sigma, new_sigma) - self.assertIs(self.factory.sigma, new_sigma) - - def test_sigma_too_many_bounds(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.sigma, new_sigma) - - def test_sigma_incompatible_units(self): - new_sigma = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.sigma, new_sigma) - - def test_surface_air_pressure(self): - new_surface_air_pressure = mock.Mock(units=Unit("Pa"), nbounds=0) - self.factory.update( - self.surface_air_pressure, new_surface_air_pressure - ) - self.assertIs( - self.factory.surface_air_pressure, new_surface_air_pressure - ) - - def test_surface_air_pressure_incompatible_units(self): - new_surface_air_pressure = mock.Mock(units=Unit("mbar"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update( - self.surface_air_pressure, new_surface_air_pressure - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py b/lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py deleted file mode 100644 index 3375f63bf2..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_AuxCoordFactory.py +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for `iris.aux_factory.AuxCoordFactory`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -import iris -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.aux_factory import AuxCoordFactory -from iris.coords import AuxCoord - - -class Test__nd_points(tests.IrisTest): - def test_numpy_scalar_coord__zero_ndim(self): - points = np.array(1) - coord = AuxCoord(points) - result = AuxCoordFactory._nd_points(coord, (), 0) - expected = np.array([1]) - self.assertArrayEqual(result, expected) - - def test_numpy_scalar_coord(self): - value = 1 - points = np.array(value) - coord = AuxCoord(points) - result = AuxCoordFactory._nd_points(coord, (), 2) - expected = np.array(value).reshape(1, 1) - self.assertArrayEqual(result, expected) - - def test_numpy_simple(self): - points = np.arange(12).reshape(4, 3) - coord = AuxCoord(points) - result = AuxCoordFactory._nd_points(coord, (0, 1), 2) - expected = points - self.assertArrayEqual(result, expected) - - def test_numpy_complex(self): - points = np.arange(12).reshape(4, 3) - coord = AuxCoord(points) - result = AuxCoordFactory._nd_points(coord, (3, 2), 5) - expected = points.T[np.newaxis, np.newaxis, ..., np.newaxis] - self.assertArrayEqual(result, expected) - - def test_lazy_simple(self): - raw_points = np.arange(12).reshape(4, 3) - points = as_lazy_data(raw_points, raw_points.shape) - coord = AuxCoord(points) - self.assertTrue(is_lazy_data(coord.core_points())) - result = AuxCoordFactory._nd_points(coord, (0, 1), 2) - # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_points())) - self.assertTrue(is_lazy_data(result)) - expected = raw_points - self.assertArrayEqual(result, expected) - - def test_lazy_complex(self): - raw_points = np.arange(12).reshape(4, 3) - points = as_lazy_data(raw_points, raw_points.shape) - coord = AuxCoord(points) - self.assertTrue(is_lazy_data(coord.core_points())) - result = AuxCoordFactory._nd_points(coord, (3, 2), 5) - # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_points())) - self.assertTrue(is_lazy_data(result)) - expected = raw_points.T[np.newaxis, np.newaxis, ..., np.newaxis] - self.assertArrayEqual(result, expected) - - -class Test__nd_bounds(tests.IrisTest): - def test_numpy_scalar_coord__zero_ndim(self): - points = np.array(0.5) - bounds = np.arange(2) - coord = AuxCoord(points, bounds=bounds) - result = AuxCoordFactory._nd_bounds(coord, (), 0) - expected = bounds - self.assertArrayEqual(result, expected) - - def test_numpy_scalar_coord(self): - points = np.array(0.5) - bounds = np.arange(2).reshape(1, 2) - coord = AuxCoord(points, bounds=bounds) - result = AuxCoordFactory._nd_bounds(coord, (), 2) - expected = bounds[np.newaxis] - self.assertArrayEqual(result, expected) - - def test_numpy_simple(self): - points = np.arange(12).reshape(4, 3) - bounds = np.arange(24).reshape(4, 3, 2) - coord = AuxCoord(points, bounds=bounds) - result = AuxCoordFactory._nd_bounds(coord, (0, 1), 2) - expected = bounds - self.assertArrayEqual(result, expected) - - def test_numpy_complex(self): - points = np.arange(12).reshape(4, 3) - bounds = np.arange(24).reshape(4, 3, 2) - coord = AuxCoord(points, bounds=bounds) - result = AuxCoordFactory._nd_bounds(coord, (3, 2), 5) - expected = bounds.transpose((1, 0, 2)).reshape(1, 1, 3, 4, 1, 2) - self.assertArrayEqual(result, expected) - - def test_lazy_simple(self): - raw_points = np.arange(12).reshape(4, 3) - points = as_lazy_data(raw_points, raw_points.shape) - raw_bounds = np.arange(24).reshape(4, 3, 2) - bounds = as_lazy_data(raw_bounds, raw_bounds.shape) - coord = AuxCoord(points, bounds=bounds) - self.assertTrue(is_lazy_data(coord.core_bounds())) - result = AuxCoordFactory._nd_bounds(coord, (0, 1), 2) - # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_bounds())) - self.assertTrue(is_lazy_data(result)) - expected = raw_bounds - self.assertArrayEqual(result, expected) - - def test_lazy_complex(self): - raw_points = np.arange(12).reshape(4, 3) - points = as_lazy_data(raw_points, raw_points.shape) - raw_bounds = np.arange(24).reshape(4, 3, 2) - bounds = as_lazy_data(raw_bounds, raw_bounds.shape) - coord = AuxCoord(points, bounds=bounds) - self.assertTrue(is_lazy_data(coord.core_bounds())) - result = AuxCoordFactory._nd_bounds(coord, (3, 2), 5) - # Check we haven't triggered the loading of the coordinate values. - self.assertTrue(is_lazy_data(coord.core_bounds())) - self.assertTrue(is_lazy_data(result)) - expected = raw_bounds.transpose((1, 0, 2)).reshape(1, 1, 3, 4, 1, 2) - self.assertArrayEqual(result, expected) - - -@tests.skip_data -class Test_lazy_aux_coords(tests.IrisTest): - def setUp(self): - path = tests.get_data_path( - ["NetCDF", "testing", "small_theta_colpex.nc"] - ) - self.cube = iris.load_cube(path, "air_potential_temperature") - - def _check_lazy(self): - coords = self.cube.aux_coords + self.cube.derived_coords - for coord in coords: - self.assertTrue(coord.has_lazy_points()) - if coord.has_bounds(): - self.assertTrue(coord.has_lazy_bounds()) - - def test_lazy_coord_loading(self): - # Test that points and bounds arrays stay lazy upon cube loading. - self._check_lazy() - - def test_lazy_coord_printing(self): - # Test that points and bounds arrays stay lazy after cube printing. - _ = str(self.cube) - self._check_lazy() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py b/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py deleted file mode 100644 index 48fead3aa5..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_HybridPressureFactory.py +++ /dev/null @@ -1,309 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -`iris.aux_factory.HybridPressureFactory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import cf_units -import numpy as np - -import iris -from iris.aux_factory import HybridPressureFactory - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.delta = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.sigma = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock( - units=cf_units.Unit("Pa"), nbounds=0 - ) - - def test_insufficient_coords(self): - with self.assertRaises(ValueError): - HybridPressureFactory() - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=None, sigma=self.sigma, surface_air_pressure=None - ) - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=None, - sigma=None, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_incompatible_delta_units(self): - self.delta.units = cf_units.Unit("m") - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_incompatible_sigma_units(self): - self.sigma.units = cf_units.Unit("Pa") - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_incompatible_surface_air_pressure_units(self): - self.surface_air_pressure.units = cf_units.Unit("unknown") - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_different_pressure_units(self): - self.delta.units = cf_units.Unit("hPa") - self.surface_air_pressure.units = cf_units.Unit("Pa") - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_too_many_delta_bounds(self): - self.delta.nbounds = 4 - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_too_many_sigma_bounds(self): - self.sigma.nbounds = 4 - with self.assertRaises(ValueError): - HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_factory_metadata(self): - factory = HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - self.assertEqual(factory.standard_name, "air_pressure") - self.assertIsNone(factory.long_name) - self.assertIsNone(factory.var_name) - self.assertEqual(factory.units, self.delta.units) - self.assertEqual(factory.units, self.surface_air_pressure.units) - self.assertIsNone(factory.coord_system) - self.assertEqual(factory.attributes, {}) - - def test_promote_sigma_units_unknown_to_dimensionless(self): - sigma = mock.Mock(units=cf_units.Unit("unknown"), nbounds=0) - factory = HybridPressureFactory( - delta=self.delta, - sigma=sigma, - surface_air_pressure=self.surface_air_pressure, - ) - self.assertEqual("1", factory.dependencies["sigma"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.delta = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.sigma = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock( - units=cf_units.Unit("Pa"), nbounds=0 - ) - - def test_value(self): - kwargs = dict( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - factory = HybridPressureFactory(**kwargs) - self.assertEqual(factory.dependencies, kwargs) - - -class Test_make_coord(tests.IrisTest): - @staticmethod - def coords_dims_func(coord): - mapping = dict( - level_pressure=(0,), sigma=(0,), surface_air_pressure=(1, 2) - ) - return mapping[coord.name()] - - def setUp(self): - self.delta = iris.coords.DimCoord( - [0.0, 1.0, 2.0], long_name="level_pressure", units="Pa" - ) - self.sigma = iris.coords.DimCoord( - [1.0, 0.9, 0.8], long_name="sigma", units="1" - ) - self.surface_air_pressure = iris.coords.AuxCoord( - np.arange(4).reshape(2, 2), "surface_air_pressure", units="Pa" - ) - - def test_points_only(self): - # Determine expected coord by manually broadcasting coord points - # knowing the dimension mapping. - delta_pts = self.delta.points[..., np.newaxis, np.newaxis] - sigma_pts = self.sigma.points[..., np.newaxis, np.newaxis] - surf_pts = self.surface_air_pressure.points[np.newaxis, ...] - expected_points = delta_pts + sigma_pts * surf_pts - expected_coord = iris.coords.AuxCoord( - expected_points, standard_name="air_pressure", units="Pa" - ) - factory = HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) - - def test_none_delta(self): - delta_pts = 0 - sigma_pts = self.sigma.points[..., np.newaxis, np.newaxis] - surf_pts = self.surface_air_pressure.points[np.newaxis, ...] - expected_points = delta_pts + sigma_pts * surf_pts - expected_coord = iris.coords.AuxCoord( - expected_points, standard_name="air_pressure", units="Pa" - ) - factory = HybridPressureFactory( - sigma=self.sigma, surface_air_pressure=self.surface_air_pressure - ) - derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) - - def test_none_sigma(self): - delta_pts = self.delta.points[..., np.newaxis, np.newaxis] - sigma_pts = 0 - surf_pts = self.surface_air_pressure.points[np.newaxis, ...] - expected_points = delta_pts + sigma_pts * surf_pts - expected_coord = iris.coords.AuxCoord( - expected_points, standard_name="air_pressure", units="Pa" - ) - factory = HybridPressureFactory( - delta=self.delta, surface_air_pressure=self.surface_air_pressure - ) - derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) - - def test_none_surface_air_pressure(self): - # Note absence of broadcasting as multidimensional coord - # is not present. - expected_points = self.delta.points - expected_coord = iris.coords.AuxCoord( - expected_points, standard_name="air_pressure", units="Pa" - ) - factory = HybridPressureFactory(delta=self.delta, sigma=self.sigma) - derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) - - def test_with_bounds(self): - self.delta.guess_bounds(0) - self.sigma.guess_bounds(0.5) - # Determine expected coord by manually broadcasting coord points - # and bounds based on the dimension mapping. - delta_pts = self.delta.points[..., np.newaxis, np.newaxis] - sigma_pts = self.sigma.points[..., np.newaxis, np.newaxis] - surf_pts = self.surface_air_pressure.points[np.newaxis, ...] - expected_points = delta_pts + sigma_pts * surf_pts - delta_vals = self.delta.bounds.reshape(3, 1, 1, 2) - sigma_vals = self.sigma.bounds.reshape(3, 1, 1, 2) - surf_vals = self.surface_air_pressure.points.reshape(1, 2, 2, 1) - expected_bounds = delta_vals + sigma_vals * surf_vals - expected_coord = iris.coords.AuxCoord( - expected_points, - standard_name="air_pressure", - units="Pa", - bounds=expected_bounds, - ) - factory = HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - derived_coord = factory.make_coord(self.coords_dims_func) - self.assertEqual(expected_coord, derived_coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.delta = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.sigma = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - self.surface_air_pressure = mock.Mock( - units=cf_units.Unit("Pa"), nbounds=0 - ) - - self.factory = HybridPressureFactory( - delta=self.delta, - sigma=self.sigma, - surface_air_pressure=self.surface_air_pressure, - ) - - def test_good_delta(self): - new_delta_coord = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.factory.update(self.delta, new_delta_coord) - self.assertIs(self.factory.delta, new_delta_coord) - - def test_bad_delta(self): - new_delta_coord = mock.Mock(units=cf_units.Unit("1"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.delta, new_delta_coord) - - def test_alternative_bad_delta(self): - new_delta_coord = mock.Mock(units=cf_units.Unit("Pa"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.delta, new_delta_coord) - - def test_good_surface_air_pressure(self): - new_surface_p_coord = mock.Mock(units=cf_units.Unit("Pa"), nbounds=0) - self.factory.update(self.surface_air_pressure, new_surface_p_coord) - self.assertIs(self.factory.surface_air_pressure, new_surface_p_coord) - - def test_bad_surface_air_pressure(self): - new_surface_p_coord = mock.Mock(units=cf_units.Unit("km"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.surface_air_pressure, new_surface_p_coord) - - def test_non_dependency(self): - old_coord = mock.Mock() - new_coord = mock.Mock() - orig_dependencies = self.factory.dependencies - self.factory.update(old_coord, new_coord) - self.assertEqual(orig_dependencies, self.factory.dependencies) - - def test_none_delta(self): - self.factory.update(self.delta, None) - self.assertIsNone(self.factory.delta) - - def test_none_sigma(self): - self.factory.update(self.sigma, None) - self.assertIsNone(self.factory.sigma) - - def test_insufficient_coords(self): - self.factory.update(self.delta, None) - with self.assertRaises(ValueError): - self.factory.update(self.surface_air_pressure, None) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py deleted file mode 100644 index f588c9f001..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_OceanSFactory.py +++ /dev/null @@ -1,325 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -`iris.aux_factory.OceanSFactory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris.aux_factory import OceanSFactory -from iris.coords import AuxCoord, DimCoord - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - eta=self.eta, - depth=self.depth, - a=self.a, - b=self.b, - depth_c=self.depth_c, - ) - - def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): - OceanSFactory() - with self.assertRaises(ValueError): - OceanSFactory( - s=None, - eta=self.eta, - depth=self.depth, - a=self.a, - b=self.b, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSFactory( - s=self.s, - eta=None, - depth=self.depth, - a=self.a, - b=self.b, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSFactory( - s=self.s, - eta=self.eta, - depth=None, - a=self.a, - b=self.b, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSFactory( - s=self.s, - eta=self.eta, - depth=self.depth, - a=None, - b=self.b, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSFactory( - s=self.s, - eta=self.eta, - depth=self.depth, - a=self.a, - b=None, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSFactory( - s=self.s, - eta=self.eta, - depth=self.depth, - a=self.a, - b=self.b, - depth_c=None, - ) - - def test_s_too_many_bounds(self): - self.s.nbounds = 4 - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_a_non_scalar(self): - self.a.shape = (2,) - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_b_non_scalar(self): - self.b.shape = (2,) - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_depth_c_non_scalar(self): - self.depth_c.shape = (2,) - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_s_incompatible_units(self): - self.s.units = Unit("km") - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_eta_incompatible_units(self): - self.eta.units = Unit("km") - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_depth_c_incompatible_units(self): - self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_depth_incompatible_units(self): - self.depth.units = Unit("km") - with self.assertRaises(ValueError): - OceanSFactory(**self.kwargs) - - def test_promote_s_units_unknown_to_dimensionless(self): - s = mock.Mock(units=Unit("unknown"), nbounds=0) - self.kwargs["s"] = s - factory = OceanSFactory(**self.kwargs) - self.assertEqual("1", factory.dependencies["s"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - eta=self.eta, - depth=self.depth, - a=self.a, - b=self.b, - depth_c=self.depth_c, - ) - - def test_values(self): - factory = OceanSFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) - - -class Test_make_coord(tests.IrisTest): - @staticmethod - def coord_dims(coord): - mapping = dict( - s=(0,), eta=(1, 2), depth=(1, 2), a=(), b=(), depth_c=() - ) - return mapping[coord.name()] - - @staticmethod - def derive(s, eta, depth, a, b, depth_c, coord=True): - c = (1 - b) * np.sinh(a * s) / np.sinh(a) + b * ( - np.tanh(a * (s + 0.5)) / (2 * np.tanh(0.5 * a)) - 0.5 - ) - result = eta * (1 + s) + depth_c * s + (depth - depth_c) * c - if coord: - name = "sea_surface_height_above_reference_ellipsoid" - result = AuxCoord( - result, - standard_name=name, - units="m", - attributes=dict(positive="up"), - ) - return result - - def setUp(self): - self.s = DimCoord( - np.arange(-0.975, 0, 0.05, dtype=float), units="1", long_name="s" - ) - self.eta = AuxCoord( - np.arange(-1, 3, dtype=np.float64).reshape(2, 2), - long_name="eta", - units="m", - ) - self.depth = AuxCoord( - np.arange(4, dtype=np.float64).reshape(2, 2) * 1e3, - long_name="depth", - units="m", - ) - self.a = AuxCoord([4], units="1", long_name="a") - self.b = AuxCoord([0.9], units="1", long_name="b") - self.depth_c = AuxCoord([4], long_name="depth_c", units="m") - self.kwargs = dict( - s=self.s, - eta=self.eta, - depth=self.depth, - a=self.a, - b=self.b, - depth_c=self.depth_c, - ) - - def test_derived_points(self): - # Broadcast expected points given the known dimensional mapping. - s = self.s.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - a = self.a.points - b = self.b.points - depth_c = self.depth_c.points - # Calculate the expected result. - expected_coord = self.derive(s, eta, depth, a, b, depth_c) - # Calculate the actual result. - factory = OceanSFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - eta=self.eta, - depth=self.depth, - a=self.a, - b=self.b, - depth_c=self.depth_c, - ) - self.factory = OceanSFactory(**self.kwargs) - - def test_s(self): - new_s = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.s, new_s) - self.assertIs(self.factory.s, new_s) - - def test_s_too_many_bounds(self): - new_s = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.s, new_s) - - def test_s_incompatible_units(self): - new_s = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.s, new_s) - - def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) - - def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.eta, new_eta) - - def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) - - def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.depth, new_depth) - - def test_a(self): - new_a = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.factory.update(self.a, new_a) - self.assertIs(self.factory.a, new_a) - - def test_a_non_scalar(self): - new_a = mock.Mock(units=Unit("1"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): - self.factory.update(self.a, new_a) - - def test_b(self): - new_b = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.factory.update(self.b, new_b) - self.assertIs(self.factory.b, new_b) - - def test_b_non_scalar(self): - new_b = mock.Mock(units=Unit("1"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): - self.factory.update(self.b, new_b) - - def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) - - def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py b/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py deleted file mode 100644 index 7a2f4c631c..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_OceanSg1Factory.py +++ /dev/null @@ -1,298 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -`iris.aux_factory.OceanSg1Factory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris.aux_factory import OceanSg1Factory -from iris.coords import AuxCoord, DimCoord - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - - def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): - OceanSg1Factory() - with self.assertRaises(ValueError): - OceanSg1Factory( - s=None, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg1Factory( - s=self.s, - c=None, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg1Factory( - s=self.s, - c=self.c, - eta=None, - depth=self.depth, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg1Factory( - s=self.s, - c=self.c, - eta=self.eta, - depth=None, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg1Factory( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=None, - ) - - def test_s_too_many_bounds(self): - self.s.nbounds = 4 - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_c_too_many_bounds(self): - self.c.nbounds = 4 - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_depth_c_non_scalar(self): - self.depth_c.shape = (2,) - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_s_incompatible_units(self): - self.s.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_c_incompatible_units(self): - self.c.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_eta_incompatible_units(self): - self.eta.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_depth_c_incompatible_units(self): - self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_depth_incompatible_units(self): - self.depth.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg1Factory(**self.kwargs) - - def test_promote_c_and_s_units_unknown_to_dimensionless(self): - c = mock.Mock(units=Unit("unknown"), nbounds=0) - s = mock.Mock(units=Unit("unknown"), nbounds=0) - self.kwargs["c"] = c - self.kwargs["s"] = s - factory = OceanSg1Factory(**self.kwargs) - self.assertEqual("1", factory.dependencies["c"].units) - self.assertEqual("1", factory.dependencies["s"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - - def test_values(self): - factory = OceanSg1Factory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) - - -class Test_make_coord(tests.IrisTest): - @staticmethod - def coord_dims(coord): - mapping = dict(s=(0,), c=(0,), eta=(1, 2), depth=(1, 2), depth_c=()) - return mapping[coord.name()] - - @staticmethod - def derive(s, c, eta, depth, depth_c, coord=True): - S = depth_c * s + (depth - depth_c) * c - result = S + eta * (1 + S / depth) - if coord: - name = "sea_surface_height_above_reference_ellipsoid" - result = AuxCoord( - result, - standard_name=name, - units="m", - attributes=dict(positive="up"), - ) - return result - - def setUp(self): - self.s = DimCoord( - np.linspace(-0.985, -0.014, 36), units="1", long_name="s" - ) - self.c = DimCoord( - np.linspace(-0.959, -0.001, 36), units="1", long_name="c" - ) - self.eta = AuxCoord( - np.arange(-1, 3, dtype=np.float64).reshape(2, 2), - long_name="eta", - units="m", - ) - self.depth = AuxCoord( - np.array([[5, 200], [1000, 4000]], dtype=np.float64), - long_name="depth", - units="m", - ) - self.depth_c = AuxCoord([5], long_name="depth_c", units="m") - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - - def test_derived_points(self): - # Broadcast expected points given the known dimensional mapping. - s = self.s.points[..., np.newaxis, np.newaxis] - c = self.c.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - depth_c = self.depth_c.points - # Calculate the expected result. - expected_coord = self.derive(s, c, eta, depth, depth_c) - # Calculate the actual result. - factory = OceanSg1Factory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - self.factory = OceanSg1Factory(**self.kwargs) - - def test_s(self): - new_s = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.s, new_s) - self.assertIs(self.factory.s, new_s) - - def test_c(self): - new_c = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.c, new_c) - self.assertIs(self.factory.c, new_c) - - def test_s_too_many_bounds(self): - new_s = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.s, new_s) - - def test_c_too_many_bounds(self): - new_c = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.c, new_c) - - def test_s_incompatible_units(self): - new_s = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.s, new_s) - - def test_c_incompatible_units(self): - new_c = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.c, new_c) - - def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) - - def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.eta, new_eta) - - def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) - - def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.depth, new_depth) - - def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) - - def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py b/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py deleted file mode 100644 index 4d1f268a1e..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_OceanSg2Factory.py +++ /dev/null @@ -1,298 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -`iris.aux_factory.OceanSg2Factory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris.aux_factory import OceanSg2Factory -from iris.coords import AuxCoord, DimCoord - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - - def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): - OceanSg2Factory() - with self.assertRaises(ValueError): - OceanSg2Factory( - s=None, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg2Factory( - s=self.s, - c=None, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg2Factory( - s=self.s, - c=self.c, - eta=None, - depth=self.depth, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg2Factory( - s=self.s, - c=self.c, - eta=self.eta, - depth=None, - depth_c=self.depth_c, - ) - with self.assertRaises(ValueError): - OceanSg2Factory( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=None, - ) - - def test_s_too_many_bounds(self): - self.s.nbounds = 4 - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_c_too_many_bounds(self): - self.c.nbounds = 4 - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_depth_c_non_scalar(self): - self.depth_c.shape = (2,) - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_s_incompatible_units(self): - self.s.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_c_incompatible_units(self): - self.c.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_eta_incompatible_units(self): - self.eta.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_depth_c_incompatible_units(self): - self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_depth_incompatible_units(self): - self.depth.units = Unit("km") - with self.assertRaises(ValueError): - OceanSg2Factory(**self.kwargs) - - def test_promote_c_and_s_units_unknown_to_dimensionless(self): - c = mock.Mock(units=Unit("unknown"), nbounds=0) - s = mock.Mock(units=Unit("unknown"), nbounds=0) - self.kwargs["c"] = c - self.kwargs["s"] = s - factory = OceanSg2Factory(**self.kwargs) - self.assertEqual("1", factory.dependencies["c"].units) - self.assertEqual("1", factory.dependencies["s"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - - def test_values(self): - factory = OceanSg2Factory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) - - -class Test_make_coord(tests.IrisTest): - @staticmethod - def coord_dims(coord): - mapping = dict(s=(0,), c=(0,), eta=(1, 2), depth=(1, 2), depth_c=()) - return mapping[coord.name()] - - @staticmethod - def derive(s, c, eta, depth, depth_c, coord=True): - S = (depth_c * s + depth * c) / (depth_c + depth) - result = eta + (eta + depth) * S - if coord: - name = "sea_surface_height_above_reference_ellipsoid" - result = AuxCoord( - result, - standard_name=name, - units="m", - attributes=dict(positive="up"), - ) - return result - - def setUp(self): - self.s = DimCoord( - np.linspace(-0.985, -0.014, 36), units="1", long_name="s" - ) - self.c = DimCoord( - np.linspace(-0.959, -0.001, 36), units="1", long_name="c" - ) - self.eta = AuxCoord( - np.arange(-1, 3, dtype=np.float64).reshape(2, 2), - long_name="eta", - units="m", - ) - self.depth = AuxCoord( - np.array([[5, 200], [1000, 4000]], dtype=np.float64), - long_name="depth", - units="m", - ) - self.depth_c = AuxCoord([1], long_name="depth_c", units="m") - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - - def test_derived_points(self): - # Broadcast expected points given the known dimensional mapping. - s = self.s.points[..., np.newaxis, np.newaxis] - c = self.c.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - depth_c = self.depth_c.points - # Calculate the expected result. - expected_coord = self.derive(s, c, eta, depth, depth_c) - # Calculate the actual result. - factory = OceanSg2Factory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.s = mock.Mock(units=Unit("1"), nbounds=0) - self.c = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.kwargs = dict( - s=self.s, - c=self.c, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - ) - self.factory = OceanSg2Factory(**self.kwargs) - - def test_s(self): - new_s = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.s, new_s) - self.assertIs(self.factory.s, new_s) - - def test_c(self): - new_c = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.c, new_c) - self.assertIs(self.factory.c, new_c) - - def test_s_too_many_bounds(self): - new_s = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.s, new_s) - - def test_c_too_many_bounds(self): - new_c = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.c, new_c) - - def test_s_incompatible_units(self): - new_s = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.s, new_s) - - def test_c_incompatible_units(self): - new_c = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.c, new_c) - - def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) - - def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.eta, new_eta) - - def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) - - def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.depth, new_depth) - - def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) - - def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py deleted file mode 100644 index 30d9647952..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_OceanSigmaFactory.py +++ /dev/null @@ -1,174 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -`iris.aux_factory.OceanSigmaFactory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris.aux_factory import OceanSigmaFactory -from iris.coords import AuxCoord, DimCoord - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.kwargs = dict(sigma=self.sigma, eta=self.eta, depth=self.depth) - - def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): - OceanSigmaFactory() - with self.assertRaises(ValueError): - OceanSigmaFactory(sigma=None, eta=self.eta, depth=self.depth) - with self.assertRaises(ValueError): - OceanSigmaFactory(sigma=self.sigma, eta=None, depth=self.depth) - with self.assertRaises(ValueError): - OceanSigmaFactory(sigma=self.sigma, eta=self.eta, depth=None) - - def test_sigma_too_many_bounds(self): - self.sigma.nbounds = 4 - with self.assertRaises(ValueError): - OceanSigmaFactory(**self.kwargs) - - def test_sigma_incompatible_units(self): - self.sigma.units = Unit("km") - with self.assertRaises(ValueError): - OceanSigmaFactory(**self.kwargs) - - def test_eta_incompatible_units(self): - self.eta.units = Unit("km") - with self.assertRaises(ValueError): - OceanSigmaFactory(**self.kwargs) - - def test_depth_incompatible_units(self): - self.depth.units = Unit("km") - with self.assertRaises(ValueError): - OceanSigmaFactory(**self.kwargs) - - def test_promote_sigma_units_unknown_to_dimensionless(self): - sigma = mock.Mock(units=Unit("unknown"), nbounds=0) - self.kwargs["sigma"] = sigma - factory = OceanSigmaFactory(**self.kwargs) - self.assertEqual("1", factory.dependencies["sigma"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.kwargs = dict(sigma=self.sigma, eta=self.eta, depth=self.depth) - - def test_values(self): - factory = OceanSigmaFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) - - -class Test_make_coord(tests.IrisTest): - @staticmethod - def coord_dims(coord): - mapping = dict(sigma=(0,), eta=(1, 2), depth=(1, 2)) - return mapping[coord.name()] - - @staticmethod - def derive(sigma, eta, depth, coord=True): - result = eta + sigma * (depth + eta) - if coord: - name = "sea_surface_height_above_reference_ellipsoid" - result = AuxCoord( - result, - standard_name=name, - units="m", - attributes=dict(positive="up"), - ) - return result - - def setUp(self): - self.sigma = DimCoord( - np.linspace(-0.05, -1, 5), long_name="sigma", units="1" - ) - self.eta = AuxCoord( - np.arange(-1, 3, dtype=np.float64).reshape(2, 2), - long_name="eta", - units="m", - ) - self.depth = AuxCoord( - np.arange(4, dtype=np.float64).reshape(2, 2) * 1e3, - long_name="depth", - units="m", - ) - self.kwargs = dict(sigma=self.sigma, eta=self.eta, depth=self.depth) - - def test_derived_points(self): - # Broadcast expected points given the known dimensional mapping. - sigma = self.sigma.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - # Calculate the expected result. - expected_coord = self.derive(sigma, eta, depth) - # Calculate the actual result. - factory = OceanSigmaFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.kwargs = dict(sigma=self.sigma, eta=self.eta, depth=self.depth) - self.factory = OceanSigmaFactory(**self.kwargs) - - def test_sigma(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.sigma, new_sigma) - self.assertIs(self.factory.sigma, new_sigma) - - def test_sigma_too_many_bounds(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.sigma, new_sigma) - - def test_sigma_incompatible_units(self): - new_sigma = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.sigma, new_sigma) - - def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) - - def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.eta, new_eta) - - def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) - - def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.depth, new_depth) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py b/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py deleted file mode 100644 index 736a883846..0000000000 --- a/lib/iris/tests/unit/aux_factory/test_OceanSigmaZFactory.py +++ /dev/null @@ -1,450 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -`iris.aux_factory.OceanSigmaZFactory` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris.aux_factory import OceanSigmaZFactory -from iris.coords import AuxCoord, DimCoord - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.zlev = mock.Mock(units=Unit("m"), nbounds=0) - self.kwargs = dict( - sigma=self.sigma, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - nsigma=self.nsigma, - zlev=self.zlev, - ) - - def test_insufficient_coordinates(self): - with self.assertRaises(ValueError): - OceanSigmaZFactory() - with self.assertRaises(ValueError): - OceanSigmaZFactory( - sigma=self.sigma, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - nsigma=self.nsigma, - zlev=None, - ) - with self.assertRaises(ValueError): - OceanSigmaZFactory( - sigma=None, - eta=None, - depth=self.depth, - depth_c=self.depth_c, - nsigma=self.nsigma, - zlev=self.zlev, - ) - with self.assertRaises(ValueError): - OceanSigmaZFactory( - sigma=self.sigma, - eta=None, - depth=None, - depth_c=self.depth_c, - nsigma=self.nsigma, - zlev=self.zlev, - ) - with self.assertRaises(ValueError): - OceanSigmaZFactory( - sigma=self.sigma, - eta=None, - depth=self.depth, - depth_c=None, - nsigma=self.nsigma, - zlev=self.zlev, - ) - with self.assertRaises(ValueError): - OceanSigmaZFactory( - sigma=self.sigma, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - nsigma=None, - zlev=self.zlev, - ) - - def test_sigma_too_many_bounds(self): - self.sigma.nbounds = 4 - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_zlev_too_many_bounds(self): - self.zlev.nbounds = 4 - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_sigma_zlev_same_boundedness(self): - self.zlev.nbounds = 2 - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_depth_c_non_scalar(self): - self.depth_c.shape = (2,) - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_nsigma_non_scalar(self): - self.nsigma.shape = (4,) - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_zlev_incompatible_units(self): - self.zlev.units = Unit("Pa") - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_sigma_incompatible_units(self): - self.sigma.units = Unit("km") - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_eta_incompatible_units(self): - self.eta.units = Unit("km") - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_depth_c_incompatible_units(self): - self.depth_c.units = Unit("km") - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_depth_incompatible_units(self): - self.depth.units = Unit("km") - with self.assertRaises(ValueError): - OceanSigmaZFactory(**self.kwargs) - - def test_promote_sigma_units_unknown_to_dimensionless(self): - sigma = mock.Mock(units=Unit("unknown"), nbounds=0) - self.kwargs["sigma"] = sigma - factory = OceanSigmaZFactory(**self.kwargs) - self.assertEqual("1", factory.dependencies["sigma"].units) - - -class Test_dependencies(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.zlev = mock.Mock(units=Unit("m"), nbounds=0) - self.kwargs = dict( - sigma=self.sigma, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - nsigma=self.nsigma, - zlev=self.zlev, - ) - - def test_values(self): - factory = OceanSigmaZFactory(**self.kwargs) - self.assertEqual(factory.dependencies, self.kwargs) - - -class Test_make_coord(tests.IrisTest): - @staticmethod - def coord_dims(coord): - mapping = dict( - sigma=(0,), - eta=(1, 2), - depth=(1, 2), - depth_c=(), - nsigma=(), - zlev=(0,), - ) - return mapping[coord.name()] - - @staticmethod - def derive(sigma, eta, depth, depth_c, nsigma, zlev, coord=True): - nsigma_slice = slice(0, int(nsigma)) - temp = eta + sigma * (np.minimum(depth_c, depth) + eta) - shape = temp.shape - result = np.ones(shape, dtype=temp.dtype) * zlev - result[nsigma_slice] = temp[nsigma_slice] - if coord: - name = "sea_surface_height_above_reference_ellipsoid" - result = AuxCoord( - result, - standard_name=name, - units="m", - attributes=dict(positive="up"), - ) - return result - - def setUp(self): - self.sigma = DimCoord( - np.arange(5, dtype=np.float64) * 10, long_name="sigma", units="1" - ) - self.eta = AuxCoord( - np.arange(4, dtype=np.float64).reshape(2, 2), - long_name="eta", - units="m", - ) - self.depth = AuxCoord( - np.arange(4, dtype=np.float64).reshape(2, 2) * 10, - long_name="depth", - units="m", - ) - self.depth_c = AuxCoord([15], long_name="depth_c", units="m") - self.nsigma = AuxCoord([3], long_name="nsigma") - self.zlev = DimCoord( - np.arange(5, dtype=np.float64) * 10, long_name="zlev", units="m" - ) - self.kwargs = dict( - sigma=self.sigma, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - nsigma=self.nsigma, - zlev=self.zlev, - ) - - def test_derived_points(self): - # Broadcast expected points given the known dimensional mapping. - sigma = self.sigma.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - depth_c = self.depth_c.points - nsigma = self.nsigma.points - zlev = self.zlev.points[..., np.newaxis, np.newaxis] - # Calculate the expected result. - expected_coord = self.derive(sigma, eta, depth, depth_c, nsigma, zlev) - # Calculate the actual result. - factory = OceanSigmaZFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - def test_derived_points_with_bounds(self): - self.sigma.guess_bounds() - self.zlev.guess_bounds() - # Broadcast expected points given the known dimensional mapping. - sigma = self.sigma.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - depth_c = self.depth_c.points - nsigma = self.nsigma.points - zlev = self.zlev.points[..., np.newaxis, np.newaxis] - # Calculate the expected coordinate with points. - expected_coord = self.derive(sigma, eta, depth, depth_c, nsigma, zlev) - # Broadcast expected bounds given the known dimensional mapping. - sigma = self.sigma.bounds.reshape(sigma.shape + (2,)) - eta = self.eta.points.reshape(eta.shape + (1,)) - depth = self.depth.points.reshape(depth.shape + (1,)) - depth_c = self.depth_c.points.reshape(depth_c.shape + (1,)) - nsigma = self.nsigma.points.reshape(nsigma.shape + (1,)) - zlev = self.zlev.bounds.reshape(zlev.shape + (2,)) - # Calculate the expected bounds. - bounds = self.derive( - sigma, eta, depth, depth_c, nsigma, zlev, coord=False - ) - expected_coord.bounds = bounds - # Calculate the actual result. - factory = OceanSigmaZFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - def test_no_eta(self): - # Broadcast expected points given the known dimensional mapping. - sigma = self.sigma.points[..., np.newaxis, np.newaxis] - eta = 0 - depth = self.depth.points[np.newaxis, ...] - depth_c = self.depth_c.points - nsigma = self.nsigma.points - zlev = self.zlev.points[..., np.newaxis, np.newaxis] - # Calculate the expected result. - expected_coord = self.derive(sigma, eta, depth, depth_c, nsigma, zlev) - # Calculate the actual result. - self.kwargs["eta"] = None - factory = OceanSigmaZFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - def test_no_sigma(self): - # Broadcast expected points given the known dimensional mapping. - sigma = 0 - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - depth_c = self.depth_c.points - nsigma = self.nsigma.points - zlev = self.zlev.points[..., np.newaxis, np.newaxis] - # Calculate the expected result. - expected_coord = self.derive(sigma, eta, depth, depth_c, nsigma, zlev) - # Calculate the actual result. - self.kwargs["sigma"] = None - factory = OceanSigmaZFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - def test_no_depth_c(self): - # Broadcast expected points given the known dimensional mapping. - sigma = self.sigma.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = self.depth.points[np.newaxis, ...] - depth_c = 0 - nsigma = self.nsigma.points - zlev = self.zlev.points[..., np.newaxis, np.newaxis] - # Calculate the expected result. - expected_coord = self.derive(sigma, eta, depth, depth_c, nsigma, zlev) - # Calculate the actual result. - self.kwargs["depth_c"] = None - factory = OceanSigmaZFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - def test_no_depth(self): - # Broadcast expected points given the known dimensional mapping. - sigma = self.sigma.points[..., np.newaxis, np.newaxis] - eta = self.eta.points[np.newaxis, ...] - depth = 0 - depth_c = self.depth_c.points - nsigma = self.nsigma.points - zlev = self.zlev.points[..., np.newaxis, np.newaxis] - # Calculate the expected result. - expected_coord = self.derive(sigma, eta, depth, depth_c, nsigma, zlev) - # Calculate the actual result. - self.kwargs["depth"] = None - factory = OceanSigmaZFactory(**self.kwargs) - coord = factory.make_coord(self.coord_dims) - self.assertEqual(expected_coord, coord) - - -class Test_update(tests.IrisTest): - def setUp(self): - self.sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.eta = mock.Mock(units=Unit("m"), nbounds=0) - self.depth = mock.Mock(units=Unit("m"), nbounds=0) - self.depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.zlev = mock.Mock(units=Unit("m"), nbounds=0) - self.kwargs = dict( - sigma=self.sigma, - eta=self.eta, - depth=self.depth, - depth_c=self.depth_c, - nsigma=self.nsigma, - zlev=self.zlev, - ) - self.factory = OceanSigmaZFactory(**self.kwargs) - - def test_sigma(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=0) - self.factory.update(self.sigma, new_sigma) - self.assertIs(self.factory.sigma, new_sigma) - - def test_sigma_too_many_bounds(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.sigma, new_sigma) - - def test_sigma_zlev_same_boundedness(self): - new_sigma = mock.Mock(units=Unit("1"), nbounds=2) - with self.assertRaises(ValueError): - self.factory.update(self.sigma, new_sigma) - - def test_sigma_incompatible_units(self): - new_sigma = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.sigma, new_sigma) - - def test_eta(self): - new_eta = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.eta, new_eta) - self.assertIs(self.factory.eta, new_eta) - - def test_eta_incompatible_units(self): - new_eta = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.eta, new_eta) - - def test_depth(self): - new_depth = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.depth, new_depth) - self.assertIs(self.factory.depth, new_depth) - - def test_depth_incompatible_units(self): - new_depth = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.depth, new_depth) - - def test_depth_c(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(1,)) - self.factory.update(self.depth_c, new_depth_c) - self.assertIs(self.factory.depth_c, new_depth_c) - - def test_depth_c_non_scalar(self): - new_depth_c = mock.Mock(units=Unit("m"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - def test_depth_c_incompatible_units(self): - new_depth_c = mock.Mock(units=Unit("Pa"), nbounds=0, shape=(1,)) - with self.assertRaises(ValueError): - self.factory.update(self.depth_c, new_depth_c) - - def test_nsigma(self): - new_nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(1,)) - self.factory.update(self.nsigma, new_nsigma) - self.assertIs(self.factory.nsigma, new_nsigma) - - def test_nsigma_missing(self): - with self.assertRaises(ValueError): - self.factory.update(self.nsigma, None) - - def test_nsigma_non_scalar(self): - new_nsigma = mock.Mock(units=Unit("1"), nbounds=0, shape=(10,)) - with self.assertRaises(ValueError): - self.factory.update(self.nsigma, new_nsigma) - - def test_zlev(self): - new_zlev = mock.Mock(units=Unit("m"), nbounds=0) - self.factory.update(self.zlev, new_zlev) - self.assertIs(self.factory.zlev, new_zlev) - - def test_zlev_missing(self): - with self.assertRaises(ValueError): - self.factory.update(self.zlev, None) - - def test_zlev_too_many_bounds(self): - new_zlev = mock.Mock(units=Unit("m"), nbounds=4) - with self.assertRaises(ValueError): - self.factory.update(self.zlev, new_zlev) - - def test_zlev_same_boundedness(self): - new_zlev = mock.Mock(units=Unit("m"), nbounds=2) - with self.assertRaises(ValueError): - self.factory.update(self.zlev, new_zlev) - - def test_zlev_incompatible_units(self): - new_zlev = new_zlev = mock.Mock(units=Unit("Pa"), nbounds=0) - with self.assertRaises(ValueError): - self.factory.update(self.zlev, new_zlev) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/__init__.py b/lib/iris/tests/unit/common/__init__.py deleted file mode 100644 index 5380785042..0000000000 --- a/lib/iris/tests/unit/common/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.common` module.""" diff --git a/lib/iris/tests/unit/common/lenient/__init__.py b/lib/iris/tests/unit/common/lenient/__init__.py deleted file mode 100644 index 2a99e7a4c2..0000000000 --- a/lib/iris/tests/unit/common/lenient/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.common.lenient` package.""" diff --git a/lib/iris/tests/unit/common/lenient/test_Lenient.py b/lib/iris/tests/unit/common/lenient/test_Lenient.py deleted file mode 100644 index 62e2b24891..0000000000 --- a/lib/iris/tests/unit/common/lenient/test_Lenient.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.lenient.Lenient`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, Lenient - - -class Test___init__(tests.IrisTest): - def test_default(self): - lenient = Lenient() - expected = dict(maths=True) - self.assertEqual(expected, lenient.__dict__) - - def test_kwargs(self): - lenient = Lenient(maths=False) - expected = dict(maths=False) - self.assertEqual(expected, lenient.__dict__) - - def test_kwargs_invalid(self): - emsg = "Invalid .* option, got 'merge'." - with self.assertRaisesRegex(KeyError, emsg): - _ = Lenient(merge=True) - - -class Test___contains__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test_in(self): - self.assertIn("maths", self.lenient) - - def test_not_in(self): - self.assertNotIn("concatenate", self.lenient) - - -class Test___getitem__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test_in(self): - self.assertTrue(self.lenient["maths"]) - - def test_not_in(self): - emsg = "Invalid .* option, got 'MATHS'." - with self.assertRaisesRegex(KeyError, emsg): - _ = self.lenient["MATHS"] - - -class Test___repr__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test(self): - expected = "Lenient(maths=True)" - self.assertEqual(expected, repr(self.lenient)) - - -class Test___setitem__(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test_key_invalid(self): - emsg = "Invalid .* option, got 'MATHS." - with self.assertRaisesRegex(KeyError, emsg): - self.lenient["MATHS"] = False - - def test_maths_value_invalid(self): - value = sentinel.value - emsg = f"Invalid .* option 'maths' value, got {value!r}." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient["maths"] = value - - def test_maths_disable__lenient_enable_true(self): - self.assertTrue(_LENIENT.enable) - self.lenient["maths"] = False - self.assertFalse(self.lenient.__dict__["maths"]) - self.assertFalse(_LENIENT.enable) - - def test_maths_disable__lenient_enable_false(self): - _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.lenient["maths"] = False - self.assertFalse(self.lenient.__dict__["maths"]) - self.assertFalse(_LENIENT.enable) - - def test_maths_enable__lenient_enable_true(self): - self.assertTrue(_LENIENT.enable) - self.lenient["maths"] = True - self.assertTrue(self.lenient.__dict__["maths"]) - self.assertTrue(_LENIENT.enable) - - def test_maths_enable__lenient_enable_false(self): - _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.lenient["maths"] = True - self.assertTrue(self.lenient.__dict__["maths"]) - self.assertTrue(_LENIENT.enable) - - -class Test_context(tests.IrisTest): - def setUp(self): - self.lenient = Lenient() - - def test_nop(self): - self.assertTrue(self.lenient["maths"]) - - with self.lenient.context(): - self.assertTrue(self.lenient["maths"]) - - self.assertTrue(self.lenient["maths"]) - - def test_maths_disable__lenient_true(self): - # synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) - - with self.lenient.context(maths=False): - # still synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) - - # still synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) - - def test_maths_disable__lenient_false(self): - # not synchronised - _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) - - with self.lenient.context(maths=False): - # now synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) - - # still synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) - - def test_maths_enable__lenient_true(self): - # not synchronised - self.assertTrue(_LENIENT.enable) - self.lenient.__dict__["maths"] = False - self.assertFalse(self.lenient["maths"]) - - with self.lenient.context(maths=True): - # now synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) - - # still synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) - - def test_maths_enable__lenient_false(self): - # synchronised - _LENIENT.__dict__["enable"] = False - self.assertFalse(_LENIENT.enable) - self.lenient.__dict__["maths"] = False - self.assertFalse(self.lenient["maths"]) - - with self.lenient.context(maths=True): - # still synchronised - self.assertTrue(_LENIENT.enable) - self.assertTrue(self.lenient["maths"]) - - # still synchronised - self.assertFalse(_LENIENT.enable) - self.assertFalse(self.lenient["maths"]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test__Lenient.py b/lib/iris/tests/unit/common/lenient/test__Lenient.py deleted file mode 100644 index 44f38d9c5a..0000000000 --- a/lib/iris/tests/unit/common/lenient/test__Lenient.py +++ /dev/null @@ -1,835 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.lenient._Lenient`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from collections.abc import Iterable - -from iris.common.lenient import ( - _LENIENT_ENABLE_DEFAULT, - _LENIENT_PROTECTED, - _Lenient, - _qualname, -) - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.expected = dict(active=None, enable=_LENIENT_ENABLE_DEFAULT) - - def test_default(self): - lenient = _Lenient() - self.assertEqual(self.expected, lenient.__dict__) - - def test_args_service_str(self): - service = "service1" - lenient = _Lenient(service) - self.expected.update(dict(service1=True)) - self.assertEqual(self.expected, lenient.__dict__) - - def test_args_services_str(self): - services = ("service1", "service2") - lenient = _Lenient(*services) - self.expected.update(dict(service1=True, service2=True)) - self.assertEqual(self.expected, lenient.__dict__) - - def test_args_services_callable(self): - def service1(): - pass - - def service2(): - pass - - services = (service1, service2) - lenient = _Lenient(*services) - self.expected.update( - {_qualname(service1): True, _qualname(service2): True} - ) - self.assertEqual(self.expected, lenient.__dict__) - - def test_kwargs_client_str(self): - client = dict(client1="service1") - lenient = _Lenient(**client) - self.expected.update(dict(client1=("service1",))) - self.assertEqual(self.expected, lenient.__dict__) - - def test_kwargs_clients_str(self): - clients = dict(client1="service1", client2="service2") - lenient = _Lenient(**clients) - self.expected.update( - dict(client1=("service1",), client2=("service2",)) - ) - self.assertEqual(self.expected, lenient.__dict__) - - def test_kwargs_clients_callable(self): - def client1(): - pass - - def client2(): - pass - - def service1(): - pass - - def service2(): - pass - - qualname_client1 = _qualname(client1) - qualname_client2 = _qualname(client2) - clients = { - qualname_client1: service1, - qualname_client2: (service1, service2), - } - lenient = _Lenient(**clients) - self.expected.update( - { - _qualname(client1): (_qualname(service1),), - _qualname(client2): (_qualname(service1), _qualname(service2)), - } - ) - self.assertEqual(self.expected, lenient.__dict__) - - -class Test___call__(tests.IrisTest): - def setUp(self): - self.client = "myclient" - self.lenient = _Lenient() - - def test_missing_service_str(self): - self.assertFalse(self.lenient("myservice")) - - def test_missing_service_callable(self): - def myservice(): - pass - - self.assertFalse(self.lenient(myservice)) - - def test_disabled_service_str(self): - service = "myservice" - self.lenient.__dict__[service] = False - self.assertFalse(self.lenient(service)) - - def test_disable_service_callable(self): - def myservice(): - pass - - qualname_service = _qualname(myservice) - self.lenient.__dict__[qualname_service] = False - self.assertFalse(self.lenient(myservice)) - - def test_service_str_with_no_active_client(self): - service = "myservice" - self.lenient.__dict__[service] = True - self.assertFalse(self.lenient(service)) - - def test_service_callable_with_no_active_client(self): - def myservice(): - pass - - qualname_service = _qualname(myservice) - self.lenient.__dict__[qualname_service] = True - self.assertFalse(self.lenient(myservice)) - - def test_service_str_with_active_client_with_no_registered_services(self): - service = "myservice" - self.lenient.__dict__[service] = True - self.lenient.__dict__["active"] = self.client - self.assertFalse(self.lenient(service)) - - def test_service_callable_with_active_client_with_no_registered_services( - self, - ): - def myservice(): - pass - - def myclient(): - pass - - qualname_service = _qualname(myservice) - self.lenient.__dict__[qualname_service] = True - self.lenient.__dict__["active"] = _qualname(myclient) - self.assertFalse(self.lenient(myservice)) - - def test_service_str_with_active_client_with_unmatched_registered_services( - self, - ): - service = "myservice" - self.lenient.__dict__[service] = True - self.lenient.__dict__["active"] = self.client - self.lenient.__dict__[self.client] = ("service1", "service2") - self.assertFalse(self.lenient(service)) - - def test_service_callable_with_active_client_with_unmatched_registered_services( - self, - ): - def myservice(): - pass - - def myclient(): - pass - - qualname_service = _qualname(myservice) - qualname_client = _qualname(myclient) - self.lenient.__dict__[qualname_service] = True - self.lenient.__dict__["active"] = qualname_client - self.lenient.__dict__[qualname_client] = ("service1", "service2") - self.assertFalse(self.lenient(myservice)) - - def test_service_str_with_active_client_with_registered_services(self): - service = "myservice" - self.lenient.__dict__[service] = True - self.lenient.__dict__["active"] = self.client - self.lenient.__dict__[self.client] = ("service1", "service2", service) - self.assertTrue(self.lenient(service)) - - def test_service_callable_with_active_client_with_registered_services( - self, - ): - def myservice(): - pass - - def myclient(): - pass - - qualname_service = _qualname(myservice) - qualname_client = _qualname(myclient) - self.lenient.__dict__[qualname_service] = True - self.lenient.__dict__["active"] = qualname_client - self.lenient.__dict__[qualname_client] = ( - "service1", - "service2", - qualname_service, - ) - self.assertTrue(self.lenient(myservice)) - - def test_service_str_with_active_client_with_unmatched_registered_service_str( - self, - ): - service = "myservice" - self.lenient.__dict__[service] = True - self.lenient.__dict__["active"] = self.client - self.lenient.__dict__[self.client] = "serviceXXX" - self.assertFalse(self.lenient(service)) - - def test_service_callable_with_active_client_with_unmatched_registered_service_str( - self, - ): - def myservice(): - pass - - def myclient(): - pass - - qualname_service = _qualname(myservice) - qualname_client = _qualname(myclient) - self.lenient.__dict__[qualname_service] = True - self.lenient.__dict__["active"] = qualname_client - self.lenient.__dict__[qualname_client] = f"{qualname_service}XXX" - self.assertFalse(self.lenient(myservice)) - - def test_service_str_with_active_client_with_registered_service_str(self): - service = "myservice" - self.lenient.__dict__[service] = True - self.lenient.__dict__["active"] = self.client - self.lenient.__dict__[self.client] = service - self.assertTrue(self.lenient(service)) - - def test_service_callable_with_active_client_with_registered_service_str( - self, - ): - def myservice(): - pass - - def myclient(): - pass - - qualname_service = _qualname(myservice) - qualname_client = _qualname(myclient) - self.lenient.__dict__[qualname_service] = True - self.lenient.__dict__["active"] = qualname_client - self.lenient.__dict__[qualname_client] = qualname_service - self.assertTrue(self.lenient(myservice)) - - def test_enable(self): - service = "myservice" - self.lenient.__dict__[service] = True - self.lenient.__dict__["active"] = self.client - self.lenient.__dict__[self.client] = service - self.assertTrue(self.lenient(service)) - self.lenient.__dict__["enable"] = False - self.assertFalse(self.lenient(service)) - - -class Test___contains__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_in(self): - self.assertIn("active", self.lenient) - - def test_not_in(self): - self.assertNotIn("ACTIVATE", self.lenient) - - def test_in_qualname(self): - def func(): - pass - - qualname_func = _qualname(func) - lenient = _Lenient() - lenient.__dict__[qualname_func] = None - self.assertIn(func, lenient) - self.assertIn(qualname_func, lenient) - - -class Test___getattr__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_in(self): - self.assertIsNone(self.lenient.active) - - def test_not_in(self): - emsg = "Invalid .* option, got 'wibble'." - with self.assertRaisesRegex(AttributeError, emsg): - _ = self.lenient.wibble - - -class Test__getitem__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_in(self): - self.assertIsNone(self.lenient["active"]) - - def test_in_callable(self): - def service(): - pass - - qualname_service = _qualname(service) - self.lenient.__dict__[qualname_service] = True - self.assertTrue(self.lenient[service]) - - def test_not_in(self): - emsg = "Invalid .* option, got 'wibble'." - with self.assertRaisesRegex(KeyError, emsg): - _ = self.lenient["wibble"] - - def test_not_in_callable(self): - def service(): - pass - - qualname_service = _qualname(service) - emsg = f"Invalid .* option, got '{qualname_service}'." - with self.assertRaisesRegex(KeyError, emsg): - _ = self.lenient[service] - - -class Test___setitem__(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_in(self): - emsg = "Invalid .* option, got 'wibble'." - with self.assertRaisesRegex(KeyError, emsg): - self.lenient["wibble"] = None - - def test_in_value_str(self): - client = "client" - service = "service" - self.lenient.__dict__[client] = None - self.lenient[client] = service - self.assertEqual(self.lenient.__dict__[client], (service,)) - - def test_callable_in_value_str(self): - def client(): - pass - - service = "service" - qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = service - self.assertEqual(self.lenient.__dict__[qualname_client], (service,)) - - def test_in_value_callable(self): - def service(): - pass - - client = "client" - qualname_service = _qualname(service) - self.lenient.__dict__[client] = None - self.lenient[client] = service - self.assertEqual(self.lenient.__dict__[client], (qualname_service,)) - - def test_callable_in_value_callable(self): - def client(): - pass - - def service(): - pass - - qualname_client = _qualname(client) - qualname_service = _qualname(service) - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = service - self.assertEqual( - self.lenient.__dict__[qualname_client], (qualname_service,) - ) - - def test_in_value_bool(self): - client = "client" - self.lenient.__dict__[client] = None - self.lenient[client] = True - self.assertTrue(self.lenient.__dict__[client]) - self.assertFalse(isinstance(self.lenient.__dict__[client], Iterable)) - - def test_callable_in_value_bool(self): - def client(): - pass - - qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = True - self.assertTrue(self.lenient.__dict__[qualname_client]) - self.assertFalse( - isinstance(self.lenient.__dict__[qualname_client], Iterable) - ) - - def test_in_value_iterable(self): - client = "client" - services = ("service1", "service2") - self.lenient.__dict__[client] = None - self.lenient[client] = services - self.assertEqual(self.lenient.__dict__[client], services) - - def test_callable_in_value_iterable(self): - def client(): - pass - - qualname_client = _qualname(client) - services = ("service1", "service2") - self.lenient.__dict__[qualname_client] = None - self.lenient[client] = services - self.assertEqual(self.lenient.__dict__[qualname_client], services) - - def test_in_value_iterable_callable(self): - def service1(): - pass - - def service2(): - pass - - client = "client" - self.lenient.__dict__[client] = None - qualname_services = (_qualname(service1), _qualname(service2)) - self.lenient[client] = (service1, service2) - self.assertEqual(self.lenient.__dict__[client], qualname_services) - - def test_callable_in_value_iterable_callable(self): - def client(): - pass - - def service1(): - pass - - def service2(): - pass - - qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = None - qualname_services = (_qualname(service1), _qualname(service2)) - self.lenient[client] = (service1, service2) - self.assertEqual( - self.lenient.__dict__[qualname_client], qualname_services - ) - - def test_active_iterable(self): - active = "active" - self.assertIsNone(self.lenient.__dict__[active]) - emsg = "Invalid .* option 'active'" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient[active] = (None,) - - def test_active_str(self): - active = "active" - client = "client1" - self.assertIsNone(self.lenient.__dict__[active]) - self.lenient[active] = client - self.assertEqual(self.lenient.__dict__[active], client) - - def test_active_callable(self): - def client(): - pass - - active = "active" - qualname_client = _qualname(client) - self.assertIsNone(self.lenient.__dict__[active]) - self.lenient[active] = client - self.assertEqual(self.lenient.__dict__[active], qualname_client) - - def test_enable(self): - enable = "enable" - self.assertEqual( - self.lenient.__dict__[enable], _LENIENT_ENABLE_DEFAULT - ) - self.lenient[enable] = True - self.assertTrue(self.lenient.__dict__[enable]) - self.lenient[enable] = False - self.assertFalse(self.lenient.__dict__[enable]) - - def test_enable_invalid(self): - emsg = "Invalid .* option 'enable'" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient["enable"] = None - - -class Test_context(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - self.default = dict(active=None, enable=_LENIENT_ENABLE_DEFAULT) - - def copy(self): - return self.lenient.__dict__.copy() - - def test_nop(self): - pre = self.copy() - with self.lenient.context(): - context = self.copy() - post = self.copy() - self.assertEqual(pre, self.default) - self.assertEqual(context, self.default) - self.assertEqual(post, self.default) - - def test_active_str(self): - client = "client" - pre = self.copy() - with self.lenient.context(active=client): - context = self.copy() - post = self.copy() - self.assertEqual(pre, self.default) - expected = self.default.copy() - expected.update(dict(active=client)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) - - def test_active_callable(self): - def client(): - pass - - pre = self.copy() - with self.lenient.context(active=client): - context = self.copy() - post = self.copy() - qualname_client = _qualname(client) - self.assertEqual(pre, self.default) - expected = self.default.copy() - expected.update(dict(active=qualname_client)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) - - def test_kwargs(self): - client = "client" - self.lenient.__dict__["service1"] = False - self.lenient.__dict__["service2"] = False - pre = self.copy() - with self.lenient.context(active=client, service1=True, service2=True): - context = self.copy() - post = self.copy() - self.default.update(dict(service1=False, service2=False)) - self.assertEqual(pre, self.default) - expected = self.default.copy() - expected.update(dict(active=client, service1=True, service2=True)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) - - def test_args_str(self): - client = "client" - services = ("service1", "service2") - pre = self.copy() - with self.lenient.context(*services, active=client): - context = self.copy() - post = self.copy() - self.assertEqual(pre, self.default) - expected = self.default.copy() - expected.update(dict(active=client, client=services)) - self.assertEqual(context["active"], expected["active"]) - self.assertEqual(set(context["client"]), set(expected["client"])) - self.assertEqual(post, self.default) - - def test_args_callable(self): - def service1(): - pass - - def service2(): - pass - - client = "client" - services = (service1, service2) - pre = self.copy() - with self.lenient.context(*services, active=client): - context = self.copy() - post = self.copy() - qualname_services = tuple([_qualname(service) for service in services]) - self.assertEqual(pre, self.default) - expected = self.default.copy() - expected.update(dict(active=client, client=qualname_services)) - self.assertEqual(context["active"], expected["active"]) - self.assertEqual(set(context["client"]), set(expected["client"])) - self.assertEqual(post, self.default) - - def test_context_runtime(self): - services = ("service1", "service2") - pre = self.copy() - with self.lenient.context(*services): - context = self.copy() - post = self.copy() - self.assertEqual(pre, self.default) - expected = self.default.copy() - expected.update(dict(active="__context", __context=services)) - self.assertEqual(context, expected) - self.assertEqual(post, self.default) - - -class Test_enable(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_getter(self): - self.assertEqual(self.lenient.enable, _LENIENT_ENABLE_DEFAULT) - - def test_setter_invalid(self): - emsg = "Invalid .* option 'enable'" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.enable = 0 - - def test_setter(self): - self.assertEqual(self.lenient.enable, _LENIENT_ENABLE_DEFAULT) - self.lenient.enable = False - self.assertFalse(self.lenient.enable) - - -class Test_register_client(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_protected(self): - emsg = "Cannot register .* client" - for protected in _LENIENT_PROTECTED: - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.register_client(protected, "service") - - def test_str_service_str(self): - client = "client" - services = "service" - self.lenient.register_client(client, services) - self.assertIn(client, self.lenient.__dict__) - self.assertEqual(self.lenient.__dict__[client], (services,)) - - def test_str_services_str(self): - client = "client" - services = ("service1", "service2") - self.lenient.register_client(client, services) - self.assertIn(client, self.lenient.__dict__) - self.assertEqual(self.lenient.__dict__[client], services) - - def test_callable_service_callable(self): - def client(): - pass - - def service(): - pass - - qualname_client = _qualname(client) - qualname_service = _qualname(service) - self.lenient.register_client(client, service) - self.assertIn(qualname_client, self.lenient.__dict__) - self.assertEqual( - self.lenient.__dict__[qualname_client], (qualname_service,) - ) - - def test_callable_services_callable(self): - def client(): - pass - - def service1(): - pass - - def service2(): - pass - - qualname_client = _qualname(client) - qualname_services = (_qualname(service1), _qualname(service2)) - self.lenient.register_client(client, (service1, service2)) - self.assertIn(qualname_client, self.lenient.__dict__) - self.assertEqual( - self.lenient.__dict__[qualname_client], qualname_services - ) - - def test_services_empty(self): - emsg = "Require at least one .* client service." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.register_client("client", ()) - - def test_services_overwrite(self): - client = "client" - services = ("service1", "service2") - self.lenient.__dict__[client] = services - self.assertEqual(self.lenient[client], services) - new_services = ("service3", "service4") - self.lenient.register_client(client, services=new_services) - self.assertEqual(self.lenient[client], new_services) - - def test_services_append(self): - client = "client" - services = ("service1", "service2") - self.lenient.__dict__[client] = services - self.assertEqual(self.lenient[client], services) - new_services = ("service3", "service4") - self.lenient.register_client( - client, services=new_services, append=True - ) - expected = set(services + new_services) - self.assertEqual(set(self.lenient[client]), expected) - - -class Test_register_service(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_str(self): - service = "service" - self.assertNotIn(service, self.lenient.__dict__) - self.lenient.register_service(service) - self.assertIn(service, self.lenient.__dict__) - self.assertFalse(isinstance(self.lenient.__dict__[service], Iterable)) - self.assertTrue(self.lenient.__dict__[service]) - - def test_callable(self): - def service(): - pass - - qualname_service = _qualname(service) - self.assertNotIn(qualname_service, self.lenient.__dict__) - self.lenient.register_service(service) - self.assertIn(qualname_service, self.lenient.__dict__) - self.assertFalse( - isinstance(self.lenient.__dict__[qualname_service], Iterable) - ) - self.assertTrue(self.lenient.__dict__[qualname_service]) - - def test_not_protected(self): - emsg = "Cannot register .* service" - for protected in _LENIENT_PROTECTED: - self.lenient.__dict__[protected] = None - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.register_service("active") - - -class Test_unregister_client(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_protected(self): - emsg = "Cannot unregister .* client, as .* is a protected .* option." - for protected in _LENIENT_PROTECTED: - self.lenient.__dict__[protected] = None - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client(protected) - - def test_not_in(self): - emsg = "Cannot unregister unknown .* client" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client("client") - - def test_not_client(self): - client = "client" - self.lenient.__dict__[client] = True - emsg = "Cannot unregister .* client, as .* is not a valid .* client." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client(client) - - def test_not_client_callable(self): - def client(): - pass - - qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = True - emsg = "Cannot unregister .* client, as .* is not a valid .* client." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_client(client) - - def test_str(self): - client = "client" - self.lenient.__dict__[client] = (None,) - self.lenient.unregister_client(client) - self.assertNotIn(client, self.lenient.__dict__) - - def test_callable(self): - def client(): - pass - - qualname_client = _qualname(client) - self.lenient.__dict__[qualname_client] = (None,) - self.lenient.unregister_client(client) - self.assertNotIn(qualname_client, self.lenient.__dict__) - - -class Test_unregister_service(tests.IrisTest): - def setUp(self): - self.lenient = _Lenient() - - def test_not_protected(self): - emsg = "Cannot unregister .* service, as .* is a protected .* option." - for protected in _LENIENT_PROTECTED: - self.lenient.__dict__[protected] = None - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service(protected) - - def test_not_in(self): - emsg = "Cannot unregister unknown .* service" - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service("service") - - def test_not_service(self): - service = "service" - self.lenient.__dict__[service] = (None,) - emsg = "Cannot unregister .* service, as .* is not a valid .* service." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service(service) - - def test_not_service_callable(self): - def service(): - pass - - qualname_service = _qualname(service) - self.lenient.__dict__[qualname_service] = (None,) - emsg = "Cannot unregister .* service, as .* is not a valid .* service." - with self.assertRaisesRegex(ValueError, emsg): - self.lenient.unregister_service(service) - - def test_str(self): - service = "service" - self.lenient.__dict__[service] = True - self.lenient.unregister_service(service) - self.assertNotIn(service, self.lenient.__dict__) - - def test_callable(self): - def service(): - pass - - qualname_service = _qualname(service) - self.lenient.__dict__[qualname_service] = True - self.lenient.unregister_service(service) - self.assertNotIn(qualname_service, self.lenient.__dict__) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test__lenient_client.py b/lib/iris/tests/unit/common/lenient/test__lenient_client.py deleted file mode 100644 index 3a19563efc..0000000000 --- a/lib/iris/tests/unit/common/lenient/test__lenient_client.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.common.lenient._lenient_client`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from inspect import getmodule -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _lenient_client - - -class Test(tests.IrisTest): - def setUp(self): - module_name = getmodule(self).__name__ - self.client = f"{module_name}" + ".Test.{}..myclient" - self.service = f"{module_name}" + ".Test.{}..myservice" - self.active = "active" - self.args_in = sentinel.arg1, sentinel.arg2 - self.kwargs_in = dict(kwarg1=sentinel.kwarg1, kwarg2=sentinel.kwarg2) - - def test_args_too_many(self): - emsg = "Invalid lenient client arguments, expecting 1" - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_client(None, None) - - def test_args_not_callable(self): - emsg = "Invalid lenient client argument, expecting a callable" - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_client(None) - - def test_args_and_kwargs(self): - def func(): - pass - - emsg = ( - "Invalid lenient client, got both arguments and keyword arguments" - ) - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_client(func, services=func) - - def test_call_naked(self): - @_lenient_client - def myclient(): - return _LENIENT.__dict__.copy() - - result = myclient() - self.assertIn(self.active, result) - qualname_client = self.client.format("test_call_naked") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) - - def test_call_naked_alternative(self): - def myclient(): - return _LENIENT.__dict__.copy() - - result = _lenient_client(myclient)() - self.assertIn(self.active, result) - qualname_client = self.client.format("test_call_naked_alternative") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) - - def test_call_naked_client_args_kwargs(self): - @_lenient_client - def myclient(*args, **kwargs): - return args, kwargs - - args_out, kwargs_out = myclient(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) - - def test_call_naked_doc(self): - @_lenient_client - def myclient(): - """myclient doc-string""" - - self.assertEqual(myclient.__doc__, "myclient doc-string") - - def test_call_no_kwargs(self): - @_lenient_client() - def myclient(): - return _LENIENT.__dict__.copy() - - result = myclient() - self.assertIn(self.active, result) - qualname_client = self.client.format("test_call_no_kwargs") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) - - def test_call_no_kwargs_alternative(self): - def myclient(): - return _LENIENT.__dict__.copy() - - result = (_lenient_client())(myclient)() - self.assertIn(self.active, result) - qualname_client = self.client.format("test_call_no_kwargs_alternative") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) - - def test_call_kwargs_none(self): - @_lenient_client(services=None) - def myclient(): - return _LENIENT.__dict__.copy() - - result = myclient() - self.assertIn(self.active, result) - qualname_client = self.client.format("test_call_kwargs_none") - self.assertEqual(result[self.active], qualname_client) - self.assertNotIn(qualname_client, result) - - def test_call_kwargs_single(self): - service = sentinel.service - - @_lenient_client(services=service) - def myclient(): - return _LENIENT.__dict__.copy() - - result = myclient() - self.assertIn(self.active, result) - qualname_client = self.client.format("test_call_kwargs_single") - self.assertEqual(result[self.active], qualname_client) - self.assertIn(qualname_client, result) - self.assertEqual(result[qualname_client], (service,)) - - def test_call_kwargs_single_callable(self): - def myservice(): - pass - - @_lenient_client(services=myservice) - def myclient(): - return _LENIENT.__dict__.copy() - - test_name = "test_call_kwargs_single_callable" - result = myclient() - self.assertIn(self.active, result) - qualname_client = self.client.format(test_name) - self.assertEqual(result[self.active], qualname_client) - self.assertIn(qualname_client, result) - qualname_services = (self.service.format(test_name),) - self.assertEqual(result[qualname_client], qualname_services) - - def test_call_kwargs_iterable(self): - services = (sentinel.service1, sentinel.service2) - - @_lenient_client(services=services) - def myclient(): - return _LENIENT.__dict__.copy() - - result = myclient() - self.assertIn(self.active, result) - qualname_client = self.client.format("test_call_kwargs_iterable") - self.assertEqual(result[self.active], qualname_client) - self.assertIn(qualname_client, result) - self.assertEqual(set(result[qualname_client]), set(services)) - - def test_call_client_args_kwargs(self): - @_lenient_client() - def myclient(*args, **kwargs): - return args, kwargs - - args_out, kwargs_out = myclient(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) - - def test_call_doc(self): - @_lenient_client() - def myclient(): - """myclient doc-string""" - - self.assertEqual(myclient.__doc__, "myclient doc-string") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test__lenient_service.py b/lib/iris/tests/unit/common/lenient/test__lenient_service.py deleted file mode 100644 index 9545b137ea..0000000000 --- a/lib/iris/tests/unit/common/lenient/test__lenient_service.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.common.lenient._lenient_service`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from inspect import getmodule -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _lenient_service - - -class Test(tests.IrisTest): - def setUp(self): - module_name = getmodule(self).__name__ - self.service = f"{module_name}" + ".Test.{}..myservice" - self.args_in = sentinel.arg1, sentinel.arg2 - self.kwargs_in = dict(kwarg1=sentinel.kwarg1, kwarg2=sentinel.kwarg2) - - def test_args_too_many(self): - emsg = "Invalid lenient service arguments, expecting 1" - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_service(None, None) - - def test_args_not_callable(self): - emsg = "Invalid lenient service argument, expecting a callable" - with self.assertRaisesRegex(AssertionError, emsg): - _lenient_service(None) - - def test_call_naked(self): - @_lenient_service - def myservice(): - return _LENIENT.__dict__.copy() - - qualname_service = self.service.format("test_call_naked") - state = _LENIENT.__dict__ - self.assertIn(qualname_service, state) - self.assertTrue(state[qualname_service]) - result = myservice() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) - - def test_call_naked_alternative(self): - def myservice(): - return _LENIENT.__dict__.copy() - - qualname_service = self.service.format("test_call_naked_alternative") - result = _lenient_service(myservice)() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) - - def test_call_naked_service_args_kwargs(self): - @_lenient_service - def myservice(*args, **kwargs): - return args, kwargs - - args_out, kwargs_out = myservice(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) - - def test_call_naked_doc(self): - @_lenient_service - def myservice(): - """myservice doc-string""" - - self.assertEqual(myservice.__doc__, "myservice doc-string") - - def test_call(self): - @_lenient_service() - def myservice(): - return _LENIENT.__dict__.copy() - - qualname_service = self.service.format("test_call") - state = _LENIENT.__dict__ - self.assertIn(qualname_service, state) - self.assertTrue(state[qualname_service]) - result = myservice() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) - - def test_call_alternative(self): - def myservice(): - return _LENIENT.__dict__.copy() - - qualname_service = self.service.format("test_call_alternative") - result = (_lenient_service())(myservice)() - self.assertIn(qualname_service, result) - self.assertTrue(result[qualname_service]) - - def test_call_service_args_kwargs(self): - @_lenient_service() - def myservice(*args, **kwargs): - return args, kwargs - - args_out, kwargs_out = myservice(*self.args_in, **self.kwargs_in) - self.assertEqual(args_out, self.args_in) - self.assertEqual(kwargs_out, self.kwargs_in) - - def test_call_doc(self): - @_lenient_service() - def myservice(): - """myservice doc-string""" - - self.assertEqual(myservice.__doc__, "myservice doc-string") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/lenient/test__qualname.py b/lib/iris/tests/unit/common/lenient/test__qualname.py deleted file mode 100644 index 3deefbf30d..0000000000 --- a/lib/iris/tests/unit/common/lenient/test__qualname.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.common.lenient._qualname`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from inspect import getmodule -from unittest.mock import sentinel - -from iris.common.lenient import _qualname - - -class Test(tests.IrisTest): - def setUp(self): - module_name = getmodule(self).__name__ - self.locals = f"{module_name}" + ".Test.{}..{}" - - def test_pass_thru_non_callable(self): - func = sentinel.func - result = _qualname(func) - self.assertEqual(result, func) - - def test_callable_function_local(self): - def myfunc(): - pass - - qualname_func = self.locals.format( - "test_callable_function_local", "myfunc" - ) - result = _qualname(myfunc) - self.assertEqual(result, qualname_func) - - def test_callable_function(self): - import iris - - result = _qualname(iris.load) - self.assertEqual(result, "iris.load") - - def test_callable_method_local(self): - class MyClass: - def mymethod(self): - pass - - qualname_method = self.locals.format( - "test_callable_method_local", "MyClass.mymethod" - ) - result = _qualname(MyClass.mymethod) - self.assertEqual(result, qualname_method) - - def test_callable_method(self): - import iris - - result = _qualname(iris.cube.Cube.add_ancillary_variable) - self.assertEqual(result, "iris.cube.Cube.add_ancillary_variable") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/__init__.py b/lib/iris/tests/unit/common/metadata/__init__.py deleted file mode 100644 index aba33c8312..0000000000 --- a/lib/iris/tests/unit/common/metadata/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.common.metadata` package.""" diff --git a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py b/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py deleted file mode 100644 index 9efb43ec42..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_AncillaryVariableMetadata.py +++ /dev/null @@ -1,492 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.metadata.AncillaryVariableMetadata`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import AncillaryVariableMetadata, BaseMetadata - - -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.cls = AncillaryVariableMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - ) - fmt = ( - "AncillaryVariableMetadata(standard_name={!r}, long_name={!r}, " - "var_name={!r}, units={!r}, attributes={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - ) - self.assertEqual(expected, repr(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - ) - self.assertEqual(self.cls._fields, expected) - - def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) - - -class Test___eq__(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - ) - self.dummy = sentinel.dummy - self.cls = AncillaryVariableMetadata - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value - metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = AncillaryVariableMetadata - self.one = self.cls(1, 1, 1, 1, 1) - self.two = self.cls(1, 1, 1, 2, 1) - self.none = self.cls(1, 1, 1, None, 1) - self.attributes = self.cls(1, 1, 1, 1, 10) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes(self): - result = self.one < self.attributes - self.assertFalse(result) - result = self.attributes < self.one - self.assertFalse(result) - - -class Test_combine(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - ) - self.dummy = sentinel.dummy - self.cls = AncillaryVariableMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.combine.__doc__, self.cls.combine.__doc__ - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["units"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - -class Test_difference(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - ) - self.dummy = sentinel.dummy - self.cls = AncillaryVariableMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.difference.__doc__, self.cls.difference.__doc__ - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["units"] = (left["units"], right["units"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["units"] = lexpected["units"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_strict_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - -class Test_equal(tests.IrisTest): - def setUp(self): - self.cls = AncillaryVariableMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py b/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py deleted file mode 100644 index f4760b3051..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_BaseMetadata.py +++ /dev/null @@ -1,1653 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.metadata.BaseMetadata`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from collections import OrderedDict -import unittest.mock as mock -from unittest.mock import sentinel - -import numpy as np -import numpy.ma as ma - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata, CubeMetadata - - -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.cls = BaseMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - ) - fmt = ( - "BaseMetadata(standard_name={!r}, long_name={!r}, " - "var_name={!r}, units={!r}, attributes={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - ) - self.assertEqual(expected, repr(metadata)) - - def test_str(self): - metadata = self.cls( - standard_name="", - long_name=None, - var_name=self.var_name, - units=self.units, - attributes={}, - ) - expected = ( - f"BaseMetadata(var_name={self.var_name!r}, units={self.units!r})" - ) - self.assertEqual(expected, str(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - ) - self.assertEqual(expected, self.cls._fields) - - -class Test___eq__(tests.IrisTest): - def setUp(self): - self.kwargs = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - ) - self.cls = BaseMetadata - self.metadata = self.cls(**self.kwargs) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_cannot_compare_non_class(self): - result = self.metadata.__eq__(None) - self.assertIs(NotImplemented, result) - - def test_cannot_compare_different_class(self): - other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) - result = self.metadata.__eq__(other) - self.assertIs(NotImplemented, result) - - def test_lenient(self): - return_value = sentinel.return_value - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ) as mlenient: - with mock.patch.object( - self.cls, "_compare_lenient", return_value=return_value - ) as mcompare: - result = self.metadata.__eq__(self.metadata) - - self.assertEqual(return_value, result) - self.assertEqual(1, mcompare.call_count) - (arg,), kwargs = mcompare.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - self.assertEqual(1, mlenient.call_count) - (arg,), kwargs = mlenient.call_args - self.assertEqual(_qualname(self.cls.__eq__), _qualname(arg)) - self.assertEqual(dict(), kwargs) - - def test_strict_same(self): - self.assertTrue(self.metadata.__eq__(self.metadata)) - other = self.cls(**self.kwargs) - self.assertTrue(self.metadata.__eq__(other)) - self.assertTrue(other.__eq__(self.metadata)) - - def test_strict_different(self): - self.kwargs["var_name"] = None - other = self.cls(**self.kwargs) - self.assertFalse(self.metadata.__eq__(other)) - self.assertFalse(other.__eq__(self.metadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - self.one = self.cls(1, 1, 1, 1, 1) - self.two = self.cls(1, 1, 1, 2, 1) - self.none = self.cls(1, 1, 1, None, 1) - self.attributes = self.cls(1, 1, 1, 1, 10) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes(self): - result = self.one < self.attributes - self.assertFalse(result) - result = self.attributes < self.one - self.assertFalse(result) - - -class Test___ne__(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.other = sentinel.other - - def test_notimplemented(self): - return_value = NotImplemented - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as mocker: - result = self.metadata.__ne__(self.other) - - self.assertIs(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(self.other, arg) - self.assertEqual(dict(), kwargs) - - def test_negate_true(self): - return_value = True - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as mocker: - result = self.metadata.__ne__(self.other) - - self.assertFalse(result) - (arg,), kwargs = mocker.call_args - self.assertEqual(self.other, arg) - self.assertEqual(dict(), kwargs) - - def test_negate_false(self): - return_value = False - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as mocker: - result = self.metadata.__ne__(self.other) - - self.assertTrue(result) - (arg,), kwargs = mocker.call_args - self.assertEqual(self.other, arg) - self.assertEqual(dict(), kwargs) - - -class Test__combine(tests.IrisTest): - def setUp(self): - self.kwargs = dict( - standard_name="standard_name", - long_name="long_name", - var_name="var_name", - units="units", - attributes=dict(one=sentinel.one, two=sentinel.two), - ) - self.cls = BaseMetadata - self.metadata = self.cls(**self.kwargs) - - def test_lenient(self): - return_value = sentinel._combine_lenient - other = sentinel.other - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ) as mlenient: - with mock.patch.object( - self.cls, "_combine_lenient", return_value=return_value - ) as mcombine: - result = self.metadata._combine(other) - - self.assertEqual(1, mlenient.call_count) - (arg,), kwargs = mlenient.call_args - self.assertEqual(self.metadata.combine, arg) - self.assertEqual(dict(), kwargs) - - self.assertEqual(return_value, result) - self.assertEqual(1, mcombine.call_count) - (arg,), kwargs = mcombine.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_strict(self): - dummy = sentinel.dummy - values = self.kwargs.copy() - values["standard_name"] = dummy - values["var_name"] = dummy - values["attributes"] = dummy - other = self.cls(**values) - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - result = self.metadata._combine(other) - - expected = [ - None if values[field] == dummy else values[field] - for field in self.cls._fields - ] - self.assertEqual(expected, result) - - -class Test__combine_lenient(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() - self.names = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - ) - - def test_strict_units(self): - left = self.none.copy() - left["units"] = "K" - right = left.copy() - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - expected = list(left.values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) - - def test_strict_units_different(self): - left = self.none.copy() - right = self.none.copy() - left["units"] = "K" - right["units"] = "km" - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._combine_lenient(rmetadata) - expected = list(self.none.values()) - self.assertEqual(expected, result) - result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) - - def test_strict_units_different_none(self): - left = self.none.copy() - right = self.none.copy() - left["units"] = "K" - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._combine_lenient(rmetadata) - expected = list(self.none.values()) - self.assertEqual(expected, result) - - result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) - - def test_attributes(self): - left = self.none.copy() - right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = dict(item=sentinel.right) - left["attributes"] = ldict - right["attributes"] = rdict - rmetadata = self.cls(**right) - return_value = sentinel.return_value - with mock.patch.object( - self.cls, - "_combine_lenient_attributes", - return_value=return_value, - ) as mocker: - lmetadata = self.cls(**left) - result = lmetadata._combine_lenient(rmetadata) - - expected = self.none.copy() - expected["attributes"] = return_value - expected = list(expected.values()) - self.assertEqual(expected, result) - - self.assertEqual(1, mocker.call_count) - args, kwargs = mocker.call_args - expected = (ldict, rdict) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) - - def test_attributes_non_mapping_different(self): - left = self.none.copy() - right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = sentinel.right - left["attributes"] = ldict - right["attributes"] = rdict - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - expected = list(self.none.copy().values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) - - def test_attributes_non_mapping_different_none(self): - left = self.none.copy() - right = self.none.copy() - ldict = dict(item=sentinel.left) - left["attributes"] = ldict - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._combine_lenient(rmetadata) - expected = self.none.copy() - expected["attributes"] = ldict - expected = list(expected.values()) - self.assertEqual(expected, result) - - result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) - - def test_names(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - expected = list(left.values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) - - def test_names_different(self): - dummy = sentinel.dummy - left = self.none.copy() - right = self.none.copy() - left.update(self.names) - right["standard_name"] = dummy - right["long_name"] = dummy - right["var_name"] = dummy - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - expected = list(self.none.copy().values()) - self.assertEqual(expected, lmetadata._combine_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._combine_lenient(lmetadata)) - - def test_names_different_none(self): - left = self.none.copy() - right = self.none.copy() - left.update(self.names) - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._combine_lenient(rmetadata) - expected = list(left.values()) - self.assertEqual(expected, result) - - result = rmetadata._combine_lenient(lmetadata) - self.assertEqual(expected, result) - - -class Test__combine_lenient_attributes(tests.IrisTest): - def setUp(self): - self.values = OrderedDict( - one="one", - two="two", - three=np.int16(123), - four=np.arange(10), - five=ma.arange(10), - ) - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy - - def test_same(self): - left = self.values.copy() - right = self.values.copy() - - result = self.metadata._combine_lenient_attributes(left, right) - expected = left - self.assertDictEqual(expected, result) - - result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) - - def test_different(self): - left = self.values.copy() - right = self.values.copy() - left["two"] = left["four"] = self.dummy - - result = self.metadata._combine_lenient_attributes(left, right) - expected = self.values.copy() - for key in ["two", "four"]: - del expected[key] - self.assertDictEqual(expected, result) - - result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) - - def test_different_none(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = left["five"] = None - - result = self.metadata._combine_lenient_attributes(left, right) - expected = self.values.copy() - for key in ["one", "three", "five"]: - del expected[key] - self.assertDictEqual(expected, result) - - result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) - - def test_extra(self): - left = self.values.copy() - right = self.values.copy() - left["extra_left"] = "extra_left" - right["extra_right"] = "extra_right" - - result = self.metadata._combine_lenient_attributes(left, right) - expected = self.values.copy() - expected["extra_left"] = left["extra_left"] - expected["extra_right"] = right["extra_right"] - self.assertDictEqual(expected, result) - - result = self.metadata._combine_lenient_attributes(right, left) - self.assertDictEqual(expected, result) - - -class Test__combine_strict_attributes(tests.IrisTest): - def setUp(self): - self.values = OrderedDict( - one="one", - two="two", - three=np.int32(123), - four=np.arange(10), - five=ma.arange(10), - ) - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy - - def test_same(self): - left = self.values.copy() - right = self.values.copy() - - result = self.metadata._combine_strict_attributes(left, right) - expected = left - self.assertDictEqual(expected, result) - - result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) - - def test_different(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = self.dummy - - result = self.metadata._combine_strict_attributes(left, right) - expected = self.values.copy() - for key in ["one", "three"]: - del expected[key] - self.assertDictEqual(expected, result) - - result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) - - def test_different_none(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = left["five"] = None - - result = self.metadata._combine_strict_attributes(left, right) - expected = self.values.copy() - for key in ["one", "three", "five"]: - del expected[key] - self.assertDictEqual(expected, result) - - result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) - - def test_extra(self): - left = self.values.copy() - right = self.values.copy() - left["extra_left"] = "extra_left" - right["extra_right"] = "extra_right" - - result = self.metadata._combine_strict_attributes(left, right) - expected = self.values.copy() - self.assertDictEqual(expected, result) - - result = self.metadata._combine_strict_attributes(right, left) - self.assertDictEqual(expected, result) - - -class Test__compare_lenient(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() - self.names = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - ) - - def test_name_same(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) - - # mocker not called for "units" nor "var_name" members. - expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) - - def test_name_same_lenient_false__long_name_different(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - right["long_name"] = sentinel.dummy - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) - - # mocker not called for "units" nor "var_name" members. - expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) - - def test_name_same_lenient_true__var_name_different(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - right["var_name"] = sentinel.dummy - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) - - # mocker not called for "units" nor "var_name" members. - expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) - - def test_name_different(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - right["standard_name"] = None - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - with mock.patch.object(self.cls, "_is_attributes") as mocker: - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) - - self.assertEqual(0, mocker.call_count) - - def test_strict_units(self): - left = self.none.copy() - left.update(self.names) - left["units"] = "K" - right = left.copy() - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) - - # mocker not called for "units" nor "var_name" members. - expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) - - def test_strict_units_different(self): - left = self.none.copy() - left.update(self.names) - left["units"] = "K" - right = left.copy() - right["units"] = "m" - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - with mock.patch.object( - self.cls, "_is_attributes", return_value=False - ) as mocker: - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) - - # mocker not called for "units" nor "var_name" members. - expected = (len(self.cls._fields) - 2) * 2 - self.assertEqual(expected, mocker.call_count) - - def test_attributes(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - ldict = dict(item=sentinel.left) - rdict = dict(item=sentinel.right) - left["attributes"] = ldict - right["attributes"] = rdict - rmetadata = self.cls(**right) - with mock.patch.object( - self.cls, - "_compare_lenient_attributes", - return_value=True, - ) as mocker: - lmetadata = self.cls(**left) - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) - - self.assertEqual(2, mocker.call_count) - expected = [((ldict, rdict),), ((rdict, ldict),)] - self.assertEqual(expected, mocker.call_args_list) - - def test_attributes_non_mapping_different(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - ldict = dict(item=sentinel.left) - rdict = sentinel.right - left["attributes"] = ldict - right["attributes"] = rdict - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - self.assertFalse(lmetadata._compare_lenient(rmetadata)) - self.assertFalse(rmetadata._compare_lenient(lmetadata)) - - def test_attributes_non_mapping_different_none(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - ldict = dict(item=sentinel.left) - left["attributes"] = ldict - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._compare_lenient(lmetadata)) - - def test_names(self): - left = self.none.copy() - left.update(self.names) - left["long_name"] = None - right = self.none.copy() - right["long_name"] = left["standard_name"] - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - self.assertTrue(lmetadata._compare_lenient(rmetadata)) - self.assertTrue(rmetadata._combine_lenient(lmetadata)) - - -class Test__compare_lenient_attributes(tests.IrisTest): - def setUp(self): - self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, - three=np.int16(123), - four=np.arange(10), - five=ma.arange(5), - ) - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy - - def test_same(self): - left = self.values.copy() - right = self.values.copy() - - self.assertTrue(self.metadata._compare_lenient_attributes(left, right)) - self.assertTrue(self.metadata._compare_lenient_attributes(right, left)) - - def test_different(self): - left = self.values.copy() - right = self.values.copy() - left["two"] = left["four"] = self.dummy - - self.assertFalse( - self.metadata._compare_lenient_attributes(left, right) - ) - self.assertFalse( - self.metadata._compare_lenient_attributes(right, left) - ) - - def test_different_none(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = left["five"] = None - - self.assertFalse( - self.metadata._compare_lenient_attributes(left, right) - ) - self.assertFalse( - self.metadata._compare_lenient_attributes(right, left) - ) - - def test_extra(self): - left = self.values.copy() - right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right - - self.assertTrue(self.metadata._compare_lenient_attributes(left, right)) - self.assertTrue(self.metadata._compare_lenient_attributes(right, left)) - - -class Test__compare_strict_attributes(tests.IrisTest): - def setUp(self): - self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, - three=np.int16(123), - four=np.arange(10), - five=ma.arange(5), - ) - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy - - def test_same(self): - left = self.values.copy() - right = self.values.copy() - - self.assertTrue(self.metadata._compare_strict_attributes(left, right)) - self.assertTrue(self.metadata._compare_strict_attributes(right, left)) - - def test_different(self): - left = self.values.copy() - right = self.values.copy() - left["two"] = left["four"] = self.dummy - - self.assertFalse(self.metadata._compare_strict_attributes(left, right)) - self.assertFalse(self.metadata._compare_strict_attributes(right, left)) - - def test_different_none(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = left["five"] = None - - self.assertFalse(self.metadata._compare_strict_attributes(left, right)) - self.assertFalse(self.metadata._compare_strict_attributes(right, left)) - - def test_extra(self): - left = self.values.copy() - right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right - - self.assertFalse(self.metadata._compare_strict_attributes(left, right)) - self.assertFalse(self.metadata._compare_strict_attributes(right, left)) - - -class Test__difference(tests.IrisTest): - def setUp(self): - self.kwargs = dict( - standard_name="standard_name", - long_name="long_name", - var_name="var_name", - units="units", - attributes=dict(one=sentinel.one, two=sentinel.two), - ) - self.cls = BaseMetadata - self.metadata = self.cls(**self.kwargs) - - def test_lenient(self): - return_value = sentinel._difference_lenient - other = sentinel.other - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ) as mlenient: - with mock.patch.object( - self.cls, "_difference_lenient", return_value=return_value - ) as mdifference: - result = self.metadata._difference(other) - - self.assertEqual(1, mlenient.call_count) - (arg,), kwargs = mlenient.call_args - self.assertEqual(self.metadata.difference, arg) - self.assertEqual(dict(), kwargs) - - self.assertEqual(return_value, result) - self.assertEqual(1, mdifference.call_count) - (arg,), kwargs = mdifference.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_strict(self): - dummy = sentinel.dummy - values = self.kwargs.copy() - values["long_name"] = dummy - values["units"] = dummy - other = self.cls(**values) - method = "_difference_strict_attributes" - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - with mock.patch.object( - self.cls, method, return_value=None - ) as mdifference: - result = self.metadata._difference(other) - - expected = [ - (self.kwargs[field], dummy) if values[field] == dummy else None - for field in self.cls._fields - ] - self.assertEqual(expected, result) - self.assertEqual(1, mdifference.call_count) - args, kwargs = mdifference.call_args - expected = (self.kwargs["attributes"], values["attributes"]) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) - - with mock.patch.object( - self.cls, method, return_value=None - ) as mdifference: - result = other._difference(self.metadata) - - expected = [ - (dummy, self.kwargs[field]) if values[field] == dummy else None - for field in self.cls._fields - ] - self.assertEqual(expected, result) - self.assertEqual(1, mdifference.call_count) - args, kwargs = mdifference.call_args - expected = (self.kwargs["attributes"], values["attributes"]) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) - - -class Test__difference_lenient(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - self.none = self.cls(*(None,) * len(self.cls._fields))._asdict() - self.names = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - ) - - def test_strict_units(self): - left = self.none.copy() - left["units"] = "km" - right = left.copy() - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - expected = list(self.none.values()) - self.assertEqual(expected, lmetadata._difference_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._difference_lenient(lmetadata)) - - def test_strict_units_different(self): - left = self.none.copy() - right = self.none.copy() - lunits, runits = "m", "km" - left["units"] = lunits - right["units"] = runits - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._difference_lenient(rmetadata) - expected = self.none.copy() - expected["units"] = (lunits, runits) - expected = list(expected.values()) - self.assertEqual(expected, result) - - result = rmetadata._difference_lenient(lmetadata) - expected = self.none.copy() - expected["units"] = (runits, lunits) - expected = list(expected.values()) - self.assertEqual(expected, result) - - def test_strict_units_different_none(self): - left = self.none.copy() - right = self.none.copy() - lunits, runits = "m", None - left["units"] = lunits - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._difference_lenient(rmetadata) - expected = self.none.copy() - expected["units"] = (lunits, runits) - expected = list(expected.values()) - - self.assertEqual(expected, result) - result = rmetadata._difference_lenient(lmetadata) - expected = self.none.copy() - expected["units"] = (runits, lunits) - expected = list(expected.values()) - self.assertEqual(expected, result) - - def test_attributes(self): - left = self.none.copy() - right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = dict(item=sentinel.right) - left["attributes"] = ldict - right["attributes"] = rdict - rmetadata = self.cls(**right) - return_value = sentinel.return_value - with mock.patch.object( - self.cls, - "_difference_lenient_attributes", - return_value=return_value, - ) as mocker: - lmetadata = self.cls(**left) - result = lmetadata._difference_lenient(rmetadata) - - expected = self.none.copy() - expected["attributes"] = return_value - expected = list(expected.values()) - self.assertEqual(expected, result) - - self.assertEqual(1, mocker.call_count) - args, kwargs = mocker.call_args - expected = (ldict, rdict) - self.assertEqual(expected, args) - self.assertEqual(dict(), kwargs) - - def test_attributes_non_mapping_different(self): - left = self.none.copy() - right = self.none.copy() - ldict = dict(item=sentinel.left) - rdict = sentinel.right - left["attributes"] = ldict - right["attributes"] = rdict - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._difference_lenient(rmetadata) - expected = self.none.copy() - expected["attributes"] = (ldict, rdict) - expected = list(expected.values()) - self.assertEqual(expected, result) - - result = rmetadata._difference_lenient(lmetadata) - expected = self.none.copy() - expected["attributes"] = (rdict, ldict) - expected = list(expected.values()) - self.assertEqual(expected, result) - - def test_attributes_non_mapping_different_none(self): - left = self.none.copy() - right = self.none.copy() - ldict = dict(item=sentinel.left) - left["attributes"] = ldict - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._difference_lenient(rmetadata) - expected = list(self.none.copy().values()) - self.assertEqual(expected, result) - - result = rmetadata._difference_lenient(lmetadata) - self.assertEqual(expected, result) - - def test_names(self): - left = self.none.copy() - left.update(self.names) - right = left.copy() - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - expected = list(self.none.values()) - self.assertEqual(expected, lmetadata._difference_lenient(rmetadata)) - self.assertEqual(expected, rmetadata._difference_lenient(lmetadata)) - - def test_names_different(self): - dummy = sentinel.dummy - left = self.none.copy() - right = self.none.copy() - left.update(self.names) - right["standard_name"] = dummy - right["long_name"] = dummy - right["var_name"] = dummy - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._difference_lenient(rmetadata) - expected = self.none.copy() - expected["standard_name"] = ( - left["standard_name"], - right["standard_name"], - ) - expected["long_name"] = (left["long_name"], right["long_name"]) - expected["var_name"] = (left["var_name"], right["var_name"]) - expected = list(expected.values()) - self.assertEqual(expected, result) - - result = rmetadata._difference_lenient(lmetadata) - expected = self.none.copy() - expected["standard_name"] = ( - right["standard_name"], - left["standard_name"], - ) - expected["long_name"] = (right["long_name"], left["long_name"]) - expected["var_name"] = (right["var_name"], left["var_name"]) - expected = list(expected.values()) - self.assertEqual(expected, result) - - def test_names_different_none(self): - left = self.none.copy() - right = self.none.copy() - left.update(self.names) - lmetadata = self.cls(**left) - rmetadata = self.cls(**right) - - result = lmetadata._difference_lenient(rmetadata) - expected = list(self.none.values()) - self.assertEqual(expected, result) - - result = rmetadata._difference_lenient(lmetadata) - self.assertEqual(expected, result) - - -class Test__difference_lenient_attributes(tests.IrisTest): - def setUp(self): - self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, - three=np.float64(3.14), - four=np.arange(10, dtype=np.float64), - five=ma.arange(10, dtype=np.int16), - ) - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy - - def test_same(self): - left = self.values.copy() - right = self.values.copy() - - result = self.metadata._difference_lenient_attributes(left, right) - self.assertIsNone(result) - - result = self.metadata._difference_lenient_attributes(right, left) - self.assertIsNone(result) - - def test_different(self): - left = self.values.copy() - right = self.values.copy() - left["two"] = left["four"] = self.dummy - - result = self.metadata._difference_lenient_attributes(left, right) - for key in ["one", "three", "five"]: - del left[key] - del right[key] - expected_left, expected_right = (left, right) - result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) - - result = self.metadata._difference_lenient_attributes(right, left) - result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) - - def test_different_none(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = left["five"] = None - - result = self.metadata._difference_lenient_attributes(left, right) - for key in ["two", "four"]: - del left[key] - del right[key] - expected_left, expected_right = (left, right) - result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) - - result = self.metadata._difference_lenient_attributes(right, left) - result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) - - def test_extra(self): - left = self.values.copy() - right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right - result = self.metadata._difference_lenient_attributes(left, right) - self.assertIsNone(result) - - result = self.metadata._difference_lenient_attributes(right, left) - self.assertIsNone(result) - - -class Test__difference_strict_attributes(tests.IrisTest): - def setUp(self): - self.values = OrderedDict( - one=sentinel.one, - two=sentinel.two, - three=np.int32(123), - four=np.arange(10), - five=ma.arange(10), - ) - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.dummy = sentinel.dummy - - def test_same(self): - left = self.values.copy() - right = self.values.copy() - - result = self.metadata._difference_strict_attributes(left, right) - self.assertIsNone(result) - result = self.metadata._difference_strict_attributes(right, left) - self.assertIsNone(result) - - def test_different(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = left["five"] = self.dummy - - result = self.metadata._difference_strict_attributes(left, right) - expected_left = left.copy() - expected_right = right.copy() - for key in ["two", "four"]: - del expected_left[key] - del expected_right[key] - result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) - - result = self.metadata._difference_strict_attributes(right, left) - result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) - - def test_different_none(self): - left = self.values.copy() - right = self.values.copy() - left["one"] = left["three"] = left["five"] = None - - result = self.metadata._difference_strict_attributes(left, right) - expected_left = left.copy() - expected_right = right.copy() - for key in ["two", "four"]: - del expected_left[key] - del expected_right[key] - result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) - - result = self.metadata._difference_strict_attributes(right, left) - result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) - - def test_extra(self): - left = self.values.copy() - right = self.values.copy() - left["extra_left"] = sentinel.extra_left - right["extra_right"] = sentinel.extra_right - - result = self.metadata._difference_strict_attributes(left, right) - expected_left = dict(extra_left=left["extra_left"]) - expected_right = dict(extra_right=right["extra_right"]) - result_left, result_right = result - self.assertDictEqual(expected_left, result_left) - self.assertDictEqual(expected_right, result_right) - - result = self.metadata._difference_strict_attributes(right, left) - result_left, result_right = result - self.assertDictEqual(expected_right, result_left) - self.assertDictEqual(expected_left, result_right) - - -class Test__is_attributes(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - self.metadata = self.cls(*(None,) * len(self.cls._fields)) - self.field = "attributes" - - def test_field(self): - self.assertTrue(self.metadata._is_attributes(self.field, {}, {})) - - def test_field_not_attributes(self): - self.assertFalse(self.metadata._is_attributes(None, {}, {})) - - def test_left_not_mapping(self): - self.assertFalse(self.metadata._is_attributes(self.field, None, {})) - - def test_right_not_mapping(self): - self.assertFalse(self.metadata._is_attributes(self.field, {}, None)) - - -class Test_combine(tests.IrisTest): - def setUp(self): - kwargs = dict( - standard_name="standard_name", - long_name="long_name", - var_name="var_name", - units="units", - attributes="attributes", - ) - self.cls = BaseMetadata - self.metadata = self.cls(**kwargs) - self.mock_kwargs = OrderedDict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_cannot_combine_non_class(self): - emsg = "Cannot combine" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.combine(None) - - def test_cannot_combine_different_class(self): - other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) - emsg = "Cannot combine" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.combine(other) - - def test_lenient_default(self): - return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_combine", return_value=return_value - ) as mocker: - result = self.metadata.combine(self.metadata) - - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_true(self): - return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_combine", return_value=return_value - ) as mcombine: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.combine(self.metadata, lenient=True) - - self.assertEqual(1, mcontext.call_count) - (arg,), kwargs = mcontext.call_args - self.assertEqual(_qualname(self.cls.combine), arg) - self.assertEqual(dict(), kwargs) - - self.assertEqual(result._asdict(), self.mock_kwargs) - self.assertEqual(1, mcombine.call_count) - (arg,), kwargs = mcombine.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_false(self): - return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_combine", return_value=return_value - ) as mcombine: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.combine(self.metadata, lenient=False) - - self.assertEqual(1, mcontext.call_count) - args, kwargs = mcontext.call_args - self.assertEqual((), args) - self.assertEqual({_qualname(self.cls.combine): False}, kwargs) - - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mcombine.call_count) - (arg,), kwargs = mcombine.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - -class Test_difference(tests.IrisTest): - def setUp(self): - kwargs = dict( - standard_name="standard_name", - long_name="long_name", - var_name="var_name", - units="units", - attributes="attributes", - ) - self.cls = BaseMetadata - self.metadata = self.cls(**kwargs) - self.mock_kwargs = OrderedDict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_cannot_differ_non_class(self): - emsg = "Cannot differ" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.difference(None) - - def test_cannot_differ_different_class(self): - other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) - emsg = "Cannot differ" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.difference(other) - - def test_lenient_default(self): - return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_difference", return_value=return_value - ) as mocker: - result = self.metadata.difference(self.metadata) - - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_true(self): - return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_difference", return_value=return_value - ) as mdifference: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.difference(self.metadata, lenient=True) - - self.assertEqual(1, mcontext.call_count) - (arg,), kwargs = mcontext.call_args - self.assertEqual(_qualname(self.cls.difference), arg) - self.assertEqual(dict(), kwargs) - - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mdifference.call_count) - (arg,), kwargs = mdifference.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_false(self): - return_value = self.mock_kwargs.values() - with mock.patch.object( - self.cls, "_difference", return_value=return_value - ) as mdifference: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.difference(self.metadata, lenient=False) - - self.assertEqual(mcontext.call_count, 1) - args, kwargs = mcontext.call_args - self.assertEqual((), args) - self.assertEqual({_qualname(self.cls.difference): False}, kwargs) - - self.assertEqual(self.mock_kwargs, result._asdict()) - self.assertEqual(1, mdifference.call_count) - (arg,), kwargs = mdifference.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - -class Test_equal(tests.IrisTest): - def setUp(self): - kwargs = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - ) - self.cls = BaseMetadata - self.metadata = self.cls(**kwargs) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue((_LENIENT[self.cls.equal])) - - def test_cannot_compare_non_class(self): - emsg = "Cannot compare" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.equal(None) - - def test_cannot_compare_different_class(self): - other = CubeMetadata(*(None,) * len(CubeMetadata._fields)) - emsg = "Cannot compare" - with self.assertRaisesRegex(TypeError, emsg): - self.metadata.equal(other) - - def test_lenient_default(self): - return_value = sentinel.return_value - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as mocker: - result = self.metadata.equal(self.metadata) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_true(self): - return_value = sentinel.return_value - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as m__eq__: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.equal(self.metadata, lenient=True) - - self.assertEqual(return_value, result) - self.assertEqual(1, mcontext.call_count) - (arg,), kwargs = mcontext.call_args - self.assertEqual(_qualname(self.cls.equal), arg) - self.assertEqual(dict(), kwargs) - - self.assertEqual(1, m__eq__.call_count) - (arg,), kwargs = m__eq__.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - def test_lenient_false(self): - return_value = sentinel.return_value - with mock.patch.object( - self.cls, "__eq__", return_value=return_value - ) as m__eq__: - with mock.patch.object(_LENIENT, "context") as mcontext: - result = self.metadata.equal(self.metadata, lenient=False) - - self.assertEqual(1, mcontext.call_count) - args, kwargs = mcontext.call_args - self.assertEqual((), args) - self.assertEqual({_qualname(self.cls.equal): False}, kwargs) - - self.assertEqual(return_value, result) - self.assertEqual(1, m__eq__.call_count) - (arg,), kwargs = m__eq__.call_args - self.assertEqual(id(self.metadata), id(arg)) - self.assertEqual(dict(), kwargs) - - -class Test_name(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - self.default = self.cls.DEFAULT_NAME - - @staticmethod - def _make(standard_name=None, long_name=None, var_name=None): - return BaseMetadata( - standard_name=standard_name, - long_name=long_name, - var_name=var_name, - units=None, - attributes=None, - ) - - def test_standard_name(self): - token = "standard_name" - metadata = self._make(standard_name=token) - - result = metadata.name() - self.assertEqual(token, result) - result = metadata.name(token=True) - self.assertEqual(token, result) - - def test_standard_name__invalid_token(self): - token = "nope nope" - metadata = self._make(standard_name=token) - - result = metadata.name() - self.assertEqual(token, result) - result = metadata.name(token=True) - self.assertEqual(self.default, result) - - def test_long_name(self): - token = "long_name" - metadata = self._make(long_name=token) - - result = metadata.name() - self.assertEqual(token, result) - result = metadata.name(token=True) - self.assertEqual(token, result) - - def test_long_name__invalid_token(self): - token = "nope nope" - metadata = self._make(long_name=token) - - result = metadata.name() - self.assertEqual(token, result) - result = metadata.name(token=True) - self.assertEqual(self.default, result) - - def test_var_name(self): - token = "var_name" - metadata = self._make(var_name=token) - - result = metadata.name() - self.assertEqual(token, result) - result = metadata.name(token=True) - self.assertEqual(token, result) - - def test_var_name__invalid_token(self): - token = "nope nope" - metadata = self._make(var_name=token) - - result = metadata.name() - self.assertEqual(token, result) - result = metadata.name(token=True) - self.assertEqual(self.default, result) - - def test_default(self): - metadata = self._make() - - result = metadata.name() - self.assertEqual(self.default, result) - result = metadata.name(token=True) - self.assertEqual(self.default, result) - - def test_default__invalid_token(self): - token = "nope nope" - metadata = self._make() - - result = metadata.name(default=token) - self.assertEqual(token, result) - - emsg = "Cannot retrieve a valid name token" - with self.assertRaisesRegex(ValueError, emsg): - metadata.name(default=token, token=True) - - -class Test_token(tests.IrisTest): - def setUp(self): - self.cls = BaseMetadata - - def test_passthru_None(self): - result = self.cls.token(None) - self.assertIsNone(result) - - def test_fail_leading_underscore(self): - result = self.cls.token("_nope") - self.assertIsNone(result) - - def test_fail_leading_dot(self): - result = self.cls.token(".nope") - self.assertIsNone(result) - - def test_fail_leading_plus(self): - result = self.cls.token("+nope") - self.assertIsNone(result) - - def test_fail_leading_at(self): - result = self.cls.token("@nope") - self.assertIsNone(result) - - def test_fail_space(self): - result = self.cls.token("nope nope") - self.assertIsNone(result) - - def test_fail_colon(self): - result = self.cls.token("nope:") - self.assertIsNone(result) - - def test_pass_simple(self): - token = "simple" - result = self.cls.token(token) - self.assertEqual(token, result) - - def test_pass_leading_digit(self): - token = "123simple" - result = self.cls.token(token) - self.assertEqual(token, result) - - def test_pass_mixture(self): - token = "S.imple@one+two_3" - result = self.cls.token(token) - self.assertEqual(token, result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py b/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py deleted file mode 100644 index a434651206..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_CellMeasureMetadata.py +++ /dev/null @@ -1,661 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.metadata.CellMeasureMetadata`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata, CellMeasureMetadata - - -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.measure = mock.sentinel.measure - self.cls = CellMeasureMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - measure=self.measure, - ) - fmt = ( - "CellMeasureMetadata(standard_name={!r}, long_name={!r}, " - "var_name={!r}, units={!r}, attributes={!r}, measure={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - self.measure, - ) - self.assertEqual(expected, repr(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - "measure", - ) - self.assertEqual(self.cls._fields, expected) - - def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) - - -class Test___eq__(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - measure=sentinel.measure, - ) - self.dummy = sentinel.dummy - self.cls = CellMeasureMetadata - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value - metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_measure_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_measure(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_measure(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_measure_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = CellMeasureMetadata - self.one = self.cls(1, 1, 1, 1, 1, 1) - self.two = self.cls(1, 1, 1, 2, 1, 1) - self.none = self.cls(1, 1, 1, None, 1, 1) - self.attributes = self.cls(1, 1, 1, 1, 10, 1) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes(self): - result = self.one < self.attributes - self.assertFalse(result) - result = self.attributes < self.one - self.assertFalse(result) - - -class Test_combine(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - measure=sentinel.measure, - ) - self.dummy = sentinel.dummy - self.cls = CellMeasureMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.combine.__doc__, self.cls.combine.__doc__ - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_measure_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = None - rmetadata = self.cls(**right) - expected = right.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["units"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different_measure(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["measure"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_measure(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["measure"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_measure_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["measure"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - -class Test_difference(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - measure=sentinel.measure, - ) - self.dummy = sentinel.dummy - self.cls = CellMeasureMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.difference.__doc__, self.cls.difference.__doc__ - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_measure_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["measure"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["measure"] = (sentinel.measure, None) - rexpected = deepcopy(self.none)._asdict() - rexpected["measure"] = (None, sentinel.measure) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["units"] = (left["units"], right["units"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["units"] = lexpected["units"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different_measure(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["measure"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["measure"] = (left["measure"], right["measure"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["measure"] = lexpected["measure"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_strict_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_measure(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["measure"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["measure"] = (left["measure"], right["measure"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["measure"] = lexpected["measure"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_measure_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["measure"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["measure"] = (left["measure"], right["measure"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["measure"] = lexpected["measure"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - -class Test_equal(tests.IrisTest): - def setUp(self): - self.cls = CellMeasureMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py b/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py deleted file mode 100644 index e3b7486012..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_CoordMetadata.py +++ /dev/null @@ -1,722 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.metadata.CoordMetadata`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata, CoordMetadata - - -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.coord_system = mock.sentinel.coord_system - self.climatological = mock.sentinel.climatological - self.cls = CoordMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - coord_system=self.coord_system, - climatological=self.climatological, - ) - fmt = ( - "CoordMetadata(standard_name={!r}, long_name={!r}, " - "var_name={!r}, units={!r}, attributes={!r}, coord_system={!r}, " - "climatological={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - self.coord_system, - self.climatological, - ) - self.assertEqual(expected, repr(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - "coord_system", - "climatological", - ) - self.assertEqual(self.cls._fields, expected) - - def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) - - -class Test___eq__(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - coord_system=sentinel.coord_system, - climatological=sentinel.climatological, - ) - self.dummy = sentinel.dummy - self.cls = CoordMetadata - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value - metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = CoordMetadata - self.one = self.cls(1, 1, 1, 1, 1, 1, 1) - self.two = self.cls(1, 1, 1, 2, 1, 1, 1) - self.none = self.cls(1, 1, 1, None, 1, 1, 1) - self.attributes_cs = self.cls(1, 1, 1, 1, 10, 10, 1) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes_coord_system(self): - result = self.one < self.attributes_cs - self.assertFalse(result) - result = self.attributes_cs < self.one - self.assertFalse(result) - - -class Test_combine(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - coord_system=sentinel.coord_system, - climatological=sentinel.climatological, - ) - self.dummy = sentinel.dummy - self.cls = CoordMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.combine.__doc__, self.cls.combine.__doc__ - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = right.copy() - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertTrue( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertTrue( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["units"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - -class Test_difference(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - coord_system=sentinel.coord_system, - climatological=sentinel.climatological, - ) - self.dummy = sentinel.dummy - self.cls = CoordMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.difference.__doc__, self.cls.difference.__doc__ - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - member_value = getattr(lmetadata, member) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (member_value, None) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = (None, member_value) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["units"] = (left["units"], right["units"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["units"] = lexpected["units"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_strict_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - -class Test_equal(tests.IrisTest): - def setUp(self): - self.cls = CoordMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py b/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py deleted file mode 100644 index 848431565b..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_CubeMetadata.py +++ /dev/null @@ -1,829 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.metadata.CubeMetadata`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata, CubeMetadata - - -def _make_metadata( - standard_name=None, - long_name=None, - var_name=None, - attributes=None, - force_mapping=True, -): - if force_mapping: - if attributes is None: - attributes = {} - else: - attributes = dict(STASH=attributes) - - return CubeMetadata( - standard_name=standard_name, - long_name=long_name, - var_name=var_name, - units=None, - attributes=attributes, - cell_methods=None, - ) - - -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.cell_methods = mock.sentinel.cell_methods - self.cls = CubeMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - cell_methods=self.cell_methods, - ) - fmt = ( - "CubeMetadata(standard_name={!r}, long_name={!r}, var_name={!r}, " - "units={!r}, attributes={!r}, cell_methods={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - self.cell_methods, - ) - self.assertEqual(expected, repr(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - "cell_methods", - ) - self.assertEqual(self.cls._fields, expected) - - def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) - - -class Test___eq__(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - # Must be a mapping. - attributes=dict(), - cell_methods=sentinel.cell_methods, - ) - self.dummy = sentinel.dummy - self.cls = CubeMetadata - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value - metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_cell_methods_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_cell_methods(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_cell_methods(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_measure_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = CubeMetadata - self.one = self.cls(1, 1, 1, 1, 1, 1) - self.two = self.cls(1, 1, 1, 2, 1, 1) - self.none = self.cls(1, 1, 1, None, 1, 1) - self.attributes_cm = self.cls(1, 1, 1, 1, 10, 10) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes_cell_methods(self): - result = self.one < self.attributes_cm - self.assertFalse(result) - result = self.attributes_cm < self.one - self.assertFalse(result) - - -class Test_combine(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - cell_methods=sentinel.cell_methods, - ) - self.dummy = sentinel.dummy - self.cls = CubeMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.combine.__doc__, self.cls.combine.__doc__ - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_cell_methods_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = None - rmetadata = self.cls(**right) - expected = right.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["units"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different_cell_methods(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["cell_methods"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_cell_methods(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["cell_methods"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_cell_methods_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["cell_methods"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - -class Test_difference(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - cell_methods=sentinel.cell_methods, - ) - self.dummy = sentinel.dummy - self.cls = CubeMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.difference.__doc__, self.cls.difference.__doc__ - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_cell_methods_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["cell_methods"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["cell_methods"] = (sentinel.cell_methods, None) - rexpected = deepcopy(self.none)._asdict() - rexpected["cell_methods"] = (None, sentinel.cell_methods) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["units"] = (left["units"], right["units"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["units"] = lexpected["units"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different_cell_methods(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["cell_methods"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["cell_methods"] = ( - left["cell_methods"], - right["cell_methods"], - ) - rexpected = deepcopy(self.none)._asdict() - rexpected["cell_methods"] = lexpected["cell_methods"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_strict_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_cell_methods(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["cell_methods"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["cell_methods"] = ( - left["cell_methods"], - right["cell_methods"], - ) - rexpected = deepcopy(self.none)._asdict() - rexpected["cell_methods"] = lexpected["cell_methods"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_measure_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["cell_methods"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["cell_methods"] = ( - left["cell_methods"], - right["cell_methods"], - ) - rexpected = deepcopy(self.none)._asdict() - rexpected["cell_methods"] = lexpected["cell_methods"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - -class Test_equal(tests.IrisTest): - def setUp(self): - self.cls = CubeMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -class Test_name(tests.IrisTest): - def setUp(self): - self.default = CubeMetadata.DEFAULT_NAME - - def test_standard_name(self): - token = "standard_name" - metadata = _make_metadata(standard_name=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, token) - - def test_standard_name__invalid_token(self): - token = "nope nope" - metadata = _make_metadata(standard_name=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, self.default) - - def test_long_name(self): - token = "long_name" - metadata = _make_metadata(long_name=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, token) - - def test_long_name__invalid_token(self): - token = "nope nope" - metadata = _make_metadata(long_name=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, self.default) - - def test_var_name(self): - token = "var_name" - metadata = _make_metadata(var_name=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, token) - - def test_var_name__invalid_token(self): - token = "nope nope" - metadata = _make_metadata(var_name=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, self.default) - - def test_attributes(self): - token = "stash" - metadata = _make_metadata(attributes=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, token) - - def test_attributes__invalid_token(self): - token = "nope nope" - metadata = _make_metadata(attributes=token) - result = metadata.name() - self.assertEqual(result, token) - result = metadata.name(token=True) - self.assertEqual(result, self.default) - - def test_attributes__non_mapping(self): - metadata = _make_metadata(force_mapping=False) - self.assertIsNone(metadata.attributes) - emsg = "Invalid 'CubeMetadata.attributes' member, must be a mapping." - with self.assertRaisesRegex(AttributeError, emsg): - _ = metadata.name() - - def test_default(self): - metadata = _make_metadata() - result = metadata.name() - self.assertEqual(result, self.default) - result = metadata.name(token=True) - self.assertEqual(result, self.default) - - def test_default__invalid_token(self): - token = "nope nope" - metadata = _make_metadata() - result = metadata.name(default=token) - self.assertEqual(result, token) - emsg = "Cannot retrieve a valid name token" - with self.assertRaisesRegex(ValueError, emsg): - _ = metadata.name(default=token, token=True) - - -class Test__names(tests.IrisTest): - def test_standard_name(self): - token = "standard_name" - metadata = _make_metadata(standard_name=token) - expected = (token, None, None, None) - result = metadata._names - self.assertEqual(expected, result) - - def test_long_name(self): - token = "long_name" - metadata = _make_metadata(long_name=token) - expected = (None, token, None, None) - result = metadata._names - self.assertEqual(expected, result) - - def test_var_name(self): - token = "var_name" - metadata = _make_metadata(var_name=token) - expected = (None, None, token, None) - result = metadata._names - self.assertEqual(expected, result) - - def test_attributes(self): - token = "stash" - metadata = _make_metadata(attributes=token) - expected = (None, None, None, token) - result = metadata._names - self.assertEqual(expected, result) - - def test_attributes__non_mapping(self): - metadata = _make_metadata(force_mapping=False) - self.assertIsNone(metadata.attributes) - emsg = "Invalid 'CubeMetadata.attributes' member, must be a mapping." - with self.assertRaisesRegex(AttributeError, emsg): - _ = metadata._names - - def test_None(self): - metadata = _make_metadata() - expected = (None, None, None, None) - result = metadata._names - self.assertEqual(expected, result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py b/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py deleted file mode 100644 index 4ffeb7a67a..0000000000 --- a/lib/iris/tests/unit/common/metadata/test__NamedTupleMeta.py +++ /dev/null @@ -1,148 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.metadata._NamedTupleMeta`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from abc import abstractmethod - -from iris.common.metadata import _NamedTupleMeta - - -class Test(tests.IrisTest): - @staticmethod - def names(classes): - return [cls.__name__ for cls in classes] - - @staticmethod - def emsg_generate(members): - if isinstance(members, str): - members = (members,) - emsg = ".* missing {} required positional argument{}: {}" - args = ", ".join([f"{member!r}" for member in members[:-1]]) - count = len(members) - if count == 1: - args += f"{members[-1]!r}" - elif count == 2: - args += f" and {members[-1]!r}" - else: - args += f", and {members[-1]!r}" - plural = "s" if count > 1 else "" - return emsg.format(len(members), plural, args) - - def test__no_bases_with_abstract_members_property(self): - class Metadata(metaclass=_NamedTupleMeta): - @property - @abstractmethod - def _members(self): - pass - - expected = ["object"] - self.assertEqual(self.names(Metadata.__bases__), expected) - expected = ["Metadata", "object"] - self.assertEqual(self.names(Metadata.__mro__), expected) - emsg = ( - "Can't instantiate abstract class .* with abstract " - "method.* _members" - ) - with self.assertRaisesRegex(TypeError, emsg): - _ = Metadata() - - def test__no_bases_single_member(self): - member = "arg_one" - - class Metadata(metaclass=_NamedTupleMeta): - _members = member - - expected = ["MetadataNamedtuple"] - self.assertEqual(self.names(Metadata.__bases__), expected) - expected = ["Metadata", "MetadataNamedtuple", "tuple", "object"] - self.assertEqual(self.names(Metadata.__mro__), expected) - emsg = self.emsg_generate(member) - with self.assertRaisesRegex(TypeError, emsg): - _ = Metadata() - metadata = Metadata(1) - self.assertEqual(metadata._fields, (member,)) - self.assertEqual(metadata.arg_one, 1) - - def test__no_bases_multiple_members(self): - members = ("arg_one", "arg_two") - - class Metadata(metaclass=_NamedTupleMeta): - _members = members - - expected = ["MetadataNamedtuple"] - self.assertEqual(self.names(Metadata.__bases__), expected) - expected = ["Metadata", "MetadataNamedtuple", "tuple", "object"] - self.assertEqual(self.names(Metadata.__mro__), expected) - emsg = self.emsg_generate(members) - with self.assertRaisesRegex(TypeError, emsg): - _ = Metadata() - values = range(len(members)) - metadata = Metadata(*values) - self.assertEqual(metadata._fields, members) - expected = dict(zip(members, values)) - self.assertEqual(metadata._asdict(), expected) - - def test__multiple_bases_multiple_members(self): - members_parent = ("arg_one", "arg_two") - members_child = ("arg_three", "arg_four") - - class MetadataParent(metaclass=_NamedTupleMeta): - _members = members_parent - - class MetadataChild(MetadataParent): - _members = members_child - - # Check the parent class... - expected = ["MetadataParentNamedtuple"] - self.assertEqual(self.names(MetadataParent.__bases__), expected) - expected = [ - "MetadataParent", - "MetadataParentNamedtuple", - "tuple", - "object", - ] - self.assertEqual(self.names(MetadataParent.__mro__), expected) - emsg = self.emsg_generate(members_parent) - with self.assertRaisesRegex(TypeError, emsg): - _ = MetadataParent() - values_parent = range(len(members_parent)) - metadata_parent = MetadataParent(*values_parent) - self.assertEqual(metadata_parent._fields, members_parent) - expected = dict(zip(members_parent, values_parent)) - self.assertEqual(metadata_parent._asdict(), expected) - - # Check the dependant child class... - expected = ["MetadataChildNamedtuple", "MetadataParent"] - self.assertEqual(self.names(MetadataChild.__bases__), expected) - expected = [ - "MetadataChild", - "MetadataChildNamedtuple", - "MetadataParent", - "MetadataParentNamedtuple", - "tuple", - "object", - ] - self.assertEqual(self.names(MetadataChild.__mro__), expected) - emsg = self.emsg_generate((*members_parent, *members_child)) - with self.assertRaisesRegex(TypeError, emsg): - _ = MetadataChild() - fields_child = (*members_parent, *members_child) - values_child = range(len(fields_child)) - metadata_child = MetadataChild(*values_child) - self.assertEqual(metadata_child._fields, fields_child) - expected = dict(zip(fields_child, values_child)) - self.assertEqual(metadata_child._asdict(), expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test_hexdigest.py b/lib/iris/tests/unit/common/metadata/test_hexdigest.py deleted file mode 100644 index 949002af89..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_hexdigest.py +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.common.metadata.hexdigest`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np -import numpy.ma as ma -from xxhash import xxh64, xxh64_hexdigest - -from iris.common.metadata import hexdigest - - -class TestBytesLikeObject(tests.IrisTest): - def setUp(self): - self.hasher = xxh64() - self.hasher.reset() - - @staticmethod - def _ndarray(value): - parts = str((value.shape, xxh64_hexdigest(value))) - return xxh64_hexdigest(parts) - - @staticmethod - def _masked(value): - parts = str( - ( - value.shape, - xxh64_hexdigest(value.data), - xxh64_hexdigest(value.mask), - ) - ) - return xxh64_hexdigest(parts) - - def test_string(self): - value = "hello world" - self.hasher.update(value) - expected = self.hasher.hexdigest() - self.assertEqual(expected, hexdigest(value)) - - def test_numpy_array_int(self): - value = np.arange(10, dtype=np.int_) - expected = self._ndarray(value) - self.assertEqual(expected, hexdigest(value)) - - def test_numpy_array_float(self): - value = np.arange(10, dtype=np.float64) - expected = self._ndarray(value) - self.assertEqual(expected, hexdigest(value)) - - def test_numpy_array_float_not_int(self): - ivalue = np.arange(10, dtype=np.int_) - fvalue = np.arange(10, dtype=np.float64) - expected = self._ndarray(ivalue) - self.assertNotEqual(expected, hexdigest(fvalue)) - - def test_numpy_array_reshape(self): - value = np.arange(10).reshape(2, 5) - expected = self._ndarray(value) - self.assertEqual(expected, hexdigest(value)) - - def test_numpy_array_reshape_not_flat(self): - value = np.arange(10).reshape(2, 5) - expected = self._ndarray(value) - self.assertNotEqual(expected, hexdigest(value.flatten())) - - def test_masked_array_int(self): - value = ma.arange(10, dtype=np.int_) - expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) - - value[0] = ma.masked - self.assertNotEqual(expected, hexdigest(value)) - expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) - - def test_masked_array_float(self): - value = ma.arange(10, dtype=np.float64) - expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) - - value[0] = ma.masked - self.assertNotEqual(expected, hexdigest(value)) - expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) - - def test_masked_array_float_not_int(self): - ivalue = ma.arange(10, dtype=np.int_) - fvalue = ma.arange(10, dtype=np.float64) - expected = self._masked(ivalue) - self.assertNotEqual(expected, hexdigest(fvalue)) - - def test_masked_array_not_array(self): - value = ma.arange(10) - expected = self._masked(value) - self.assertNotEqual(expected, hexdigest(value.data)) - - def test_masked_array_reshape(self): - value = ma.arange(10).reshape(2, 5) - expected = self._masked(value) - self.assertEqual(expected, hexdigest(value)) - - def test_masked_array_reshape_not_flat(self): - value = ma.arange(10).reshape(2, 5) - expected = self._masked(value) - self.assertNotEqual(expected, hexdigest(value.flatten())) - - -class TestNotBytesLikeObject(tests.IrisTest): - def _expected(self, value): - parts = str((type(value), value)) - return xxh64_hexdigest(parts) - - def test_int(self): - value = 123 - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_numpy_int(self): - value = int(123) - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_float(self): - value = 123.4 - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_numpy_float(self): - value = float(123.4) - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_list(self): - value = [1, 2, 3] - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_tuple(self): - value = (1, 2, 3) - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_dict(self): - value = dict(one=1, two=2, three=3) - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_sentinel(self): - value = mock.sentinel.value - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_instance(self): - class Dummy: - pass - - value = Dummy() - expected = self._expected(value) - self.assertEqual(expected, hexdigest(value)) - - def test_int_not_str(self): - value = 123 - expected = self._expected(value) - self.assertNotEqual(expected, hexdigest(str(value))) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/metadata/test_metadata_filter.py b/lib/iris/tests/unit/common/metadata/test_metadata_filter.py deleted file mode 100644 index 9c5987f235..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_metadata_filter.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.common.metadata_filter`. - -""" - -import numpy as np - -from iris.common.metadata import ( - CoordMetadata, - DimCoordMetadata, - metadata_filter, -) -from iris.coords import AuxCoord - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - -Mock = tests.mock.Mock - - -class Test_standard(tests.IrisTest): - def test_instances_non_iterable(self): - item = Mock() - item.name.return_value = "one" - result = metadata_filter(item, item="one") - self.assertEqual(1, len(result)) - self.assertIn(item, result) - - def test_name(self): - name_one = Mock() - name_one.name.return_value = "one" - name_two = Mock() - name_two.name.return_value = "two" - input_list = [name_one, name_two] - result = metadata_filter(input_list, item="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) - - def test_item(self): - coord = Mock(__class__=AuxCoord) - mock = Mock() - input_list = [coord, mock] - result = metadata_filter(input_list, item=coord) - self.assertIn(coord, result) - self.assertNotIn(mock, result) - - def test_item_metadata(self): - coord = Mock(metadata=CoordMetadata) - dim_coord = Mock(metadata=DimCoordMetadata) - input_list = [coord, dim_coord] - result = metadata_filter(input_list, item=coord) - self.assertIn(coord, result) - self.assertNotIn(dim_coord, result) - - def test_standard_name(self): - name_one = Mock(standard_name="one") - name_two = Mock(standard_name="two") - input_list = [name_one, name_two] - result = metadata_filter(input_list, standard_name="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) - - def test_long_name(self): - name_one = Mock(long_name="one") - name_two = Mock(long_name="two") - input_list = [name_one, name_two] - result = metadata_filter(input_list, long_name="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) - - def test_var_name(self): - name_one = Mock(var_name="one") - name_two = Mock(var_name="two") - input_list = [name_one, name_two] - result = metadata_filter(input_list, var_name="one") - self.assertIn(name_one, result) - self.assertNotIn(name_two, result) - - def test_attributes(self): - # Confirm that this can handle attrib dicts including np arrays. - attrib_one_two = Mock( - attributes={"one": np.arange(1), "two": np.arange(2)} - ) - attrib_three_four = Mock( - attributes={"three": np.arange(3), "four": np.arange(4)} - ) - input_list = [attrib_one_two, attrib_three_four] - result = metadata_filter( - input_list, attributes=attrib_one_two.attributes - ) - self.assertIn(attrib_one_two, result) - self.assertNotIn(attrib_three_four, result) - - def test_invalid_attributes(self): - attrib_one = Mock(attributes={"one": 1}) - input_list = [attrib_one] - self.assertRaisesRegex( - ValueError, - ".*expecting a dictionary.*", - metadata_filter, - input_list, - attributes="one", - ) - - def test_axis__by_guess(self): - # see https://docs.python.org/3/library/unittest.mock.html#deleting-attributes - axis_lon = Mock(standard_name="longitude") - del axis_lon.axis - axis_lat = Mock(standard_name="latitude") - del axis_lat.axis - input_list = [axis_lon, axis_lat] - result = metadata_filter(input_list, axis="x") - self.assertIn(axis_lon, result) - self.assertNotIn(axis_lat, result) - - def test_axis__by_member(self): - axis_x = Mock(axis="x") - axis_y = Mock(axis="y") - input_list = [axis_x, axis_y] - result = metadata_filter(input_list, axis="x") - self.assertEqual(1, len(result)) - self.assertIn(axis_x, result) - - def test_multiple_args(self): - coord_one = Mock(__class__=AuxCoord, long_name="one") - coord_two = Mock(__class__=AuxCoord, long_name="two") - input_list = [coord_one, coord_two] - result = metadata_filter(input_list, item=coord_one, long_name="one") - self.assertIn(coord_one, result) - self.assertNotIn(coord_two, result) diff --git a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py b/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py deleted file mode 100644 index 5ecf0b90d5..0000000000 --- a/lib/iris/tests/unit/common/metadata/test_metadata_manager_factory.py +++ /dev/null @@ -1,203 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.common.metadata.metadata_manager_factory`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import pickle -import unittest.mock as mock - -from cf_units import Unit - -from iris.common.metadata import ( - AncillaryVariableMetadata, - BaseMetadata, - CellMeasureMetadata, - CoordMetadata, - CubeMetadata, - metadata_manager_factory, -) -from iris.experimental.ugrid.metadata import ConnectivityMetadata - -BASES = [ - AncillaryVariableMetadata, - BaseMetadata, - CellMeasureMetadata, - ConnectivityMetadata, - CoordMetadata, - CubeMetadata, -] - - -class Test_factory(tests.IrisTest): - def test__kwargs_invalid(self): - emsg = "Invalid 'BaseMetadata' field parameters, got 'wibble'." - with self.assertRaisesRegex(ValueError, emsg): - metadata_manager_factory(BaseMetadata, wibble="nope") - - -class Test_instance(tests.IrisTest): - def setUp(self): - self.bases = BASES - - def test__namespace(self): - namespace = [ - "DEFAULT_NAME", - "__init__", - "__eq__", - "__getstate__", - "__ne__", - "__reduce__", - "__repr__", - "__setstate__", - "fields", - "name", - "token", - "values", - ] - for base in self.bases: - metadata = metadata_manager_factory(base) - for name in namespace: - self.assertTrue(hasattr(metadata, name)) - if base is CubeMetadata: - self.assertTrue(hasattr(metadata, "_names")) - self.assertIs(metadata.cls, base) - - def test__kwargs_default(self): - for base in self.bases: - kwargs = dict(zip(base._fields, [None] * len(base._fields))) - metadata = metadata_manager_factory(base) - self.assertEqual(metadata.values._asdict(), kwargs) - - def test__kwargs(self): - for base in self.bases: - kwargs = dict(zip(base._fields, range(len(base._fields)))) - metadata = metadata_manager_factory(base, **kwargs) - self.assertEqual(metadata.values._asdict(), kwargs) - - -class Test_instance___eq__(tests.IrisTest): - def setUp(self): - self.metadata = metadata_manager_factory(BaseMetadata) - - def test__not_implemented(self): - self.assertNotEqual(self.metadata, 1) - - def test__not_is_cls(self): - base = BaseMetadata - other = metadata_manager_factory(base) - self.assertIs(other.cls, base) - other.cls = CoordMetadata - self.assertNotEqual(self.metadata, other) - - def test__not_values(self): - standard_name = mock.sentinel.standard_name - other = metadata_manager_factory( - BaseMetadata, standard_name=standard_name - ) - self.assertEqual(other.standard_name, standard_name) - self.assertIsNone(other.long_name) - self.assertIsNone(other.var_name) - self.assertIsNone(other.units) - self.assertIsNone(other.attributes) - self.assertNotEqual(self.metadata, other) - - def test__same_default(self): - other = metadata_manager_factory(BaseMetadata) - self.assertEqual(self.metadata, other) - - def test__same(self): - kwargs = dict( - standard_name=1, long_name=2, var_name=3, units=4, attributes=5 - ) - metadata = metadata_manager_factory(BaseMetadata, **kwargs) - other = metadata_manager_factory(BaseMetadata, **kwargs) - self.assertEqual(metadata.values._asdict(), kwargs) - self.assertEqual(metadata, other) - - -class Test_instance____repr__(tests.IrisTest): - def setUp(self): - self.metadata = metadata_manager_factory(BaseMetadata) - - def test(self): - standard_name = mock.sentinel.standard_name - long_name = mock.sentinel.long_name - var_name = mock.sentinel.var_name - units = mock.sentinel.units - attributes = mock.sentinel.attributes - values = (standard_name, long_name, var_name, units, attributes) - - for field, value in zip(self.metadata.fields, values): - setattr(self.metadata, field, value) - - result = repr(self.metadata) - expected = ( - "MetadataManager(standard_name={!r}, long_name={!r}, var_name={!r}, " - "units={!r}, attributes={!r})" - ) - self.assertEqual(result, expected.format(*values)) - - -class Test_instance__pickle(tests.IrisTest): - def setUp(self): - self.standard_name = "standard_name" - self.long_name = "long_name" - self.var_name = "var_name" - self.units = Unit("1") - self.attributes = dict(hello="world") - values = ( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - ) - kwargs = dict(zip(BaseMetadata._fields, values)) - self.metadata = metadata_manager_factory(BaseMetadata, **kwargs) - - def test_pickle(self): - for protocol in range(pickle.HIGHEST_PROTOCOL + 1): - with self.temp_filename(suffix=".pkl") as fname: - with open(fname, "wb") as fo: - pickle.dump(self.metadata, fo, protocol=protocol) - with open(fname, "rb") as fi: - metadata = pickle.load(fi) - self.assertEqual(metadata, self.metadata) - - -class Test_instance__fields(tests.IrisTest): - def setUp(self): - self.bases = BASES - - def test(self): - for base in self.bases: - fields = base._fields - metadata = metadata_manager_factory(base) - self.assertEqual(metadata.fields, fields) - for field in fields: - hasattr(metadata, field) - - -class Test_instance__values(tests.IrisTest): - def setUp(self): - self.bases = BASES - - def test(self): - for base in self.bases: - metadata = metadata_manager_factory(base) - result = metadata.values - self.assertIsInstance(result, base) - self.assertEqual(result._fields, base._fields) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/mixin/__init__.py b/lib/iris/tests/unit/common/mixin/__init__.py deleted file mode 100644 index 493e140626..0000000000 --- a/lib/iris/tests/unit/common/mixin/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.common.mixin` package.""" diff --git a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py b/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py deleted file mode 100644 index 88a88be567..0000000000 --- a/lib/iris/tests/unit/common/mixin/test_CFVariableMixin.py +++ /dev/null @@ -1,380 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.mixin.CFVariableMixin`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from collections import OrderedDict, namedtuple -from unittest import mock - -from cf_units import Unit - -from iris.common.metadata import ( - AncillaryVariableMetadata, - BaseMetadata, - CellMeasureMetadata, - CoordMetadata, - CubeMetadata, -) -from iris.common.mixin import CFVariableMixin, LimitedAttributeDict -from iris.experimental.ugrid.metadata import ConnectivityMetadata - - -class Test__getter(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.metadata = mock.sentinel.metadata - - metadata = mock.MagicMock( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - values=self.metadata, - ) - - self.item = CFVariableMixin() - self.item._metadata_manager = metadata - - def test_standard_name(self): - self.assertEqual(self.item.standard_name, self.standard_name) - - def test_long_name(self): - self.assertEqual(self.item.long_name, self.long_name) - - def test_var_name(self): - self.assertEqual(self.item.var_name, self.var_name) - - def test_units(self): - self.assertEqual(self.item.units, self.units) - - def test_attributes(self): - self.assertEqual(self.item.attributes, self.attributes) - - def test_metadata(self): - self.assertEqual(self.item.metadata, self.metadata) - - -class Test__setter(tests.IrisTest): - def setUp(self): - metadata = mock.MagicMock( - standard_name=mock.sentinel.standard_name, - long_name=mock.sentinel.long_name, - var_name=mock.sentinel.var_name, - units=mock.sentinel.units, - attributes=mock.sentinel.attributes, - token=lambda name: name, - ) - - self.item = CFVariableMixin() - self.item._metadata_manager = metadata - - def test_standard_name__valid(self): - standard_name = "air_temperature" - self.item.standard_name = standard_name - self.assertEqual( - self.item._metadata_manager.standard_name, standard_name - ) - - def test_standard_name__none(self): - self.item.standard_name = None - self.assertIsNone(self.item._metadata_manager.standard_name) - - def test_standard_name__invalid(self): - standard_name = "nope nope" - emsg = f"{standard_name!r} is not a valid standard_name" - with self.assertRaisesRegex(ValueError, emsg): - self.item.standard_name = standard_name - - def test_long_name(self): - long_name = "long_name" - self.item.long_name = long_name - self.assertEqual(self.item._metadata_manager.long_name, long_name) - - def test_long_name__none(self): - self.item.long_name = None - self.assertIsNone(self.item._metadata_manager.long_name) - - def test_var_name(self): - var_name = "var_name" - self.item.var_name = var_name - self.assertEqual(self.item._metadata_manager.var_name, var_name) - - def test_var_name__none(self): - self.item.var_name = None - self.assertIsNone(self.item._metadata_manager.var_name) - - def test_var_name__invalid_token(self): - var_name = "nope nope" - self.item._metadata_manager.token = lambda name: None - emsg = f"{var_name!r} is not a valid NetCDF variable name." - with self.assertRaisesRegex(ValueError, emsg): - self.item.var_name = var_name - - def test_attributes(self): - attributes = dict(hello="world") - self.item.attributes = attributes - self.assertEqual(self.item._metadata_manager.attributes, attributes) - self.assertIsNot(self.item._metadata_manager.attributes, attributes) - self.assertIsInstance( - self.item._metadata_manager.attributes, LimitedAttributeDict - ) - - def test_attributes__none(self): - self.item.attributes = None - self.assertEqual(self.item._metadata_manager.attributes, {}) - - -class Test__metadata_setter(tests.IrisTest): - def setUp(self): - class Metadata: - def __init__(self): - self.cls = BaseMetadata - self.fields = BaseMetadata._fields - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.token = lambda name: name - - @property - def values(self): - return dict( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - ) - - metadata = Metadata() - self.item = CFVariableMixin() - self.item._metadata_manager = metadata - self.attributes = dict(one=1, two=2, three=3) - self.args = OrderedDict( - standard_name="air_temperature", - long_name="long_name", - var_name="var_name", - units=Unit("1"), - attributes=self.attributes, - ) - - def test_dict(self): - metadata = dict(**self.args) - self.item.metadata = metadata - self.assertEqual(self.item._metadata_manager.values, metadata) - self.assertIsNot( - self.item._metadata_manager.attributes, self.attributes - ) - - def test_dict__partial(self): - metadata = dict(**self.args) - del metadata["standard_name"] - self.item.metadata = metadata - metadata["standard_name"] = mock.sentinel.standard_name - self.assertEqual(self.item._metadata_manager.values, metadata) - self.assertIsNot( - self.item._metadata_manager.attributes, self.attributes - ) - - def test_ordereddict(self): - metadata = self.args - self.item.metadata = metadata - self.assertEqual(self.item._metadata_manager.values, metadata) - self.assertIsNot( - self.item._metadata_manager.attributes, self.attributes - ) - - def test_ordereddict__partial(self): - metadata = self.args - del metadata["long_name"] - del metadata["units"] - self.item.metadata = metadata - metadata["long_name"] = mock.sentinel.long_name - metadata["units"] = mock.sentinel.units - self.assertEqual(self.item._metadata_manager.values, metadata) - - def test_tuple(self): - metadata = tuple(self.args.values()) - self.item.metadata = metadata - result = tuple( - [ - getattr(self.item._metadata_manager, field) - for field in self.item._metadata_manager.fields - ] - ) - self.assertEqual(result, metadata) - self.assertIsNot( - self.item._metadata_manager.attributes, self.attributes - ) - - def test_tuple__missing(self): - metadata = list(self.args.values()) - del metadata[2] - emsg = "Invalid .* metadata, require .* to be specified." - with self.assertRaisesRegex(TypeError, emsg): - self.item.metadata = tuple(metadata) - - def test_namedtuple(self): - Metadata = namedtuple( - "Metadata", - ("standard_name", "long_name", "var_name", "units", "attributes"), - ) - metadata = Metadata(**self.args) - self.item.metadata = metadata - self.assertEqual( - self.item._metadata_manager.values, metadata._asdict() - ) - self.assertIsNot( - self.item._metadata_manager.attributes, metadata.attributes - ) - - def test_namedtuple__partial(self): - Metadata = namedtuple( - "Metadata", ("standard_name", "long_name", "var_name", "units") - ) - del self.args["attributes"] - metadata = Metadata(**self.args) - self.item.metadata = metadata - expected = metadata._asdict() - expected.update(dict(attributes=mock.sentinel.attributes)) - self.assertEqual(self.item._metadata_manager.values, expected) - - def test_class_ancillaryvariablemetadata(self): - metadata = AncillaryVariableMetadata(**self.args) - self.item.metadata = metadata - self.assertEqual( - self.item._metadata_manager.values, metadata._asdict() - ) - self.assertIsNot( - self.item._metadata_manager.attributes, metadata.attributes - ) - - def test_class_basemetadata(self): - metadata = BaseMetadata(**self.args) - self.item.metadata = metadata - self.assertEqual( - self.item._metadata_manager.values, metadata._asdict() - ) - self.assertIsNot( - self.item._metadata_manager.attributes, metadata.attributes - ) - - def test_class_cellmeasuremetadata(self): - self.args["measure"] = None - metadata = CellMeasureMetadata(**self.args) - self.item.metadata = metadata - expected = metadata._asdict() - del expected["measure"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot( - self.item._metadata_manager.attributes, metadata.attributes - ) - - def test_class_connectivitymetadata(self): - self.args.update( - dict(cf_role=None, start_index=None, location_axis=None) - ) - metadata = ConnectivityMetadata(**self.args) - self.item.metadata = metadata - expected = metadata._asdict() - del expected["cf_role"] - del expected["start_index"] - del expected["location_axis"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot( - self.item._metadata_manager.attributes, metadata.attributes - ) - - def test_class_coordmetadata(self): - self.args.update(dict(coord_system=None, climatological=False)) - metadata = CoordMetadata(**self.args) - self.item.metadata = metadata - expected = metadata._asdict() - del expected["coord_system"] - del expected["climatological"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot( - self.item._metadata_manager.attributes, metadata.attributes - ) - - def test_class_cubemetadata(self): - self.args["cell_methods"] = None - metadata = CubeMetadata(**self.args) - self.item.metadata = metadata - expected = metadata._asdict() - del expected["cell_methods"] - self.assertEqual(self.item._metadata_manager.values, expected) - self.assertIsNot( - self.item._metadata_manager.attributes, metadata.attributes - ) - - -class Test_rename(tests.IrisTest): - def setUp(self): - metadata = mock.MagicMock( - standard_name=mock.sentinel.standard_name, - long_name=mock.sentinel.long_name, - var_name=mock.sentinel.var_name, - units=mock.sentinel.units, - attributes=mock.sentinel.attributes, - values=mock.sentinel.metadata, - token=lambda name: name, - ) - - self.item = CFVariableMixin() - self.item._metadata_manager = metadata - - def test__valid_standard_name(self): - name = "air_temperature" - self.item.rename(name) - self.assertEqual(self.item._metadata_manager.standard_name, name) - self.assertIsNone(self.item._metadata_manager.long_name) - self.assertIsNone(self.item._metadata_manager.var_name) - - def test__invalid_standard_name(self): - name = "nope nope" - self.item.rename(name) - self.assertIsNone(self.item._metadata_manager.standard_name) - self.assertEqual(self.item._metadata_manager.long_name, name) - self.assertIsNone(self.item._metadata_manager.var_name) - - -class Test_name(tests.IrisTest): - def setUp(self): - class Metadata: - def __init__(self, name): - self.name = mock.MagicMock(return_value=name) - - self.name = mock.sentinel.name - metadata = Metadata(self.name) - - self.item = CFVariableMixin() - self.item._metadata_manager = metadata - - def test(self): - default = mock.sentinel.default - token = mock.sentinel.token - result = self.item.name(default=default, token=token) - self.assertEqual(result, self.name) - self.item._metadata_manager.name.assert_called_with( - default=default, token=token - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py b/lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py deleted file mode 100644 index 32c78b6697..0000000000 --- a/lib/iris/tests/unit/common/mixin/test_LimitedAttributeDict.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.mixin.LimitedAttributeDict`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.common.mixin import LimitedAttributeDict - - -class Test(tests.IrisTest): - def setUp(self): - self.forbidden_keys = LimitedAttributeDict._forbidden_keys - self.emsg = "{!r} is not a permitted attribute" - - def test__invalid_keys(self): - for key in self.forbidden_keys: - with self.assertRaisesRegex(ValueError, self.emsg.format(key)): - _ = LimitedAttributeDict(**{key: None}) - - def test___eq__(self): - values = dict( - one=mock.sentinel.one, - two=mock.sentinel.two, - three=mock.sentinel.three, - ) - left = LimitedAttributeDict(**values) - right = LimitedAttributeDict(**values) - self.assertEqual(left, right) - self.assertEqual(left, values) - - def test___eq___numpy(self): - values = dict(one=np.arange(1), two=np.arange(2), three=np.arange(3)) - left = LimitedAttributeDict(**values) - right = LimitedAttributeDict(**values) - self.assertEqual(left, right) - self.assertEqual(left, values) - values = dict(one=np.arange(1), two=np.arange(1), three=np.arange(1)) - left = LimitedAttributeDict(dict(one=0, two=0, three=0)) - right = LimitedAttributeDict(**values) - self.assertEqual(left, right) - self.assertEqual(left, values) - - def test___setitem__(self): - for key in self.forbidden_keys: - item = LimitedAttributeDict() - with self.assertRaisesRegex(ValueError, self.emsg.format(key)): - item[key] = None - - def test_update(self): - for key in self.forbidden_keys: - item = LimitedAttributeDict() - with self.assertRaisesRegex(ValueError, self.emsg.format(key)): - other = {key: None} - item.update(other) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py b/lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py deleted file mode 100644 index 8fc21f2965..0000000000 --- a/lib/iris/tests/unit/common/mixin/test__get_valid_standard_name.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.common.mixin._get_valid_standard_name`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.common.mixin import _get_valid_standard_name - - -class Test(tests.IrisTest): - def setUp(self): - self.emsg = "'{}' is not a valid standard_name" - - def test_pass_thru_none(self): - name = None - self.assertEqual(_get_valid_standard_name(name), name) - - def test_pass_thru_empty(self): - name = "" - self.assertEqual(_get_valid_standard_name(name), name) - - def test_pass_thru_whitespace(self): - name = " " - self.assertEqual(_get_valid_standard_name(name), name) - - def test_valid_standard_name(self): - name = "air_temperature" - self.assertEqual(_get_valid_standard_name(name), name) - - def test_standard_name_alias(self): - name = "atmosphere_optical_thickness_due_to_pm1_ambient_aerosol" - self.assertEqual(_get_valid_standard_name(name), name) - - def test_invalid_standard_name(self): - name = "not_a_standard_name" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): - _get_valid_standard_name(name) - - def test_valid_standard_name_valid_modifier(self): - name = "air_temperature standard_error" - self.assertEqual(_get_valid_standard_name(name), name) - - def test_valid_standard_name_valid_modifier_extra_spaces(self): - name = "air_temperature standard_error" - self.assertEqual(_get_valid_standard_name(name), name) - - def test_invalid_standard_name_valid_modifier(self): - name = "not_a_standard_name standard_error" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): - _get_valid_standard_name(name) - - def test_valid_standard_invalid_name_modifier(self): - name = "air_temperature extra_names standard_error" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): - _get_valid_standard_name(name) - - def test_valid_standard_valid_name_modifier_extra_names(self): - name = "air_temperature standard_error extra words" - with self.assertRaisesRegex(ValueError, self.emsg.format(name)): - _get_valid_standard_name(name) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/common/resolve/__init__.py b/lib/iris/tests/unit/common/resolve/__init__.py deleted file mode 100644 index d0b189e59d..0000000000 --- a/lib/iris/tests/unit/common/resolve/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.common.resolve` package.""" diff --git a/lib/iris/tests/unit/common/resolve/test_Resolve.py b/lib/iris/tests/unit/common/resolve/test_Resolve.py deleted file mode 100644 index 840f65db01..0000000000 --- a/lib/iris/tests/unit/common/resolve/test_Resolve.py +++ /dev/null @@ -1,4826 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.common.resolve.Resolve`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from collections import namedtuple -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import Mock, sentinel - -from cf_units import Unit -import numpy as np - -from iris.common.lenient import LENIENT -from iris.common.metadata import CubeMetadata -from iris.common.resolve import ( - Resolve, - _AuxCoverage, - _CategoryItems, - _DimCoverage, - _Item, - _PreparedFactory, - _PreparedItem, - _PreparedMetadata, -) -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test___init__(tests.IrisTest): - def setUp(self): - target = "iris.common.resolve.Resolve.__call__" - self.m_call = mock.MagicMock(return_value=sentinel.return_value) - _ = self.patch(target, new=self.m_call) - - def _assert_members_none(self, resolve): - self.assertIsNone(resolve.lhs_cube_resolved) - self.assertIsNone(resolve.rhs_cube_resolved) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) - self.assertIsNone(resolve.lhs_cube_dim_coverage) - self.assertIsNone(resolve.lhs_cube_aux_coverage) - self.assertIsNone(resolve.rhs_cube_dim_coverage) - self.assertIsNone(resolve.rhs_cube_aux_coverage) - self.assertIsNone(resolve.map_rhs_to_lhs) - self.assertIsNone(resolve.mapping) - self.assertIsNone(resolve.prepared_category) - self.assertIsNone(resolve.prepared_factories) - self.assertIsNone(resolve._broadcast_shape) - - def test_lhs_rhs_default(self): - resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self._assert_members_none(resolve) - self.assertEqual(0, self.m_call.call_count) - - def test_lhs_rhs_provided(self): - m_lhs = sentinel.lhs - m_rhs = sentinel.rhs - resolve = Resolve(lhs=m_lhs, rhs=m_rhs) - # The lhs_cube and rhs_cube are only None due - # to __call__ being mocked. See Test___call__ - # for appropriate test coverage. - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self._assert_members_none(resolve) - self.assertEqual(1, self.m_call.call_count) - call_args = mock.call(m_lhs, m_rhs) - self.assertEqual(call_args, self.m_call.call_args) - - -class Test___call__(tests.IrisTest): - def setUp(self): - self.m_lhs = mock.MagicMock(spec=Cube) - self.m_rhs = mock.MagicMock(spec=Cube) - target = "iris.common.resolve.Resolve.{method}" - method = target.format(method="_metadata_resolve") - self.m_metadata_resolve = self.patch(method) - method = target.format(method="_metadata_coverage") - self.m_metadata_coverage = self.patch(method) - method = target.format(method="_metadata_mapping") - self.m_metadata_mapping = self.patch(method) - method = target.format(method="_metadata_prepare") - self.m_metadata_prepare = self.patch(method) - - def test_lhs_not_cube(self): - emsg = "'LHS' argument to be a 'Cube'" - with self.assertRaisesRegex(TypeError, emsg): - _ = Resolve(rhs=self.m_rhs) - - def test_rhs_not_cube(self): - emsg = "'RHS' argument to be a 'Cube'" - with self.assertRaisesRegex(TypeError, emsg): - _ = Resolve(lhs=self.m_lhs) - - def _assert_called_metadata_methods(self): - call_args = mock.call() - self.assertEqual(1, self.m_metadata_resolve.call_count) - self.assertEqual(call_args, self.m_metadata_resolve.call_args) - self.assertEqual(1, self.m_metadata_coverage.call_count) - self.assertEqual(call_args, self.m_metadata_coverage.call_args) - self.assertEqual(1, self.m_metadata_mapping.call_count) - self.assertEqual(call_args, self.m_metadata_mapping.call_args) - self.assertEqual(1, self.m_metadata_prepare.call_count) - self.assertEqual(call_args, self.m_metadata_prepare.call_args) - - def test_map_rhs_to_lhs__less_than(self): - self.m_lhs.ndim = 2 - self.m_rhs.ndim = 1 - resolve = Resolve(lhs=self.m_lhs, rhs=self.m_rhs) - self.assertEqual(self.m_lhs, resolve.lhs_cube) - self.assertEqual(self.m_rhs, resolve.rhs_cube) - self.assertTrue(resolve.map_rhs_to_lhs) - self._assert_called_metadata_methods() - - def test_map_rhs_to_lhs__equal(self): - self.m_lhs.ndim = 2 - self.m_rhs.ndim = 2 - resolve = Resolve(lhs=self.m_lhs, rhs=self.m_rhs) - self.assertEqual(self.m_lhs, resolve.lhs_cube) - self.assertEqual(self.m_rhs, resolve.rhs_cube) - self.assertTrue(resolve.map_rhs_to_lhs) - self._assert_called_metadata_methods() - - def test_map_lhs_to_rhs(self): - self.m_lhs.ndim = 2 - self.m_rhs.ndim = 3 - resolve = Resolve(lhs=self.m_lhs, rhs=self.m_rhs) - self.assertEqual(self.m_lhs, resolve.lhs_cube) - self.assertEqual(self.m_rhs, resolve.rhs_cube) - self.assertFalse(resolve.map_rhs_to_lhs) - self._assert_called_metadata_methods() - - -class Test__categorise_items(tests.IrisTest): - def setUp(self): - self.coord_dims = {} - # configure dim coords - coord = mock.Mock(metadata=sentinel.dim_metadata1) - self.dim_coords = [coord] - self.coord_dims[coord] = sentinel.dims1 - # configure aux and scalar coords - self.aux_coords = [] - pairs = [ - (sentinel.aux_metadata2, sentinel.dims2), - (sentinel.aux_metadata3, sentinel.dims3), - (sentinel.scalar_metadata4, None), - (sentinel.scalar_metadata5, None), - (sentinel.scalar_metadata6, None), - ] - for metadata, dims in pairs: - coord = mock.Mock(metadata=metadata) - self.aux_coords.append(coord) - self.coord_dims[coord] = dims - func = lambda coord: self.coord_dims[coord] - self.cube = mock.Mock( - aux_coords=self.aux_coords, - dim_coords=self.dim_coords, - coord_dims=func, - ) - - def test(self): - result = Resolve._categorise_items(self.cube) - self.assertIsInstance(result, _CategoryItems) - self.assertEqual(1, len(result.items_dim)) - # check dim coords - for item in result.items_dim: - self.assertIsInstance(item, _Item) - (coord,) = self.dim_coords - dims = self.coord_dims[coord] - expected = [_Item(metadata=coord.metadata, coord=coord, dims=dims)] - self.assertEqual(expected, result.items_dim) - # check aux coords - self.assertEqual(2, len(result.items_aux)) - for item in result.items_aux: - self.assertIsInstance(item, _Item) - expected_aux, expected_scalar = [], [] - for coord in self.aux_coords: - dims = self.coord_dims[coord] - item = _Item(metadata=coord.metadata, coord=coord, dims=dims) - if dims: - expected_aux.append(item) - else: - expected_scalar.append(item) - self.assertEqual(expected_aux, result.items_aux) - # check scalar coords - self.assertEqual(3, len(result.items_scalar)) - for item in result.items_scalar: - self.assertIsInstance(item, _Item) - self.assertEqual(expected_scalar, result.items_scalar) - - -class Test__metadata_resolve(tests.IrisTest): - def setUp(self): - self.target = "iris.common.resolve.Resolve._categorise_items" - self.m_lhs_cube = sentinel.lhs_cube - self.m_rhs_cube = sentinel.rhs_cube - - @staticmethod - def _create_items(pairs): - # this wrapper (hack) is necessary in order to support mocking - # the "name" method (callable) of the metadata, as "name" is already - # part of the mock API - this is always troublesome in mock-world. - Wrapper = namedtuple("Wrapper", ("name", "value")) - result = [] - for name, dims in pairs: - metadata = Wrapper(name=lambda: str(name), value=name) - coord = mock.Mock(metadata=metadata) - item = _Item(metadata=metadata, coord=coord, dims=dims) - result.append(item) - return result - - def test_metadata_same(self): - category = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) - # configure dim coords - pairs = [(sentinel.dim_metadata1, sentinel.dims1)] - category.items_dim.extend(self._create_items(pairs)) - # configure aux coords - pairs = [ - (sentinel.aux_metadata1, sentinel.dims2), - (sentinel.aux_metadata2, sentinel.dims3), - ] - category.items_aux.extend(self._create_items(pairs)) - # configure scalar coords - pairs = [ - (sentinel.scalar_metadata1, None), - (sentinel.scalar_metadata2, None), - (sentinel.scalar_metadata3, None), - ] - category.items_scalar.extend(self._create_items(pairs)) - - side_effect = (category, category) - mocker = self.patch(self.target, side_effect=side_effect) - - resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) - - # require to explicitly configure cubes - resolve.lhs_cube = self.m_lhs_cube - resolve.rhs_cube = self.m_rhs_cube - resolve._metadata_resolve() - - self.assertEqual(mocker.call_count, 2) - calls = [mock.call(self.m_lhs_cube), mock.call(self.m_rhs_cube)] - self.assertEqual(calls, mocker.call_args_list) - - self.assertEqual(category, resolve.lhs_cube_category) - self.assertEqual(category, resolve.rhs_cube_category) - expected = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) - self.assertEqual(expected, resolve.lhs_cube_category_local) - self.assertEqual(expected, resolve.rhs_cube_category_local) - self.assertEqual(category, resolve.category_common) - - def test_metadata_overlap(self): - # configure the lhs cube category - category_lhs = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - # configure dim coords - pairs = [ - (sentinel.dim_metadata1, sentinel.dims1), - (sentinel.dim_metadata2, sentinel.dims2), - ] - category_lhs.items_dim.extend(self._create_items(pairs)) - # configure aux coords - pairs = [ - (sentinel.aux_metadata1, sentinel.dims3), - (sentinel.aux_metadata2, sentinel.dims4), - ] - category_lhs.items_aux.extend(self._create_items(pairs)) - # configure scalar coords - pairs = [ - (sentinel.scalar_metadata1, None), - (sentinel.scalar_metadata2, None), - ] - category_lhs.items_scalar.extend(self._create_items(pairs)) - - # configure the rhs cube category - category_rhs = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - # configure dim coords - category_rhs.items_dim.append(category_lhs.items_dim[0]) - pairs = [(sentinel.dim_metadata200, sentinel.dims2)] - category_rhs.items_dim.extend(self._create_items(pairs)) - # configure aux coords - category_rhs.items_aux.append(category_lhs.items_aux[0]) - pairs = [(sentinel.aux_metadata200, sentinel.dims4)] - category_rhs.items_aux.extend(self._create_items(pairs)) - # configure scalar coords - category_rhs.items_scalar.append(category_lhs.items_scalar[0]) - pairs = [(sentinel.scalar_metadata200, None)] - category_rhs.items_scalar.extend(self._create_items(pairs)) - - side_effect = (category_lhs, category_rhs) - mocker = self.patch(self.target, side_effect=side_effect) - - resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) - - # require to explicitly configure cubes - resolve.lhs_cube = self.m_lhs_cube - resolve.rhs_cube = self.m_rhs_cube - resolve._metadata_resolve() - - self.assertEqual(2, mocker.call_count) - calls = [mock.call(self.m_lhs_cube), mock.call(self.m_rhs_cube)] - self.assertEqual(calls, mocker.call_args_list) - - self.assertEqual(category_lhs, resolve.lhs_cube_category) - self.assertEqual(category_rhs, resolve.rhs_cube_category) - - items_dim = [category_lhs.items_dim[1]] - items_aux = [category_lhs.items_aux[1]] - items_scalar = [category_lhs.items_scalar[1]] - expected = _CategoryItems( - items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar - ) - self.assertEqual(expected, resolve.lhs_cube_category_local) - - items_dim = [category_rhs.items_dim[1]] - items_aux = [category_rhs.items_aux[1]] - items_scalar = [category_rhs.items_scalar[1]] - expected = _CategoryItems( - items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar - ) - self.assertEqual(expected, resolve.rhs_cube_category_local) - - items_dim = [category_lhs.items_dim[0]] - items_aux = [category_lhs.items_aux[0]] - items_scalar = [category_lhs.items_scalar[0]] - expected = _CategoryItems( - items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar - ) - self.assertEqual(expected, resolve.category_common) - - def test_metadata_different(self): - # configure the lhs cube category - category_lhs = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - # configure dim coords - pairs = [ - (sentinel.dim_metadata1, sentinel.dims1), - (sentinel.dim_metadata2, sentinel.dims2), - ] - category_lhs.items_dim.extend(self._create_items(pairs)) - # configure aux coords - pairs = [ - (sentinel.aux_metadata1, sentinel.dims3), - (sentinel.aux_metadata2, sentinel.dims4), - ] - category_lhs.items_aux.extend(self._create_items(pairs)) - # configure scalar coords - pairs = [ - (sentinel.scalar_metadata1, None), - (sentinel.scalar_metadata2, None), - ] - category_lhs.items_scalar.extend(self._create_items(pairs)) - - # configure the rhs cube category - category_rhs = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - # configure dim coords - pairs = [ - (sentinel.dim_metadata100, sentinel.dims1), - (sentinel.dim_metadata200, sentinel.dims2), - ] - category_rhs.items_dim.extend(self._create_items(pairs)) - # configure aux coords - pairs = [ - (sentinel.aux_metadata100, sentinel.dims3), - (sentinel.aux_metadata200, sentinel.dims4), - ] - category_rhs.items_aux.extend(self._create_items(pairs)) - # configure scalar coords - pairs = [ - (sentinel.scalar_metadata100, None), - (sentinel.scalar_metadata200, None), - ] - category_rhs.items_scalar.extend(self._create_items(pairs)) - - side_effect = (category_lhs, category_rhs) - mocker = self.patch(self.target, side_effect=side_effect) - - resolve = Resolve() - self.assertIsNone(resolve.lhs_cube) - self.assertIsNone(resolve.rhs_cube) - self.assertIsNone(resolve.lhs_cube_category) - self.assertIsNone(resolve.rhs_cube_category) - self.assertIsNone(resolve.lhs_cube_category_local) - self.assertIsNone(resolve.rhs_cube_category_local) - self.assertIsNone(resolve.category_common) - - # first require to explicitly lhs/rhs configure cubes - resolve.lhs_cube = self.m_lhs_cube - resolve.rhs_cube = self.m_rhs_cube - resolve._metadata_resolve() - - self.assertEqual(2, mocker.call_count) - calls = [mock.call(self.m_lhs_cube), mock.call(self.m_rhs_cube)] - self.assertEqual(calls, mocker.call_args_list) - - self.assertEqual(category_lhs, resolve.lhs_cube_category) - self.assertEqual(category_rhs, resolve.rhs_cube_category) - self.assertEqual(category_lhs, resolve.lhs_cube_category_local) - self.assertEqual(category_rhs, resolve.rhs_cube_category_local) - expected = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) - self.assertEqual(expected, resolve.category_common) - - -class Test__dim_coverage(tests.IrisTest): - def setUp(self): - self.ndim = 4 - self.cube = mock.Mock(ndim=self.ndim) - self.items = [] - parts = [ - (sentinel.metadata0, sentinel.coord0, (0,)), - (sentinel.metadata1, sentinel.coord1, (1,)), - (sentinel.metadata2, sentinel.coord2, (2,)), - (sentinel.metadata3, sentinel.coord3, (3,)), - ] - column_parts = [x for x in zip(*parts)] - self.metadata, self.coords, self.dims = [list(x) for x in column_parts] - self.dims = [dim for dim, in self.dims] - for metadata, coord, dims in parts: - item = _Item(metadata=metadata, coord=coord, dims=dims) - self.items.append(item) - - def test_coverage_no_local_no_common_all_free(self): - items = [] - common = [] - result = Resolve._dim_coverage(self.cube, items, common) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) - expected = [None] * self.ndim - self.assertEqual(expected, result.metadata) - self.assertEqual(expected, result.coords) - self.assertEqual([], result.dims_common) - self.assertEqual([], result.dims_local) - expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_free) - - def test_coverage_all_local_no_common_no_free(self): - common = [] - result = Resolve._dim_coverage(self.cube, self.items, common) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual(self.metadata, result.metadata) - self.assertEqual(self.coords, result.coords) - self.assertEqual([], result.dims_common) - self.assertEqual(self.dims, result.dims_local) - self.assertEqual([], result.dims_free) - - def test_coverage_no_local_all_common_no_free(self): - result = Resolve._dim_coverage(self.cube, self.items, self.metadata) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual(self.metadata, result.metadata) - self.assertEqual(self.coords, result.coords) - self.assertEqual(self.dims, result.dims_common) - self.assertEqual([], result.dims_local) - self.assertEqual([], result.dims_free) - - def test_coverage_mixed(self): - common = [self.items[1].metadata, self.items[2].metadata] - self.items.pop(0) - self.items.pop(-1) - metadata, coord, dims = sentinel.metadata100, sentinel.coord100, (0,) - self.items.append(_Item(metadata=metadata, coord=coord, dims=dims)) - result = Resolve._dim_coverage(self.cube, self.items, common) - self.assertIsInstance(result, _DimCoverage) - self.assertEqual(self.cube, result.cube) - expected = [ - metadata, - self.items[0].metadata, - self.items[1].metadata, - None, - ] - self.assertEqual(expected, result.metadata) - expected = [coord, self.items[0].coord, self.items[1].coord, None] - self.assertEqual(expected, result.coords) - self.assertEqual([1, 2], result.dims_common) - self.assertEqual([0], result.dims_local) - self.assertEqual([3], result.dims_free) - - -class Test__aux_coverage(tests.IrisTest): - def setUp(self): - self.ndim = 4 - self.cube = mock.Mock(ndim=self.ndim) - # configure aux coords - self.items_aux = [] - aux_parts = [ - (sentinel.aux_metadata0, sentinel.aux_coord0, (0,)), - (sentinel.aux_metadata1, sentinel.aux_coord1, (1,)), - (sentinel.aux_metadata23, sentinel.aux_coord23, (2, 3)), - ] - column_aux_parts = [x for x in zip(*aux_parts)] - self.aux_metadata, self.aux_coords, self.aux_dims = [ - list(x) for x in column_aux_parts - ] - for metadata, coord, dims in aux_parts: - item = _Item(metadata=metadata, coord=coord, dims=dims) - self.items_aux.append(item) - # configure scalar coords - self.items_scalar = [] - scalar_parts = [ - (sentinel.scalar_metadata0, sentinel.scalar_coord0, ()), - (sentinel.scalar_metadata1, sentinel.scalar_coord1, ()), - (sentinel.scalar_metadata2, sentinel.scalar_coord2, ()), - ] - column_scalar_parts = [x for x in zip(*scalar_parts)] - self.scalar_metadata, self.scalar_coords, self.scalar_dims = [ - list(x) for x in column_scalar_parts - ] - for metadata, coord, dims in scalar_parts: - item = _Item(metadata=metadata, coord=coord, dims=dims) - self.items_scalar.append(item) - - def test_coverage_no_local_no_common_all_free(self): - items_aux, items_scalar = [], [] - common_aux, common_scalar = [], [] - result = Resolve._aux_coverage( - self.cube, items_aux, items_scalar, common_aux, common_scalar - ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual([], result.common_items_aux) - self.assertEqual([], result.common_items_scalar) - self.assertEqual([], result.local_items_aux) - self.assertEqual([], result.local_items_scalar) - self.assertEqual([], result.dims_common) - self.assertEqual([], result.dims_local) - expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_free) - - def test_coverage_all_local_no_common_no_free(self): - common_aux, common_scalar = [], [] - result = Resolve._aux_coverage( - self.cube, - self.items_aux, - self.items_scalar, - common_aux, - common_scalar, - ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) - expected = [] - self.assertEqual(expected, result.common_items_aux) - self.assertEqual(expected, result.common_items_scalar) - self.assertEqual(self.items_aux, result.local_items_aux) - self.assertEqual(self.items_scalar, result.local_items_scalar) - self.assertEqual([], result.dims_common) - expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_local) - self.assertEqual([], result.dims_free) - - def test_coverage_no_local_all_common_no_free(self): - result = Resolve._aux_coverage( - self.cube, - self.items_aux, - self.items_scalar, - self.aux_metadata, - self.scalar_metadata, - ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) - self.assertEqual(self.items_aux, result.common_items_aux) - self.assertEqual(self.items_scalar, result.common_items_scalar) - self.assertEqual([], result.local_items_aux) - self.assertEqual([], result.local_items_scalar) - expected = list(range(self.ndim)) - self.assertEqual(expected, result.dims_common) - self.assertEqual([], result.dims_local) - self.assertEqual([], result.dims_free) - - def test_coverage_mixed(self): - common_aux = [self.items_aux[-1].metadata] - common_scalar = [self.items_scalar[1].metadata] - self.items_aux.pop(1) - result = Resolve._aux_coverage( - self.cube, - self.items_aux, - self.items_scalar, - common_aux, - common_scalar, - ) - self.assertIsInstance(result, _AuxCoverage) - self.assertEqual(self.cube, result.cube) - expected = [self.items_aux[-1]] - self.assertEqual(expected, result.common_items_aux) - expected = [self.items_scalar[1]] - self.assertEqual(expected, result.common_items_scalar) - expected = [self.items_aux[0]] - self.assertEqual(expected, result.local_items_aux) - expected = [self.items_scalar[0], self.items_scalar[2]] - self.assertEqual(expected, result.local_items_scalar) - self.assertEqual([2, 3], result.dims_common) - self.assertEqual([0], result.dims_local) - self.assertEqual([1], result.dims_free) - - -class Test__metadata_coverage(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - self.m_lhs_cube = sentinel.lhs_cube - self.resolve.lhs_cube = self.m_lhs_cube - self.m_rhs_cube = sentinel.rhs_cube - self.resolve.rhs_cube = self.m_rhs_cube - self.m_items_dim_metadata = sentinel.items_dim_metadata - self.m_items_aux_metadata = sentinel.items_aux_metadata - self.m_items_scalar_metadata = sentinel.items_scalar_metadata - items_dim = [mock.Mock(metadata=self.m_items_dim_metadata)] - items_aux = [mock.Mock(metadata=self.m_items_aux_metadata)] - items_scalar = [mock.Mock(metadata=self.m_items_scalar_metadata)] - category = _CategoryItems( - items_dim=items_dim, items_aux=items_aux, items_scalar=items_scalar - ) - self.resolve.category_common = category - self.m_items_dim = sentinel.items_dim - self.m_items_aux = sentinel.items_aux - self.m_items_scalar = sentinel.items_scalar - category = _CategoryItems( - items_dim=self.m_items_dim, - items_aux=self.m_items_aux, - items_scalar=self.m_items_scalar, - ) - self.resolve.lhs_cube_category = category - self.resolve.rhs_cube_category = category - target = "iris.common.resolve.Resolve._dim_coverage" - self.m_lhs_cube_dim_coverage = sentinel.lhs_cube_dim_coverage - self.m_rhs_cube_dim_coverage = sentinel.rhs_cube_dim_coverage - side_effect = ( - self.m_lhs_cube_dim_coverage, - self.m_rhs_cube_dim_coverage, - ) - self.mocker_dim_coverage = self.patch(target, side_effect=side_effect) - target = "iris.common.resolve.Resolve._aux_coverage" - self.m_lhs_cube_aux_coverage = sentinel.lhs_cube_aux_coverage - self.m_rhs_cube_aux_coverage = sentinel.rhs_cube_aux_coverage - side_effect = ( - self.m_lhs_cube_aux_coverage, - self.m_rhs_cube_aux_coverage, - ) - self.mocker_aux_coverage = self.patch(target, side_effect=side_effect) - - def test(self): - self.resolve._metadata_coverage() - self.assertEqual(2, self.mocker_dim_coverage.call_count) - calls = [ - mock.call( - self.m_lhs_cube, self.m_items_dim, [self.m_items_dim_metadata] - ), - mock.call( - self.m_rhs_cube, self.m_items_dim, [self.m_items_dim_metadata] - ), - ] - self.assertEqual(calls, self.mocker_dim_coverage.call_args_list) - self.assertEqual(2, self.mocker_aux_coverage.call_count) - calls = [ - mock.call( - self.m_lhs_cube, - self.m_items_aux, - self.m_items_scalar, - [self.m_items_aux_metadata], - [self.m_items_scalar_metadata], - ), - mock.call( - self.m_rhs_cube, - self.m_items_aux, - self.m_items_scalar, - [self.m_items_aux_metadata], - [self.m_items_scalar_metadata], - ), - ] - self.assertEqual(calls, self.mocker_aux_coverage.call_args_list) - self.assertEqual( - self.m_lhs_cube_dim_coverage, self.resolve.lhs_cube_dim_coverage - ) - self.assertEqual( - self.m_rhs_cube_dim_coverage, self.resolve.rhs_cube_dim_coverage - ) - self.assertEqual( - self.m_lhs_cube_aux_coverage, self.resolve.lhs_cube_aux_coverage - ) - self.assertEqual( - self.m_rhs_cube_aux_coverage, self.resolve.rhs_cube_aux_coverage - ) - - -class Test__dim_mapping(tests.IrisTest): - def setUp(self): - self.ndim = 3 - Wrapper = namedtuple("Wrapper", ("name",)) - cube = Wrapper(name=lambda: sentinel.name) - self.src_coverage = _DimCoverage( - cube=cube, - metadata=[], - coords=None, - dims_common=None, - dims_local=None, - dims_free=None, - ) - self.tgt_coverage = _DimCoverage( - cube=cube, - metadata=[], - coords=None, - dims_common=[], - dims_local=None, - dims_free=None, - ) - self.metadata = [ - sentinel.metadata_0, - sentinel.metadata_1, - sentinel.metadata_2, - ] - self.dummy = [sentinel.dummy_0, sentinel.dummy_1, sentinel.dummy_2] - - def test_no_mapping(self): - self.src_coverage.metadata.extend(self.metadata) - self.tgt_coverage.metadata.extend(self.dummy) - result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) - self.assertEqual(dict(), result) - - def test_full_mapping(self): - self.src_coverage.metadata.extend(self.metadata) - self.tgt_coverage.metadata.extend(self.metadata) - dims_common = list(range(self.ndim)) - self.tgt_coverage.dims_common.extend(dims_common) - result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 0, 1: 1, 2: 2} - self.assertEqual(expected, result) - - def test_transpose_mapping(self): - self.src_coverage.metadata.extend(self.metadata[::-1]) - self.tgt_coverage.metadata.extend(self.metadata) - dims_common = list(range(self.ndim)) - self.tgt_coverage.dims_common.extend(dims_common) - result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 2, 1: 1, 2: 0} - self.assertEqual(expected, result) - - def test_partial_mapping__transposed(self): - self.src_coverage.metadata.extend(self.metadata) - self.metadata[1] = sentinel.nope - self.tgt_coverage.metadata.extend(self.metadata[::-1]) - dims_common = [0, 2] - self.tgt_coverage.dims_common.extend(dims_common) - result = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 2, 2: 0} - self.assertEqual(expected, result) - - def test_bad_metadata_mapping(self): - self.src_coverage.metadata.extend(self.metadata) - self.metadata[0] = sentinel.bad - self.tgt_coverage.metadata.extend(self.metadata) - dims_common = [0] - self.tgt_coverage.dims_common.extend(dims_common) - emsg = "Failed to map common dim coordinate metadata" - with self.assertRaisesRegex(ValueError, emsg): - _ = Resolve._dim_mapping(self.src_coverage, self.tgt_coverage) - - -class Test__aux_mapping(tests.IrisTest): - def setUp(self): - self.ndim = 3 - Wrapper = namedtuple("Wrapper", ("name",)) - cube = Wrapper(name=lambda: sentinel.name) - self.src_coverage = _AuxCoverage( - cube=cube, - common_items_aux=[], - common_items_scalar=None, - local_items_aux=None, - local_items_scalar=None, - dims_common=None, - dims_local=None, - dims_free=None, - ) - self.tgt_coverage = _AuxCoverage( - cube=cube, - common_items_aux=[], - common_items_scalar=None, - local_items_aux=None, - local_items_scalar=None, - dims_common=None, - dims_local=None, - dims_free=None, - ) - self.items = [ - _Item( - metadata=sentinel.metadata0, coord=sentinel.coord0, dims=[0] - ), - _Item( - metadata=sentinel.metadata1, coord=sentinel.coord1, dims=[1] - ), - _Item( - metadata=sentinel.metadata2, coord=sentinel.coord2, dims=[2] - ), - ] - - def _copy(self, items): - # Due to a bug in python 3.6.x, performing a deepcopy of a mock.sentinel - # will yield an object that is not equivalent to its parent, so this - # is a work-around until we drop support for python 3.6.x. - import sys - - version = sys.version_info - major, minor = version.major, version.minor - result = deepcopy(items) - if major == 3 and minor <= 6: - for i, item in enumerate(items): - result[i] = result[i]._replace(metadata=item.metadata) - return result - - def test_no_mapping(self): - result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - self.assertEqual(dict(), result) - - def test_full_mapping(self): - self.src_coverage.common_items_aux.extend(self.items) - self.tgt_coverage.common_items_aux.extend(self.items) - result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 0, 1: 1, 2: 2} - self.assertEqual(expected, result) - - def test_transpose_mapping(self): - self.src_coverage.common_items_aux.extend(self.items) - items = self._copy(self.items) - items[0].dims[0] = 2 - items[2].dims[0] = 0 - self.tgt_coverage.common_items_aux.extend(items) - result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 2, 1: 1, 2: 0} - self.assertEqual(expected, result) - - def test_partial_mapping__transposed(self): - _ = self.items.pop(1) - self.src_coverage.common_items_aux.extend(self.items) - items = self._copy(self.items) - items[0].dims[0] = 2 - items[1].dims[0] = 0 - self.tgt_coverage.common_items_aux.extend(items) - result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 2, 2: 0} - self.assertEqual(expected, result) - - def test_mapping__match_multiple_src_metadata(self): - items = self._copy(self.items) - _ = self.items.pop(1) - self.src_coverage.common_items_aux.extend(self.items) - items[1] = items[0] - self.tgt_coverage.common_items_aux.extend(items) - result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 0, 2: 2} - self.assertEqual(expected, result) - - def test_mapping__skip_match_multiple_src_metadata(self): - items = self._copy(self.items) - _ = self.items.pop(1) - self.tgt_coverage.common_items_aux.extend(self.items) - items[1] = items[0]._replace(dims=[1]) - self.src_coverage.common_items_aux.extend(items) - result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - expected = {2: 2} - self.assertEqual(expected, result) - - def test_mapping__skip_different_rank(self): - items = self._copy(self.items) - self.src_coverage.common_items_aux.extend(self.items) - items[2] = items[2]._replace(dims=[1, 2]) - self.tgt_coverage.common_items_aux.extend(items) - result = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - expected = {0: 0, 1: 1} - self.assertEqual(expected, result) - - def test_bad_metadata_mapping(self): - self.src_coverage.common_items_aux.extend(self.items) - items = self._copy(self.items) - items[0] = items[0]._replace(metadata=sentinel.bad) - self.tgt_coverage.common_items_aux.extend(items) - emsg = "Failed to map common aux coordinate metadata" - with self.assertRaisesRegex(ValueError, emsg): - _ = Resolve._aux_mapping(self.src_coverage, self.tgt_coverage) - - -class Test_mapped(tests.IrisTest): - def test_mapping_none(self): - resolve = Resolve() - self.assertIsNone(resolve.mapping) - self.assertIsNone(resolve.mapped) - - def test_mapped__src_cube_lhs(self): - resolve = Resolve() - lhs = mock.Mock(ndim=2) - rhs = mock.Mock(ndim=3) - resolve.lhs_cube = lhs - resolve.rhs_cube = rhs - resolve.map_rhs_to_lhs = False - resolve.mapping = {0: 0, 1: 1} - self.assertTrue(resolve.mapped) - - def test_mapped__src_cube_rhs(self): - resolve = Resolve() - lhs = mock.Mock(ndim=3) - rhs = mock.Mock(ndim=2) - resolve.lhs_cube = lhs - resolve.rhs_cube = rhs - resolve.map_rhs_to_lhs = True - resolve.mapping = {0: 0, 1: 1} - self.assertTrue(resolve.mapped) - - def test_partial_mapping(self): - resolve = Resolve() - lhs = mock.Mock(ndim=3) - rhs = mock.Mock(ndim=2) - resolve.lhs_cube = lhs - resolve.rhs_cube = rhs - resolve.map_rhs_to_lhs = True - resolve.mapping = {0: 0} - self.assertFalse(resolve.mapped) - - -class Test__free_mapping(tests.IrisTest): - def setUp(self): - self.Cube = namedtuple("Wrapper", ("name", "ndim", "shape")) - self.src_dim_coverage = dict( - cube=None, - metadata=None, - coords=None, - dims_common=None, - dims_local=None, - dims_free=[], - ) - self.tgt_dim_coverage = deepcopy(self.src_dim_coverage) - self.src_aux_coverage = dict( - cube=None, - common_items_aux=None, - common_items_scalar=None, - local_items_aux=None, - local_items_scalar=None, - dims_common=None, - dims_local=None, - dims_free=[], - ) - self.tgt_aux_coverage = deepcopy(self.src_aux_coverage) - self.resolve = Resolve() - self.resolve.map_rhs_to_lhs = True - self.resolve.mapping = {} - - def _make_args(self): - args = dict( - src_dim_coverage=_DimCoverage(**self.src_dim_coverage), - tgt_dim_coverage=_DimCoverage(**self.tgt_dim_coverage), - src_aux_coverage=_AuxCoverage(**self.src_aux_coverage), - tgt_aux_coverage=_AuxCoverage(**self.tgt_aux_coverage), - ) - return args - - def test_mapping_no_dims_free(self): - ndim = 4 - shape = tuple(range(ndim)) - cube = self.Cube(name=lambda: "name", ndim=ndim, shape=shape) - self.src_dim_coverage["cube"] = cube - self.tgt_dim_coverage["cube"] = cube - args = self._make_args() - emsg = "Insufficient matching coordinate metadata" - with self.assertRaisesRegex(ValueError, emsg): - self.resolve._free_mapping(**args) - - def _make_coverage(self, name, shape, dims_free): - if name == "src": - dim_coverage = self.src_dim_coverage - aux_coverage = self.src_aux_coverage - else: - dim_coverage = self.tgt_dim_coverage - aux_coverage = self.tgt_aux_coverage - ndim = len(shape) - cube = self.Cube(name=lambda: name, ndim=ndim, shape=shape) - dim_coverage["cube"] = cube - dim_coverage["dims_free"].extend(dims_free) - aux_coverage["cube"] = cube - aux_coverage["dims_free"].extend(dims_free) - - def test_mapping_src_free_to_tgt_local(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 2 3 4 - # state f l c l state f c f - # coord d d d a coord a d d - # - # src-to-tgt mapping: - # before 1->2 - # after 0->3 1->2 2->1 - src_shape = (2, 3, 4) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt_local__broadcast_src_first(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 1 3 4 - # state f l c l state f c f - # coord d d d a coord a d d - # bcast ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->3 1->2 2->1 - src_shape = (1, 3, 4) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt_local__broadcast_src_last(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 2 3 1 - # state f l c l state f c f - # coord d d d a coord a d d - # bcast ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->3 1->2 2->1 - src_shape = (2, 3, 1) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt_local__broadcast_src_both(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 1 3 1 - # state f l c l state f c f - # coord d d d a coord a d d - # bcast ^ ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->1 1->2 2->3 - src_shape = (1, 3, 1) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 1, 1: 2, 2: 3} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt_free(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 2 3 4 - # state f f c f state f c f - # coord d d d a coord a d d - # - # src-to-tgt mapping: - # before 1->2 - # after 0->0 1->2 2->1 - src_shape = (2, 3, 4) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0, 1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt_free__broadcast_src_first(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 1 3 4 - # state f f c f state f c f - # coord d d d a coord a d d - # bcast ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->0 1->2 2->1 - src_shape = (1, 3, 4) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0, 1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt_free__broadcast_src_last(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 2 3 1 - # state f f c f state f c f - # coord d d d a coord a d d - # bcast ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->0 1->2 2->1 - src_shape = (2, 3, 1) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0, 1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt_free__broadcast_src_both(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 1 3 1 - # state f f c f state f c f - # coord d d d a coord a d d - # bcast ^ ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->0 1->2 2->1 - src_shape = (1, 3, 1) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0, 1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 0, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_src_free_to_tgt__fail(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 2 3 5 - # state f f c f state f c f - # coord d d d a coord a d d - # fail ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->0 1->2 2->? - src_shape = (2, 3, 5) - src_free = [0, 2] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [0, 1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - emsg = "Insufficient matching coordinate metadata to resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): - self.resolve._free_mapping(**args) - - def test_mapping_tgt_free_to_src_local(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: -> src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 2 3 4 - # state l f c f state l c l - # coord d d d a coord a d d - # - # src-to-tgt mapping: - # before 1->2 - # after 0->3 1->2 2->1 - src_shape = (2, 3, 4) - src_free = [] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 2) - tgt_free = [1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_tgt_free_to_src_local__broadcast_tgt_first(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: -> src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 1 3 2 shape 2 3 4 - # state l f c f state l c l - # coord d d d a coord a d d - # bcast ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->3 1->2 2->1 - src_shape = (2, 3, 4) - src_free = [] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 1, 3, 2) - tgt_free = [1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_tgt_free_to_src_local__broadcast_tgt_last(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: -> src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 1 shape 2 3 4 - # state l f c f state l c l - # coord d d d a coord a d d - # bcast ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->3 1->2 2->1 - src_shape = (2, 3, 4) - src_free = [] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 1) - tgt_free = [1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 3, 1: 2, 2: 1} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_tgt_free_to_src_local__broadcast_tgt_both(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: -> src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 1 3 1 shape 2 3 4 - # state l f c f state l c l - # coord d d d a coord a d d - # bcast ^ ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->1 1->2 2->3 - src_shape = (2, 3, 4) - src_free = [] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 1, 3, 1) - tgt_free = [1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - self.resolve._free_mapping(**args) - expected = {0: 1, 1: 2, 2: 3} - self.assertEqual(expected, self.resolve.mapping) - - def test_mapping_tgt_free_to_src_no_free__fail(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: -> src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 5 shape 2 3 4 - # state l f c f state l c l - # coord d d d a coord a d d - # fail ^ - # - # src-to-tgt mapping: - # before 1->2 - # after 0->0 1->2 2->? - src_shape = (2, 3, 4) - src_free = [] - self._make_coverage("src", src_shape, src_free) - tgt_shape = (2, 4, 3, 5) - tgt_free = [1, 3] - self._make_coverage("tgt", tgt_shape, tgt_free) - self.resolve.mapping = {1: 2} - args = self._make_args() - emsg = "Insufficient matching coordinate metadata to resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): - self.resolve._free_mapping(**args) - - -class Test__src_cube(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - self.expected = sentinel.cube - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.resolve.rhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._src_cube) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.resolve.lhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._src_cube) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._src_cube - - -class Test__src_cube_position(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.assertEqual("RHS", self.resolve._src_cube_position) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.assertEqual("LHS", self.resolve._src_cube_position) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._src_cube_position - - -class Test__src_cube_resolved__getter(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - self.expected = sentinel.cube - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.resolve.rhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._src_cube_resolved) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.resolve.lhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._src_cube_resolved) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._src_cube_resolved - - -class Test__src_cube_resolved__setter(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - self.expected = sentinel.cube - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.resolve._src_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.rhs_cube_resolved) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.resolve._src_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.lhs_cube_resolved) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._src_cube_resolved = self.expected - - -class Test__tgt_cube(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - self.expected = sentinel.cube - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.resolve.rhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.resolve.lhs_cube = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._tgt_cube - - -class Test__tgt_cube_position(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.assertEqual("RHS", self.resolve._tgt_cube_position) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.assertEqual("LHS", self.resolve._tgt_cube_position) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._tgt_cube_position - - -class Test__tgt_cube_resolved__getter(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - self.expected = sentinel.cube - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.resolve.rhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube_resolved) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.resolve.lhs_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve._tgt_cube_resolved) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._tgt_cube_resolved - - -class Test__tgt_cube_resolved__setter(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - self.expected = sentinel.cube - - def test_rhs_cube(self): - self.resolve.map_rhs_to_lhs = False - self.resolve._tgt_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.rhs_cube_resolved) - - def test_lhs_cube(self): - self.resolve.map_rhs_to_lhs = True - self.resolve._tgt_cube_resolved = self.expected - self.assertEqual(self.expected, self.resolve.lhs_cube_resolved) - - def test_fail__no_map_rhs_to_lhs(self): - with self.assertRaises(AssertionError): - self.resolve._tgt_cube_resolved = self.expected - - -class Test_shape(tests.IrisTest): - def setUp(self): - self.resolve = Resolve() - - def test_no_shape(self): - self.assertIsNone(self.resolve.shape) - - def test_shape(self): - expected = sentinel.shape - self.resolve._broadcast_shape = expected - self.assertEqual(expected, self.resolve.shape) - - -class Test__as_compatible_cubes(tests.IrisTest): - def setUp(self): - self.Cube = namedtuple( - "Wrapper", - ( - "name", - "ndim", - "shape", - "metadata", - "core_data", - "coord_dims", - "dim_coords", - "aux_coords", - "aux_factories", - ), - ) - self.resolve = Resolve() - self.resolve.map_rhs_to_lhs = True - self.resolve.mapping = {} - self.mocker = self.patch("iris.cube.Cube") - self.args = dict( - name=None, - ndim=None, - shape=None, - metadata=None, - core_data=None, - coord_dims=None, - dim_coords=None, - aux_coords=None, - aux_factories=None, - ) - - def _make_cube(self, name, shape, transpose_shape=None): - self.args["name"] = lambda: name - ndim = len(shape) - self.args["ndim"] = ndim - self.args["shape"] = shape - if name == "src": - self.args["metadata"] = sentinel.metadata - self.reshape = sentinel.reshape - m_reshape = mock.Mock(return_value=self.reshape) - self.transpose = mock.Mock( - shape=transpose_shape, reshape=m_reshape - ) - m_transpose = mock.Mock(return_value=self.transpose) - self.data = mock.Mock( - shape=shape, transpose=m_transpose, reshape=m_reshape - ) - m_copy = mock.Mock(return_value=self.data) - m_core_data = mock.Mock(copy=m_copy) - self.args["core_data"] = mock.Mock(return_value=m_core_data) - self.args["coord_dims"] = mock.Mock(side_effect=([0], [ndim - 1])) - self.dim_coord = sentinel.dim_coord - self.aux_coord = sentinel.aux_coord - self.aux_factory = sentinel.aux_factory - self.args["dim_coords"] = [self.dim_coord] - self.args["aux_coords"] = [self.aux_coord] - self.args["aux_factories"] = [self.aux_factory] - cube = self.Cube(**self.args) - self.resolve.rhs_cube = cube - self.cube = mock.Mock() - self.mocker.return_value = self.cube - else: - cube = self.Cube(**self.args) - self.resolve.lhs_cube = cube - - def test_incomplete_src_to_tgt_mapping__fail(self): - src_shape = (1, 2) - self._make_cube("src", src_shape) - tgt_shape = (3, 4) - self._make_cube("tgt", tgt_shape) - with self.assertRaises(AssertionError): - self.resolve._as_compatible_cubes() - - def test_incompatible_shapes__fail(self): - # key: (state) c=common, f=free - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 2 3 4 shape 2 3 5 - # state f c c c state c c c - # fail ^ fail ^ - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - src_shape = (2, 3, 5) - self._make_cube("src", src_shape) - tgt_shape = (2, 2, 3, 4) - self._make_cube("tgt", tgt_shape) - self.resolve.mapping = {0: 1, 1: 2, 2: 3} - emsg = "Cannot resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): - self.resolve._as_compatible_cubes() - - def test_incompatible_shapes__fail_broadcast(self): - # key: (state) c=common, f=free - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 2 4 3 2 shape 2 3 5 - # state f c c c state c c c - # fail ^ fail ^ - # - # src-to-tgt mapping: - # 0->3, 1->2, 2->1 - src_shape = (2, 3, 5) - self._make_cube("src", src_shape) - tgt_shape = (2, 4, 3, 2) - self._make_cube("tgt", tgt_shape) - self.resolve.mapping = {0: 3, 1: 2, 2: 1} - emsg = "Cannot resolve cubes" - with self.assertRaisesRegex(ValueError, emsg): - self.resolve._as_compatible_cubes() - - def _check_compatible(self, broadcast_shape): - self.assertEqual( - self.resolve.lhs_cube, self.resolve._tgt_cube_resolved - ) - self.assertEqual(self.cube, self.resolve._src_cube_resolved) - self.assertEqual(broadcast_shape, self.resolve._broadcast_shape) - self.assertEqual(1, self.mocker.call_count) - self.assertEqual(self.args["metadata"], self.cube.metadata) - self.assertEqual(2, self.resolve.rhs_cube.coord_dims.call_count) - self.assertEqual( - [mock.call(self.dim_coord), mock.call(self.aux_coord)], - self.resolve.rhs_cube.coord_dims.call_args_list, - ) - self.assertEqual(1, self.cube.add_dim_coord.call_count) - self.assertEqual( - [mock.call(self.dim_coord, [self.resolve.mapping[0]])], - self.cube.add_dim_coord.call_args_list, - ) - self.assertEqual(1, self.cube.add_aux_coord.call_count) - self.assertEqual( - [mock.call(self.aux_coord, [self.resolve.mapping[2]])], - self.cube.add_aux_coord.call_args_list, - ) - self.assertEqual(1, self.cube.add_aux_factory.call_count) - self.assertEqual( - [mock.call(self.aux_factory)], - self.cube.add_aux_factory.call_args_list, - ) - - def test_compatible(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 2 - # shape 4 3 2 shape 4 3 2 - # state c c c state c c c - # coord d a - # - # src-to-tgt mapping: - # 0->0, 1->1, 2->2 - src_shape = (4, 3, 2) - self._make_cube("src", src_shape) - tgt_shape = (4, 3, 2) - self._make_cube("tgt", tgt_shape) - mapping = {0: 0, 1: 1, 2: 2} - self.resolve.mapping = mapping - self.resolve._as_compatible_cubes() - self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual([mock.call(self.data)], self.mocker.call_args_list) - - def test_compatible__transpose(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 2 - # shape 4 3 2 shape 2 3 4 - # state c c c state c c c - # coord d a - # - # src-to-tgt mapping: - # 0->2, 1->1, 2->0 - src_shape = (2, 3, 4) - self._make_cube("src", src_shape, transpose_shape=(4, 3, 2)) - tgt_shape = (4, 3, 2) - self._make_cube("tgt", tgt_shape) - mapping = {0: 2, 1: 1, 2: 0} - self.resolve.mapping = mapping - self.resolve._as_compatible_cubes() - self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual(1, self.data.transpose.call_count) - self.assertEqual( - [mock.call([2, 1, 0])], self.data.transpose.call_args_list - ) - self.assertEqual( - [mock.call(self.transpose)], self.mocker.call_args_list - ) - - def test_compatible__reshape(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 4 3 2 - # state f c c c state c c c - # coord d a - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - src_shape = (4, 3, 2) - self._make_cube("src", src_shape) - tgt_shape = (5, 4, 3, 2) - self._make_cube("tgt", tgt_shape) - mapping = {0: 1, 1: 2, 2: 3} - self.resolve.mapping = mapping - self.resolve._as_compatible_cubes() - self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual(1, self.data.reshape.call_count) - self.assertEqual( - [mock.call((1,) + src_shape)], self.data.reshape.call_args_list - ) - self.assertEqual([mock.call(self.reshape)], self.mocker.call_args_list) - - def test_compatible__transpose_reshape(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 2 3 4 - # state f c c c state c c c - # coord d a - # - # src-to-tgt mapping: - # 0->3, 1->2, 2->1 - src_shape = (2, 3, 4) - transpose_shape = (4, 3, 2) - self._make_cube("src", src_shape, transpose_shape=transpose_shape) - tgt_shape = (5, 4, 3, 2) - self._make_cube("tgt", tgt_shape) - mapping = {0: 3, 1: 2, 2: 1} - self.resolve.mapping = mapping - self.resolve._as_compatible_cubes() - self._check_compatible(broadcast_shape=tgt_shape) - self.assertEqual(1, self.data.transpose.call_count) - self.assertEqual( - [mock.call([2, 1, 0])], self.data.transpose.call_args_list - ) - self.assertEqual(1, self.data.reshape.call_count) - self.assertEqual( - [mock.call((1,) + transpose_shape)], - self.data.reshape.call_args_list, - ) - self.assertEqual([mock.call(self.reshape)], self.mocker.call_args_list) - - def test_compatible__broadcast(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 2 - # shape 1 3 2 shape 4 1 2 - # state c c c state c c c - # coord d a - # bcast ^ bcast ^ - # - # src-to-tgt mapping: - # 0->0, 1->1, 2->2 - src_shape = (4, 1, 2) - self._make_cube("src", src_shape) - tgt_shape = (1, 3, 2) - self._make_cube("tgt", tgt_shape) - mapping = {0: 0, 1: 1, 2: 2} - self.resolve.mapping = mapping - self.resolve._as_compatible_cubes() - self._check_compatible(broadcast_shape=(4, 3, 2)) - self.assertEqual([mock.call(self.data)], self.mocker.call_args_list) - - def test_compatible__broadcast_transpose_reshape(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 1 3 2 shape 2 1 4 - # state f c c c state c c c - # coord d a - # bcast ^ bcast ^ - # - # src-to-tgt mapping: - # 0->3, 1->2, 2->1 - src_shape = (2, 1, 4) - transpose_shape = (4, 1, 2) - self._make_cube("src", src_shape) - tgt_shape = (5, 1, 3, 2) - self._make_cube("tgt", tgt_shape) - mapping = {0: 3, 1: 2, 2: 1} - self.resolve.mapping = mapping - self.resolve._as_compatible_cubes() - self._check_compatible(broadcast_shape=(5, 4, 3, 2)) - self.assertEqual(1, self.data.transpose.call_count) - self.assertEqual( - [mock.call([2, 1, 0])], self.data.transpose.call_args_list - ) - self.assertEqual(1, self.data.reshape.call_count) - self.assertEqual( - [mock.call((1,) + transpose_shape)], - self.data.reshape.call_args_list, - ) - self.assertEqual([mock.call(self.reshape)], self.mocker.call_args_list) - - -class Test__metadata_mapping(tests.IrisTest): - def setUp(self): - self.ndim = sentinel.ndim - self.src_cube = mock.Mock(ndim=self.ndim) - self.src_dim_coverage = mock.Mock(dims_free=[]) - self.src_aux_coverage = mock.Mock(dims_free=[]) - self.tgt_cube = mock.Mock(ndim=self.ndim) - self.tgt_dim_coverage = mock.Mock(dims_free=[]) - self.tgt_aux_coverage = mock.Mock(dims_free=[]) - self.resolve = Resolve() - self.map_rhs_to_lhs = True - self.resolve.map_rhs_to_lhs = self.map_rhs_to_lhs - self.resolve.rhs_cube = self.src_cube - self.resolve.rhs_cube_dim_coverage = self.src_dim_coverage - self.resolve.rhs_cube_aux_coverage = self.src_aux_coverage - self.resolve.lhs_cube = self.tgt_cube - self.resolve.lhs_cube_dim_coverage = self.tgt_dim_coverage - self.resolve.lhs_cube_aux_coverage = self.tgt_aux_coverage - self.resolve.mapping = {} - self.shape = sentinel.shape - self.resolve._broadcast_shape = self.shape - self.resolve._src_cube_resolved = mock.Mock(shape=self.shape) - self.resolve._tgt_cube_resolved = mock.Mock(shape=self.shape) - self.m_dim_mapping = self.patch( - "iris.common.resolve.Resolve._dim_mapping", return_value={} - ) - self.m_aux_mapping = self.patch( - "iris.common.resolve.Resolve._aux_mapping", return_value={} - ) - self.m_free_mapping = self.patch( - "iris.common.resolve.Resolve._free_mapping" - ) - self.m_as_compatible_cubes = self.patch( - "iris.common.resolve.Resolve._as_compatible_cubes" - ) - self.mapping = {0: 1, 1: 2, 2: 3} - - def test_mapped__dim_coords(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 4 3 2 - # state f c c c state c c c - # coord d d d coord d d d - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - self.src_cube.ndim = 3 - self.m_dim_mapping.return_value = self.mapping - self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(0, self.m_aux_mapping.call_count) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) - - def test_mapped__aux_coords(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 4 3 2 - # state f c c c state c c c - # coord a a a coord a a a - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - self.src_cube.ndim = 3 - self.m_aux_mapping.return_value = self.mapping - self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) - - def test_mapped__dim_and_aux_coords(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 4 3 2 - # state f c c c state c c c - # coord d a d coord d a d - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - dim_mapping = {0: 1, 2: 3} - aux_mapping = {1: 2} - self.src_cube.ndim = 3 - self.m_dim_mapping.return_value = dim_mapping - self.m_aux_mapping.return_value = aux_mapping - self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) - - def test_mapped__dim_coords_and_free_dims(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 4 3 2 - # state l f c c state f c c - # coord d d d coord d d - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - dim_mapping = {1: 2, 2: 3} - free_mapping = {0: 1} - self.src_cube.ndim = 3 - self.m_dim_mapping.return_value = dim_mapping - side_effect = lambda a, b, c, d: self.resolve.mapping.update( - free_mapping - ) - self.m_free_mapping.side_effect = side_effect - self.resolve._metadata_mapping() - self.assertEqual(self.mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(1, self.m_free_mapping.call_count) - expected = [ - mock.call( - self.src_dim_coverage, - self.tgt_dim_coverage, - self.src_aux_coverage, - self.tgt_aux_coverage, - ) - ] - self.assertEqual(expected, self.m_free_mapping.call_args_list) - self.assertEqual(1, self.m_as_compatible_cubes.call_count) - - def test_mapped__dim_coords_with_broadcast_flip(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 4 dims 0 1 2 4 - # shape 1 4 3 2 shape 5 4 3 2 - # state c c c c state c c c c - # coord d d d d coord d d d d - # - # src-to-tgt mapping: - # 0->0, 1->1, 2->2, 3->3 - mapping = {0: 0, 1: 1, 2: 2, 3: 3} - self.src_cube.ndim = 4 - self.tgt_cube.ndim = 4 - self.m_dim_mapping.return_value = mapping - broadcast_shape = (5, 4, 3, 2) - self.resolve._broadcast_shape = broadcast_shape - self.resolve._src_cube_resolved.shape = broadcast_shape - self.resolve._tgt_cube_resolved.shape = (1, 4, 3, 2) - self.resolve._metadata_mapping() - self.assertEqual(mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(0, self.m_aux_mapping.call_count) - self.assertEqual(0, self.m_free_mapping.call_count) - self.assertEqual(2, self.m_as_compatible_cubes.call_count) - self.assertEqual(not self.map_rhs_to_lhs, self.resolve.map_rhs_to_lhs) - - def test_mapped__dim_coords_free_flip_with_free_flip(self): - # key: (state) c=common, f=free, l=local - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 2 - # shape 4 3 2 shape 4 3 2 - # state f f c state l l c - # coord d coord d d d - # - # src-to-tgt mapping: - # 0->0, 1->1, 2->2 - dim_mapping = {2: 2} - free_mapping = {0: 0, 1: 1} - mapping = {0: 0, 1: 1, 2: 2} - self.src_cube.ndim = 3 - self.tgt_cube.ndim = 3 - self.m_dim_mapping.return_value = dim_mapping - side_effect = lambda a, b, c, d: self.resolve.mapping.update( - free_mapping - ) - self.m_free_mapping.side_effect = side_effect - self.tgt_dim_coverage.dims_free = [0, 1] - self.tgt_aux_coverage.dims_free = [0, 1] - self.resolve._metadata_mapping() - self.assertEqual(mapping, self.resolve.mapping) - self.assertEqual(1, self.m_dim_mapping.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual(expected, self.m_dim_mapping.call_args_list) - self.assertEqual(1, self.m_aux_mapping.call_count) - expected = [mock.call(self.src_aux_coverage, self.tgt_aux_coverage)] - self.assertEqual(expected, self.m_aux_mapping.call_args_list) - self.assertEqual(1, self.m_free_mapping.call_count) - expected = [ - mock.call( - self.src_dim_coverage, - self.tgt_dim_coverage, - self.src_aux_coverage, - self.tgt_aux_coverage, - ) - ] - self.assertEqual(expected, self.m_free_mapping.call_args_list) - self.assertEqual(2, self.m_as_compatible_cubes.call_count) - - -class Test__prepare_common_dim_payload(tests.IrisTest): - def setUp(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 4 3 2 - # state l c c c state c c c - # coord d d d coord d d d - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - self.points = ( - sentinel.points_0, - sentinel.points_1, - sentinel.points_2, - sentinel.points_3, - ) - self.bounds = sentinel.bounds_0, sentinel.bounds_1, sentinel.bounds_2 - self.pb_0 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[0])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[0])), - ) - self.pb_1 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[1])), - None, - ) - self.pb_2 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[2])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[2])), - ) - side_effect = (self.pb_0, self.pb_1, self.pb_2) - self.m_prepare_points_and_bounds = self.patch( - "iris.common.resolve.Resolve._prepare_points_and_bounds", - side_effect=side_effect, - ) - self.resolve = Resolve() - self.resolve.prepared_category = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - self.mapping = {0: 1, 1: 2, 2: 3} - self.resolve.mapping = self.mapping - self.metadata_combined = ( - sentinel.combined_0, - sentinel.combined_1, - sentinel.combined_2, - ) - self.src_metadata = mock.Mock( - combine=mock.Mock(side_effect=self.metadata_combined) - ) - metadata = [self.src_metadata] * len(self.mapping) - self.src_coords = [ - # N.B. these need to mimic a Coord with points and bounds, and - # be of a class which is not-a-MeshCoord. - # NOTE: strictly, bounds should =above values, and support .copy(). - # For these tests, just omitting them works + is simpler. - Mock(spec=DimCoord, points=self.points[0], bounds=None), - Mock(spec=DimCoord, points=self.points[1], bounds=None), - Mock(spec=DimCoord, points=self.points[2], bounds=None), - ] - self.src_dims_common = [0, 1, 2] - self.container = DimCoord - self.src_dim_coverage = _DimCoverage( - cube=None, - metadata=metadata, - coords=self.src_coords, - dims_common=self.src_dims_common, - dims_local=[], - dims_free=[], - ) - self.tgt_metadata = [ - sentinel.tgt_metadata_0, - sentinel.tgt_metadata_1, - sentinel.tgt_metadata_2, - sentinel.tgt_metadata_3, - ] - self.tgt_coords = [ - # N.B. these need to mimic a Coord with points and bounds, and - # be of a class which is not-a-MeshCoord. - # NOTE: strictly, bounds should =above values, and support .copy(). - # For these tests, just omitting them works + is simpler. - Mock(spec=DimCoord, points=self.points[0], bounds=None), - Mock(spec=DimCoord, points=self.points[1], bounds=None), - Mock(spec=DimCoord, points=self.points[2], bounds=None), - Mock(spec=DimCoord, points=self.points[3], bounds=None), - ] - self.tgt_dims_common = [1, 2, 3] - self.tgt_dim_coverage = _DimCoverage( - cube=None, - metadata=self.tgt_metadata, - coords=self.tgt_coords, - dims_common=self.tgt_dims_common, - dims_local=[], - dims_free=[], - ) - - def _check(self, ignore_mismatch=None, bad_points=None): - if bad_points is None: - bad_points = False - self.resolve._prepare_common_dim_payload( - self.src_dim_coverage, - self.tgt_dim_coverage, - ignore_mismatch=ignore_mismatch, - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - if not bad_points: - self.assertEqual(3, len(self.resolve.prepared_category.items_dim)) - expected = [ - _PreparedItem( - metadata=_PreparedMetadata( - combined=self.metadata_combined[0], - src=self.src_metadata, - tgt=self.tgt_metadata[self.mapping[0]], - ), - points=self.points[0], - bounds=self.bounds[0], - dims=(self.mapping[0],), - container=self.container, - ), - _PreparedItem( - metadata=_PreparedMetadata( - combined=self.metadata_combined[1], - src=self.src_metadata, - tgt=self.tgt_metadata[self.mapping[1]], - ), - points=self.points[1], - bounds=None, - dims=(self.mapping[1],), - container=self.container, - ), - _PreparedItem( - metadata=_PreparedMetadata( - combined=self.metadata_combined[2], - src=self.src_metadata, - tgt=self.tgt_metadata[self.mapping[2]], - ), - points=self.points[2], - bounds=self.bounds[2], - dims=(self.mapping[2],), - container=self.container, - ), - ] - self.assertEqual( - expected, self.resolve.prepared_category.items_dim - ) - else: - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - self.assertEqual(3, self.m_prepare_points_and_bounds.call_count) - if ignore_mismatch is None: - ignore_mismatch = False - expected = [ - mock.call( - self.src_coords[0], - self.tgt_coords[self.mapping[0]], - 0, - 1, - ignore_mismatch=ignore_mismatch, - ), - mock.call( - self.src_coords[1], - self.tgt_coords[self.mapping[1]], - 1, - 2, - ignore_mismatch=ignore_mismatch, - ), - mock.call( - self.src_coords[2], - self.tgt_coords[self.mapping[2]], - 2, - 3, - ignore_mismatch=ignore_mismatch, - ), - ] - self.assertEqual( - expected, self.m_prepare_points_and_bounds.call_args_list - ) - if not bad_points: - self.assertEqual(3, self.src_metadata.combine.call_count) - expected = [ - mock.call(metadata) for metadata in self.tgt_metadata[1:] - ] - self.assertEqual( - expected, self.src_metadata.combine.call_args_list - ) - - def test__default_ignore_mismatch(self): - self._check() - - def test__not_ignore_mismatch(self): - self._check(ignore_mismatch=False) - - def test__ignore_mismatch(self): - self._check(ignore_mismatch=True) - - def test__bad_points(self): - side_effect = [(None, None)] * len(self.mapping) - self.m_prepare_points_and_bounds.side_effect = side_effect - self._check(bad_points=True) - - -class Test__prepare_common_aux_payload(tests.IrisTest): - def setUp(self): - # key: (state) c=common, f=free - # (coord) a=aux, d=dim - # - # tgt: <- src: - # dims 0 1 2 3 dims 0 1 2 - # shape 5 4 3 2 shape 4 3 2 - # state l c c c state c c c - # coord a a a coord a a a - # - # src-to-tgt mapping: - # 0->1, 1->2, 2->3 - self.points = ( - sentinel.points_0, - sentinel.points_1, - sentinel.points_2, - sentinel.points_3, - ) - self.bounds = (sentinel.bounds_0, sentinel.bounds_1, sentinel.bounds_2) - self.pb_0 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[0])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[0])), - ) - self.pb_1 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[1])), - None, - ) - self.pb_2 = ( - mock.Mock(copy=mock.Mock(return_value=self.points[2])), - mock.Mock(copy=mock.Mock(return_value=self.bounds[2])), - ) - side_effect = (self.pb_0, self.pb_1, self.pb_2) - self.m_prepare_points_and_bounds = self.patch( - "iris.common.resolve.Resolve._prepare_points_and_bounds", - side_effect=side_effect, - ) - self.resolve = Resolve() - self.resolve.prepared_category = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - self.mapping = {0: 1, 1: 2, 2: 3} - self.resolve.mapping = self.mapping - self.resolve.map_rhs_to_lhs = True - self.metadata_combined = ( - sentinel.combined_0, - sentinel.combined_1, - sentinel.combined_2, - ) - self.src_metadata = [ - mock.Mock( - combine=mock.Mock(return_value=self.metadata_combined[0]) - ), - mock.Mock( - combine=mock.Mock(return_value=self.metadata_combined[1]) - ), - mock.Mock( - combine=mock.Mock(return_value=self.metadata_combined[2]) - ), - ] - self.src_coords = [ - # N.B. these need to mimic a Coord with points and bounds, but also - # the type() defines the 'container' property of a prepared item. - # It seems that 'type()' is not fake-able in Python, so we need to - # provide *real* DimCoords, to match "self.container" below. - DimCoord(points=[0], bounds=None), - DimCoord(points=[1], bounds=None), - DimCoord(points=[2], bounds=None), - ] - self.src_dims = [(dim,) for dim in self.mapping.keys()] - self.src_common_items = [ - _Item(*item) - for item in zip(self.src_metadata, self.src_coords, self.src_dims) - ] - self.tgt_metadata = [sentinel.tgt_metadata_0] + self.src_metadata - self.tgt_coords = [ - # N.B. these need to mimic a Coord with points and bounds, but also - # the type() defines the 'container' property of a prepared item. - # It seems that 'type()' is not fake-able in Python, so we need to - # provide *real* DimCoords, to match "self.container" below. - DimCoord(points=[0], bounds=None), - DimCoord(points=[1], bounds=None), - DimCoord(points=[2], bounds=None), - DimCoord(points=[3], bounds=None), - ] - self.tgt_dims = [None] + [(dim,) for dim in self.mapping.values()] - self.tgt_common_items = [ - _Item(*item) - for item in zip(self.tgt_metadata, self.tgt_coords, self.tgt_dims) - ] - self.container = type(self.src_coords[0]) - - def _check(self, ignore_mismatch=None, bad_points=None): - if bad_points is None: - bad_points = False - prepared_items = [] - self.resolve._prepare_common_aux_payload( - self.src_common_items, - self.tgt_common_items, - prepared_items, - ignore_mismatch=ignore_mismatch, - ) - if not bad_points: - self.assertEqual(3, len(prepared_items)) - expected = [ - _PreparedItem( - metadata=_PreparedMetadata( - combined=self.metadata_combined[0], - src=self.src_metadata[0], - tgt=self.tgt_metadata[self.mapping[0]], - ), - points=self.points[0], - bounds=self.bounds[0], - dims=self.tgt_dims[self.mapping[0]], - container=self.container, - ), - _PreparedItem( - metadata=_PreparedMetadata( - combined=self.metadata_combined[1], - src=self.src_metadata[1], - tgt=self.tgt_metadata[self.mapping[1]], - ), - points=self.points[1], - bounds=None, - dims=self.tgt_dims[self.mapping[1]], - container=self.container, - ), - _PreparedItem( - metadata=_PreparedMetadata( - combined=self.metadata_combined[2], - src=self.src_metadata[2], - tgt=self.tgt_metadata[self.mapping[2]], - ), - points=self.points[2], - bounds=self.bounds[2], - dims=self.tgt_dims[self.mapping[2]], - container=self.container, - ), - ] - self.assertEqual(expected, prepared_items) - else: - self.assertEqual(0, len(prepared_items)) - self.assertEqual(3, self.m_prepare_points_and_bounds.call_count) - if ignore_mismatch is None: - ignore_mismatch = False - expected = [ - mock.call( - self.src_coords[0], - self.tgt_coords[self.mapping[0]], - self.src_dims[0], - self.tgt_dims[self.mapping[0]], - ignore_mismatch=ignore_mismatch, - ), - mock.call( - self.src_coords[1], - self.tgt_coords[self.mapping[1]], - self.src_dims[1], - self.tgt_dims[self.mapping[1]], - ignore_mismatch=ignore_mismatch, - ), - mock.call( - self.src_coords[2], - self.tgt_coords[self.mapping[2]], - self.src_dims[2], - self.tgt_dims[self.mapping[2]], - ignore_mismatch=ignore_mismatch, - ), - ] - self.assertEqual( - expected, self.m_prepare_points_and_bounds.call_args_list - ) - if not bad_points: - for src_metadata, tgt_metadata in zip( - self.src_metadata, self.tgt_metadata[1:] - ): - self.assertEqual(1, src_metadata.combine.call_count) - expected = [mock.call(tgt_metadata)] - self.assertEqual(expected, src_metadata.combine.call_args_list) - - def test__default_ignore_mismatch(self): - self._check() - - def test__not_ignore_mismatch(self): - self._check(ignore_mismatch=False) - - def test__ignore_mismatch(self): - self._check(ignore_mismatch=True) - - def test__bad_points(self): - side_effect = [(None, None)] * len(self.mapping) - self.m_prepare_points_and_bounds.side_effect = side_effect - self._check(bad_points=True) - - def test__no_tgt_metadata_match(self): - item = self.tgt_common_items[0] - tgt_common_items = [item] * len(self.tgt_common_items) - prepared_items = [] - self.resolve._prepare_common_aux_payload( - self.src_common_items, tgt_common_items, prepared_items - ) - self.assertEqual(0, len(prepared_items)) - - def test__multi_tgt_metadata_match(self): - item = self.tgt_common_items[1] - tgt_common_items = [item] * len(self.tgt_common_items) - prepared_items = [] - self.resolve._prepare_common_aux_payload( - self.src_common_items, tgt_common_items, prepared_items - ) - self.assertEqual(0, len(prepared_items)) - - -class Test__prepare_points_and_bounds(tests.IrisTest): - def setUp(self): - self.Coord = namedtuple( - "Coord", - [ - "name", - "points", - "bounds", - "metadata", - "ndim", - "shape", - "has_bounds", - ], - ) - self.Cube = namedtuple("Cube", ["name", "shape"]) - self.resolve = Resolve() - self.resolve.map_rhs_to_lhs = True - self.src_name = sentinel.src_name - self.src_points = sentinel.src_points - self.src_bounds = sentinel.src_bounds - self.src_metadata = sentinel.src_metadata - self.src_items = dict( - name=lambda: self.src_name, - points=self.src_points, - bounds=self.src_bounds, - metadata=self.src_metadata, - ndim=None, - shape=None, - has_bounds=None, - ) - self.tgt_name = sentinel.tgt_name - self.tgt_points = sentinel.tgt_points - self.tgt_bounds = sentinel.tgt_bounds - self.tgt_metadata = sentinel.tgt_metadata - self.tgt_items = dict( - name=lambda: self.tgt_name, - points=self.tgt_points, - bounds=self.tgt_bounds, - metadata=self.tgt_metadata, - ndim=None, - shape=None, - has_bounds=None, - ) - self.m_array_equal = self.patch( - "iris.util.array_equal", side_effect=(True, True) - ) - - def test_coord_ndim_unequal__tgt_ndim_greater(self): - self.src_items["ndim"] = 1 - src_coord = self.Coord(**self.src_items) - self.tgt_items["ndim"] = 10 - tgt_coord = self.Coord(**self.tgt_items) - points, bounds = self.resolve._prepare_points_and_bounds( - src_coord, tgt_coord, src_dims=None, tgt_dims=None - ) - self.assertEqual(self.tgt_points, points) - self.assertEqual(self.tgt_bounds, bounds) - - def test_coord_ndim_unequal__src_ndim_greater(self): - self.src_items["ndim"] = 10 - src_coord = self.Coord(**self.src_items) - self.tgt_items["ndim"] = 1 - tgt_coord = self.Coord(**self.tgt_items) - points, bounds = self.resolve._prepare_points_and_bounds( - src_coord, tgt_coord, src_dims=None, tgt_dims=None - ) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) - - def test_coord_ndim_equal__shape_unequal_with_src_broadcasting(self): - # key: (state) c=common, f=free - # (coord) x=coord - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 9 9 shape 1 9 - # state c c state c c - # coord x-x coord x-x - # bcast ^ - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - broadcast_shape = (9, 9) - ndim = len(broadcast_shape) - self.resolve.mapping = mapping - self.resolve._broadcast_shape = broadcast_shape - src_shape = (1, 9) - src_dims = tuple(mapping.keys()) - self.resolve.rhs_cube = self.Cube(name=None, shape=src_shape) - self.src_items["ndim"] = ndim - self.src_items["shape"] = src_shape - src_coord = self.Coord(**self.src_items) - tgt_shape = broadcast_shape - tgt_dims = tuple(mapping.values()) - self.resolve.lhs_cube = self.Cube(name=None, shape=tgt_shape) - self.tgt_items["ndim"] = ndim - self.tgt_items["shape"] = tgt_shape - tgt_coord = self.Coord(**self.tgt_items) - points, bounds = self.resolve._prepare_points_and_bounds( - src_coord, tgt_coord, src_dims, tgt_dims - ) - self.assertEqual(self.tgt_points, points) - self.assertEqual(self.tgt_bounds, bounds) - - def test_coord_ndim_equal__shape_unequal_with_tgt_broadcasting(self): - # key: (state) c=common, f=free - # (coord) x=coord - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 1 9 shape 9 9 - # state c c state c c - # coord x-x coord x-x - # bcast ^ - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - broadcast_shape = (9, 9) - ndim = len(broadcast_shape) - self.resolve.mapping = mapping - self.resolve._broadcast_shape = broadcast_shape - src_shape = broadcast_shape - src_dims = tuple(mapping.keys()) - self.resolve.rhs_cube = self.Cube(name=None, shape=src_shape) - self.src_items["ndim"] = ndim - self.src_items["shape"] = src_shape - src_coord = self.Coord(**self.src_items) - tgt_shape = (1, 9) - tgt_dims = tuple(mapping.values()) - self.resolve.lhs_cube = self.Cube(name=None, shape=tgt_shape) - self.tgt_items["ndim"] = ndim - self.tgt_items["shape"] = tgt_shape - tgt_coord = self.Coord(**self.tgt_items) - points, bounds = self.resolve._prepare_points_and_bounds( - src_coord, tgt_coord, src_dims, tgt_dims - ) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) - - def test_coord_ndim_equal__shape_unequal_with_unsupported_broadcasting( - self, - ): - # key: (state) c=common, f=free - # (coord) x=coord - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 1 9 shape 9 1 - # state c c state c c - # coord x-x coord x-x - # bcast ^ bcast ^ - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - broadcast_shape = (9, 9) - ndim = len(broadcast_shape) - self.resolve.mapping = mapping - self.resolve._broadcast_shape = broadcast_shape - src_shape = (9, 1) - src_dims = tuple(mapping.keys()) - self.resolve.rhs_cube = self.Cube( - name=lambda: sentinel.src_cube, shape=src_shape - ) - self.src_items["ndim"] = ndim - self.src_items["shape"] = src_shape - src_coord = self.Coord(**self.src_items) - tgt_shape = (1, 9) - tgt_dims = tuple(mapping.values()) - self.resolve.lhs_cube = self.Cube( - name=lambda: sentinel.tgt_cube, shape=tgt_shape - ) - self.tgt_items["ndim"] = ndim - self.tgt_items["shape"] = tgt_shape - tgt_coord = self.Coord(**self.tgt_items) - emsg = "Cannot broadcast" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve._prepare_points_and_bounds( - src_coord, tgt_coord, src_dims, tgt_dims - ) - - def _populate( - self, src_points, tgt_points, src_bounds=None, tgt_bounds=None - ): - # key: (state) c=common, f=free - # (coord) x=coord - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state f c state f c - # coord x coord x - # - # src-to-tgt mapping: - # 0->0, 1->1 - shape = (2, 3) - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - self.resolve.map_rhs_to_lhs = True - self.resolve.rhs_cube = self.Cube( - name=lambda: sentinel.src_cube, shape=None - ) - self.resolve.lhs_cube = self.Cube( - name=lambda: sentinel.tgt_cube, shape=None - ) - ndim = 1 - src_dims = 1 - self.src_items["ndim"] = ndim - self.src_items["shape"] = (shape[src_dims],) - self.src_items["points"] = src_points - self.src_items["bounds"] = src_bounds - self.src_items["has_bounds"] = lambda: src_bounds is not None - src_coord = self.Coord(**self.src_items) - tgt_dims = 1 - self.tgt_items["ndim"] = ndim - self.tgt_items["shape"] = (shape[mapping[tgt_dims]],) - self.tgt_items["points"] = tgt_points - self.tgt_items["bounds"] = tgt_bounds - self.tgt_items["has_bounds"] = lambda: tgt_bounds is not None - tgt_coord = self.Coord(**self.tgt_items) - args = dict( - src_coord=src_coord, - tgt_coord=tgt_coord, - src_dims=src_dims, - tgt_dims=tgt_dims, - ) - return args - - def test_coord_ndim_and_shape_equal__points_equal_with_no_bounds(self): - args = self._populate(self.src_points, self.src_points) - points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertIsNone(bounds) - self.assertEqual(1, self.m_array_equal.call_count) - expected = [mock.call(self.src_points, self.src_points, withnans=True)] - self.assertEqual(expected, self.m_array_equal.call_args_list) - - def test_coord_ndim_and_shape_equal__points_equal_with_src_bounds_only( - self, - ): - args = self._populate( - self.src_points, self.src_points, src_bounds=self.src_bounds - ) - points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) - self.assertEqual(1, self.m_array_equal.call_count) - expected = [mock.call(self.src_points, self.src_points, withnans=True)] - self.assertEqual(expected, self.m_array_equal.call_args_list) - - def test_coord_ndim_and_shape_equal__points_equal_with_tgt_bounds_only( - self, - ): - args = self._populate( - self.src_points, self.src_points, tgt_bounds=self.tgt_bounds - ) - points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertEqual(self.tgt_bounds, bounds) - self.assertEqual(1, self.m_array_equal.call_count) - expected = [mock.call(self.src_points, self.src_points, withnans=True)] - self.assertEqual(expected, self.m_array_equal.call_args_list) - - def test_coord_ndim_and_shape_equal__points_equal_with_src_bounds_only_strict( - self, - ): - args = self._populate( - self.src_points, self.src_points, src_bounds=self.src_bounds - ) - with LENIENT.context(maths=False): - emsg = f"Coordinate {self.src_name} has bounds" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve._prepare_points_and_bounds(**args) - - def test_coord_ndim_and_shape_equal__points_equal_with_tgt_bounds_only_strict( - self, - ): - args = self._populate( - self.src_points, self.src_points, tgt_bounds=self.tgt_bounds - ) - with LENIENT.context(maths=False): - emsg = f"Coordinate {self.tgt_name} has bounds" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve._prepare_points_and_bounds(**args) - - def test_coord_ndim_and_shape_equal__points_equal_with_bounds_equal(self): - args = self._populate( - self.src_points, - self.src_points, - src_bounds=self.src_bounds, - tgt_bounds=self.src_bounds, - ) - points, bounds = self.resolve._prepare_points_and_bounds(**args) - self.assertEqual(self.src_points, points) - self.assertEqual(self.src_bounds, bounds) - self.assertEqual(2, self.m_array_equal.call_count) - expected = [ - mock.call(self.src_points, self.src_points, withnans=True), - mock.call(self.src_bounds, self.src_bounds, withnans=True), - ] - self.assertEqual(expected, self.m_array_equal.call_args_list) - - def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different( - self, - ): - self.m_array_equal.side_effect = (True, False) - args = self._populate( - self.src_points, - self.src_points, - src_bounds=self.src_bounds, - tgt_bounds=self.tgt_bounds, - ) - emsg = f"Coordinate {self.src_name} has different bounds" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve._prepare_points_and_bounds(**args) - - def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different_ignore_mismatch( - self, - ): - self.m_array_equal.side_effect = (True, False) - args = self._populate( - self.src_points, - self.src_points, - src_bounds=self.src_bounds, - tgt_bounds=self.tgt_bounds, - ) - points, bounds = self.resolve._prepare_points_and_bounds( - **args, ignore_mismatch=True - ) - self.assertEqual(self.src_points, points) - self.assertIsNone(bounds) - self.assertEqual(2, self.m_array_equal.call_count) - expected = [ - mock.call(self.src_points, self.src_points, withnans=True), - mock.call(self.src_bounds, self.tgt_bounds, withnans=True), - ] - self.assertEqual(expected, self.m_array_equal.call_args_list) - - def test_coord_ndim_and_shape_equal__points_equal_with_bounds_different_strict( - self, - ): - self.m_array_equal.side_effect = (True, False) - args = self._populate( - self.src_points, - self.src_points, - src_bounds=self.src_bounds, - tgt_bounds=self.tgt_bounds, - ) - with LENIENT.context(maths=False): - emsg = f"Coordinate {self.src_name} has different bounds" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve._prepare_points_and_bounds(**args) - - def test_coord_ndim_and_shape_equal__points_different(self): - self.m_array_equal.side_effect = (False,) - args = self._populate(self.src_points, self.tgt_points) - emsg = f"Coordinate {self.src_name} has different points" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve._prepare_points_and_bounds(**args) - - def test_coord_ndim_and_shape_equal__points_different_ignore_mismatch( - self, - ): - self.m_array_equal.side_effect = (False,) - args = self._populate(self.src_points, self.tgt_points) - points, bounds = self.resolve._prepare_points_and_bounds( - **args, ignore_mismatch=True - ) - self.assertIsNone(points) - self.assertIsNone(bounds) - - def test_coord_ndim_and_shape_equal__points_different_strict(self): - self.m_array_equal.side_effect = (False,) - args = self._populate(self.src_points, self.tgt_points) - with LENIENT.context(maths=False): - emsg = f"Coordinate {self.src_name} has different points" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve._prepare_points_and_bounds(**args) - - -class Test__create_prepared_item(tests.IrisTest): - def setUp(self): - Coord = namedtuple("Coord", ["points", "bounds"]) - self.points_value = sentinel.points - self.points = mock.Mock(copy=mock.Mock(return_value=self.points_value)) - self.bounds_value = sentinel.bounds - self.bounds = mock.Mock(copy=mock.Mock(return_value=self.bounds_value)) - self.coord = Coord(points=self.points, bounds=self.bounds) - self.container = type(self.coord) - self.combined = sentinel.combined - self.src = mock.Mock(combine=mock.Mock(return_value=self.combined)) - self.tgt = sentinel.tgt - - def _check(self, src=None, tgt=None): - dims = 0 - if src is not None and tgt is not None: - combined = self.combined - else: - combined = src or tgt - result = Resolve._create_prepared_item( - self.coord, dims, src_metadata=src, tgt_metadata=tgt - ) - self.assertIsInstance(result, _PreparedItem) - self.assertIsInstance(result.metadata, _PreparedMetadata) - expected = _PreparedMetadata(combined=combined, src=src, tgt=tgt) - self.assertEqual(expected, result.metadata) - self.assertEqual(self.points_value, result.points) - self.assertEqual(1, self.points.copy.call_count) - self.assertEqual([mock.call()], self.points.copy.call_args_list) - self.assertEqual(self.bounds_value, result.bounds) - self.assertEqual(1, self.bounds.copy.call_count) - self.assertEqual([mock.call()], self.bounds.copy.call_args_list) - self.assertEqual((dims,), result.dims) - self.assertEqual(self.container, result.container) - - def test__no_metadata(self): - self._check() - - def test__src_metadata_only(self): - self._check(src=self.src) - - def test__tgt_metadata_only(self): - self._check(tgt=self.tgt) - - def test__combine_metadata(self): - self._check(src=self.src, tgt=self.tgt) - - -class Test__prepare_local_payload_dim(tests.IrisTest): - def setUp(self): - self.Cube = namedtuple("Cube", ["ndim"]) - self.resolve = Resolve() - self.resolve.prepared_category = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - self.resolve.map_rhs_to_lhs = True - self.src_coverage = dict( - cube=None, - metadata=[], - coords=[], - dims_common=None, - dims_local=[], - dims_free=None, - ) - self.tgt_coverage = deepcopy(self.src_coverage) - self.prepared_item = sentinel.prepared_item - self.m_create_prepared_item = self.patch( - "iris.common.resolve.Resolve._create_prepared_item", - return_value=self.prepared_item, - ) - - def test_src_no_local_with_tgt_no_local(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c c state c c - # coord d d coord d d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _DimCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - - def test_src_no_local_with_tgt_no_local__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c c state c c - # coord d d coord d d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _DimCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - - def test_src_local_with_tgt_local(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c l - # coord d d coord d d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - self.src_coverage["dims_local"] = (1,) - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["dims_local"] = (1,) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _DimCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - - def test_src_local_with_tgt_local__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c l - # coord d d coord d d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - self.src_coverage["dims_local"] = (1,) - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["dims_local"] = (1,) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _DimCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - - def test_src_local_with_tgt_free(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c f state c l - # coord d coord d d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_dim = 1 - self.src_coverage["dims_local"] = (src_dim,) - src_metadata = sentinel.src_metadata - self.src_coverage["metadata"] = [None, src_metadata] - src_coord = sentinel.src_coord - self.src_coverage["coords"] = [None, src_coord] - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _DimCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [ - mock.call(src_coord, mapping[src_dim], src_metadata=src_metadata) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_free__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c f state c l - # coord d coord d d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_dim = 1 - self.src_coverage["dims_local"] = (src_dim,) - src_metadata = sentinel.src_metadata - self.src_coverage["metadata"] = [None, src_metadata] - src_coord = sentinel.src_coord - self.src_coverage["coords"] = [None, src_coord] - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _DimCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - - def test_src_free_with_tgt_local(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c f - # coord d d coord d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_dim = 1 - self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata - self.tgt_coverage["metadata"] = [None, tgt_metadata] - tgt_coord = sentinel.tgt_coord - self.tgt_coverage["coords"] = [None, tgt_coord] - tgt_coverage = _DimCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [mock.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_free_with_tgt_local__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c f - # coord d d coord d - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_dim = 1 - self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata - self.tgt_coverage["metadata"] = [None, tgt_metadata] - tgt_coord = sentinel.tgt_coord - self.tgt_coverage["coords"] = [None, tgt_coord] - tgt_coverage = _DimCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_dim)) - - def test_src_no_local_with_tgt_local__extra_dims(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 - # shape 4 2 3 shape 2 3 - # state l c c state c c - # coord d d d coord d d - # - # src-to-tgt mapping: - # 0->1, 1->2 - mapping = {0: 1, 1: 2} - self.resolve.mapping = mapping - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=3) - tgt_dim = 0 - self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata - self.tgt_coverage["metadata"] = [tgt_metadata, None, None] - tgt_coord = sentinel.tgt_coord - self.tgt_coverage["coords"] = [tgt_coord, None, None] - tgt_coverage = _DimCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [mock.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_no_local_with_tgt_local__extra_dims_strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 - # shape 4 2 3 shape 2 3 - # state l c c state c c - # coord d d d coord d d - # - # src-to-tgt mapping: - # 0->1, 1->2 - mapping = {0: 1, 1: 2} - self.resolve.mapping = mapping - src_coverage = _DimCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=3) - tgt_dim = 0 - self.tgt_coverage["dims_local"] = (tgt_dim,) - tgt_metadata = sentinel.tgt_metadata - self.tgt_coverage["metadata"] = [tgt_metadata, None, None] - tgt_coord = sentinel.tgt_coord - self.tgt_coverage["coords"] = [tgt_coord, None, None] - tgt_coverage = _DimCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_dim(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_dim)) - self.assertEqual( - self.prepared_item, self.resolve.prepared_category.items_dim[0] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - expected = [mock.call(tgt_coord, tgt_dim, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - -class Test__prepare_local_payload_aux(tests.IrisTest): - def setUp(self): - self.Cube = namedtuple("Cube", ["ndim"]) - self.resolve = Resolve() - self.resolve.prepared_category = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - self.resolve.map_rhs_to_lhs = True - self.src_coverage = dict( - cube=None, - common_items_aux=None, - common_items_scalar=None, - local_items_aux=[], - local_items_scalar=None, - dims_common=None, - dims_local=[], - dims_free=None, - ) - self.tgt_coverage = deepcopy(self.src_coverage) - self.src_prepared_item = sentinel.src_prepared_item - self.tgt_prepared_item = sentinel.tgt_prepared_item - self.m_create_prepared_item = self.patch( - "iris.common.resolve.Resolve._create_prepared_item", - side_effect=(self.src_prepared_item, self.tgt_prepared_item), - ) - - def test_src_no_local_with_tgt_no_local(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c c state c c - # coord a a coord a a - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) - - def test_src_no_local_with_tgt_no_local__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c c state c c - # coord a a coord a a - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) - - def test_src_local_with_tgt_local(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c l - # coord a a coord a a - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_dims = (1,) - src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) - self.src_coverage["local_items_aux"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_dims = (1,) - tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) - self.tgt_coverage["local_items_aux"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(2, len(self.resolve.prepared_category.items_aux)) - expected = [self.src_prepared_item, self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [ - mock.call(src_coord, tgt_dims, src_metadata=src_metadata), - mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata), - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_local__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c l - # coord a a coord a a - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_dims = (1,) - src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) - self.src_coverage["local_items_aux"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_dims = (1,) - tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) - self.tgt_coverage["local_items_aux"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) - - def test_src_local_with_tgt_free(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c f state c l - # coord a coord a a - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_dims = (1,) - src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) - self.src_coverage["local_items_aux"].append(src_item) - self.src_coverage["dims_local"].extend(src_dims) - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) - expected = [self.src_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(src_coord, src_dims, src_metadata=src_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_free__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c f state c l - # coord a coord a a - # - # src-to-tgt mapping: - # 0->0, 1->1 - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_dims = (1,) - src_item = _Item(metadata=src_metadata, coord=src_coord, dims=src_dims) - self.src_coverage["local_items_aux"].append(src_item) - self.src_coverage["dims_local"].extend(src_dims) - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) - - def test_src_free_with_tgt_local(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c f - # coord a a coord a - # - # src-to-tgt mapping: - # 0->0, 1->1 - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_dims = (1,) - tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) - self.tgt_coverage["local_items_aux"].append(tgt_item) - self.tgt_coverage["dims_local"].extend(tgt_dims) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) - expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_free_with_tgt_local__strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 dims 0 1 - # shape 2 3 shape 2 3 - # state c l state c f - # coord a a coord a - # - # src-to-tgt mapping: - # 0->0, 1->1 - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - mapping = {0: 0, 1: 1} - self.resolve.mapping = mapping - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=2) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_dims = (1,) - tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) - self.tgt_coverage["local_items_aux"].append(tgt_item) - self.tgt_coverage["dims_local"].extend(tgt_dims) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_aux)) - - def test_src_no_local_with_tgt_local__extra_dims(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 - # shape 4 2 3 shape 2 3 - # state l c c state c c - # coord a a a coord a a - # - # src-to-tgt mapping: - # 0->1, 1->2 - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - mapping = {0: 1, 1: 2} - self.resolve.mapping = mapping - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=3) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_dims = (0,) - tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) - self.tgt_coverage["local_items_aux"].append(tgt_item) - self.tgt_coverage["dims_local"].extend(tgt_dims) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) - expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_no_local_with_tgt_local__extra_dims_strict(self): - # key: (state) c=common, f=free, l=local - # (coord) d=dim - # - # tgt: <- src: - # dims 0 1 2 dims 0 1 - # shape 4 2 3 shape 2 3 - # state l c c state c c - # coord a a a coord a a - # - # src-to-tgt mapping: - # 0->1, 1->2 - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - mapping = {0: 1, 1: 2} - self.resolve.mapping = mapping - src_coverage = _AuxCoverage(**self.src_coverage) - self.tgt_coverage["cube"] = self.Cube(ndim=3) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_dims = (0,) - tgt_item = _Item(metadata=tgt_metadata, coord=tgt_coord, dims=tgt_dims) - self.tgt_coverage["local_items_aux"].append(tgt_item) - self.tgt_coverage["dims_local"].extend(tgt_dims) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=True): - self.resolve._prepare_local_payload_aux(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_aux)) - expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_aux) - expected = [mock.call(tgt_coord, tgt_dims, tgt_metadata=tgt_metadata)] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - -class Test__prepare_local_payload_scalar(tests.IrisTest): - def setUp(self): - self.Cube = namedtuple("Cube", ["ndim"]) - self.resolve = Resolve() - self.resolve.prepared_category = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - self.src_coverage = dict( - cube=None, - common_items_aux=None, - common_items_scalar=None, - local_items_aux=None, - local_items_scalar=[], - dims_common=None, - dims_local=[], - dims_free=None, - ) - self.tgt_coverage = deepcopy(self.src_coverage) - self.src_prepared_item = sentinel.src_prepared_item - self.tgt_prepared_item = sentinel.tgt_prepared_item - self.m_create_prepared_item = self.patch( - "iris.common.resolve.Resolve._create_prepared_item", - side_effect=(self.src_prepared_item, self.tgt_prepared_item), - ) - self.src_dims = () - self.tgt_dims = () - - def test_src_no_local_with_tgt_no_local(self): - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_no_local_with_tgt_no_local__strict(self): - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_no_local_with_tgt_no_local__src_scalar_cube(self): - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_no_local_with_tgt_no_local__src_scalar_cube_strict(self): - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_local_with_tgt_no_local(self): - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) - expected = [self.src_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [ - mock.call(src_coord, self.src_dims, src_metadata=src_metadata) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_no_local__strict(self): - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_local_with_tgt_no_local__src_scalar_cube(self): - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) - expected = [self.src_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [ - mock.call(src_coord, self.src_dims, src_metadata=src_metadata) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_no_local__src_scalar_cube_strict(self): - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_no_local_with_tgt_local(self): - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) - expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [ - mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_no_local_with_tgt_local__strict(self): - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_no_local_with_tgt_local__src_scalar_cube(self): - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) - expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [ - mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_no_local_with_tgt_local__src_scalar_cube_strict(self): - self.m_create_prepared_item.side_effect = (self.tgt_prepared_item,) - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(1, len(self.resolve.prepared_category.items_scalar)) - expected = [self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [ - mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_local(self): - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) - expected = [self.src_prepared_item, self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [ - mock.call(src_coord, self.src_dims, src_metadata=src_metadata), - mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata), - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_local__strict(self): - ndim = 2 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - def test_src_local_with_tgt_local__src_scalar_cube(self): - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - self.resolve._prepare_local_payload_scalar(src_coverage, tgt_coverage) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) - expected = [self.src_prepared_item, self.tgt_prepared_item] - self.assertEqual(expected, self.resolve.prepared_category.items_scalar) - expected = [ - mock.call(src_coord, self.src_dims, src_metadata=src_metadata), - mock.call(tgt_coord, self.tgt_dims, tgt_metadata=tgt_metadata), - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_src_local_with_tgt_local__src_scalar_cube_strict(self): - ndim = 0 - self.src_coverage["cube"] = self.Cube(ndim=ndim) - src_metadata = sentinel.src_metadata - src_coord = sentinel.src_coord - src_item = _Item( - metadata=src_metadata, coord=src_coord, dims=self.src_dims - ) - self.src_coverage["local_items_scalar"].append(src_item) - src_coverage = _AuxCoverage(**self.src_coverage) - tgt_metadata = sentinel.tgt_metadata - tgt_coord = sentinel.tgt_coord - tgt_item = _Item( - metadata=tgt_metadata, coord=tgt_coord, dims=self.tgt_dims - ) - self.tgt_coverage["local_items_scalar"].append(tgt_item) - tgt_coverage = _AuxCoverage(**self.tgt_coverage) - with LENIENT.context(maths=False): - self.resolve._prepare_local_payload_scalar( - src_coverage, tgt_coverage - ) - self.assertEqual(0, len(self.resolve.prepared_category.items_scalar)) - - -class Test__prepare_local_payload(tests.IrisTest): - def test(self): - src_dim_coverage = sentinel.src_dim_coverage - src_aux_coverage = sentinel.src_aux_coverage - tgt_dim_coverage = sentinel.tgt_dim_coverage - tgt_aux_coverage = sentinel.tgt_aux_coverage - root = "iris.common.resolve.Resolve" - m_prepare_dim = self.patch(f"{root}._prepare_local_payload_dim") - m_prepare_aux = self.patch(f"{root}._prepare_local_payload_aux") - m_prepare_scalar = self.patch(f"{root}._prepare_local_payload_scalar") - resolve = Resolve() - resolve._prepare_local_payload( - src_dim_coverage, - src_aux_coverage, - tgt_dim_coverage, - tgt_aux_coverage, - ) - self.assertEqual(1, m_prepare_dim.call_count) - expected = [mock.call(src_dim_coverage, tgt_dim_coverage)] - self.assertEqual(expected, m_prepare_dim.call_args_list) - self.assertEqual(1, m_prepare_aux.call_count) - expected = [mock.call(src_aux_coverage, tgt_aux_coverage)] - self.assertEqual(expected, m_prepare_aux.call_args_list) - self.assertEqual(1, m_prepare_scalar.call_count) - expected = [mock.call(src_aux_coverage, tgt_aux_coverage)] - self.assertEqual(expected, m_prepare_scalar.call_args_list) - - -class Test__metadata_prepare(tests.IrisTest): - def setUp(self): - self.src_cube = sentinel.src_cube - self.src_category_local = sentinel.src_category_local - self.src_dim_coverage = sentinel.src_dim_coverage - self.src_aux_coverage = mock.Mock( - common_items_aux=sentinel.src_aux_coverage_common_items_aux, - common_items_scalar=sentinel.src_aux_coverage_common_items_scalar, - ) - self.tgt_cube = sentinel.tgt_cube - self.tgt_category_local = sentinel.tgt_category_local - self.tgt_dim_coverage = sentinel.tgt_dim_coverage - self.tgt_aux_coverage = mock.Mock( - common_items_aux=sentinel.tgt_aux_coverage_common_items_aux, - common_items_scalar=sentinel.tgt_aux_coverage_common_items_scalar, - ) - self.resolve = Resolve() - root = "iris.common.resolve.Resolve" - self.m_prepare_common_dim_payload = self.patch( - f"{root}._prepare_common_dim_payload" - ) - self.m_prepare_common_aux_payload = self.patch( - f"{root}._prepare_common_aux_payload" - ) - self.m_prepare_local_payload = self.patch( - f"{root}._prepare_local_payload" - ) - self.m_prepare_factory_payload = self.patch( - f"{root}._prepare_factory_payload" - ) - - def _check(self): - self.assertIsNone(self.resolve.prepared_category) - self.assertIsNone(self.resolve.prepared_factories) - self.resolve._metadata_prepare() - expected = _CategoryItems(items_dim=[], items_aux=[], items_scalar=[]) - self.assertEqual(expected, self.resolve.prepared_category) - self.assertEqual([], self.resolve.prepared_factories) - self.assertEqual(1, self.m_prepare_common_dim_payload.call_count) - expected = [mock.call(self.src_dim_coverage, self.tgt_dim_coverage)] - self.assertEqual( - expected, self.m_prepare_common_dim_payload.call_args_list - ) - self.assertEqual(2, self.m_prepare_common_aux_payload.call_count) - expected = [ - mock.call( - self.src_aux_coverage.common_items_aux, - self.tgt_aux_coverage.common_items_aux, - [], - ), - mock.call( - self.src_aux_coverage.common_items_scalar, - self.tgt_aux_coverage.common_items_scalar, - [], - ignore_mismatch=True, - ), - ] - self.assertEqual( - expected, self.m_prepare_common_aux_payload.call_args_list - ) - self.assertEqual(1, self.m_prepare_local_payload.call_count) - expected = [ - mock.call( - self.src_dim_coverage, - self.src_aux_coverage, - self.tgt_dim_coverage, - self.tgt_aux_coverage, - ) - ] - self.assertEqual(expected, self.m_prepare_local_payload.call_args_list) - self.assertEqual(2, self.m_prepare_factory_payload.call_count) - expected = [ - mock.call(self.tgt_cube, self.tgt_category_local, from_src=False), - mock.call(self.src_cube, self.src_category_local), - ] - self.assertEqual( - expected, self.m_prepare_factory_payload.call_args_list - ) - - def test_map_rhs_to_lhs__true(self): - self.resolve.map_rhs_to_lhs = True - self.resolve.rhs_cube = self.src_cube - self.resolve.rhs_cube_category_local = self.src_category_local - self.resolve.rhs_cube_dim_coverage = self.src_dim_coverage - self.resolve.rhs_cube_aux_coverage = self.src_aux_coverage - self.resolve.lhs_cube = self.tgt_cube - self.resolve.lhs_cube_category_local = self.tgt_category_local - self.resolve.lhs_cube_dim_coverage = self.tgt_dim_coverage - self.resolve.lhs_cube_aux_coverage = self.tgt_aux_coverage - self._check() - - def test_map_rhs_to_lhs__false(self): - self.resolve.map_rhs_to_lhs = False - self.resolve.lhs_cube = self.src_cube - self.resolve.lhs_cube_category_local = self.src_category_local - self.resolve.lhs_cube_dim_coverage = self.src_dim_coverage - self.resolve.lhs_cube_aux_coverage = self.src_aux_coverage - self.resolve.rhs_cube = self.tgt_cube - self.resolve.rhs_cube_category_local = self.tgt_category_local - self.resolve.rhs_cube_dim_coverage = self.tgt_dim_coverage - self.resolve.rhs_cube_aux_coverage = self.tgt_aux_coverage - self._check() - - -class Test__prepare_factory_payload(tests.IrisTest): - def setUp(self): - self.Cube = namedtuple("Cube", ["aux_factories"]) - self.Coord = namedtuple("Coord", ["metadata"]) - self.Factory_T1 = namedtuple( - "Factory_T1", ["dependencies"] - ) # dummy factory type - self.container_T1 = type(self.Factory_T1(None)) - self.Factory_T2 = namedtuple( - "Factory_T2", ["dependencies"] - ) # dummy factory type - self.container_T2 = type(self.Factory_T2(None)) - self.resolve = Resolve() - self.resolve.map_rhs_to_lhs = True - self.resolve.prepared_factories = [] - self.m_get_prepared_item = self.patch( - "iris.common.resolve.Resolve._get_prepared_item" - ) - self.category_local = sentinel.category_local - self.from_src = sentinel.from_src - - def test_no_factory(self): - cube = self.Cube(aux_factories=[]) - self.resolve._prepare_factory_payload(cube, self.category_local) - self.assertEqual(0, len(self.resolve.prepared_factories)) - - def test_skip_factory__already_prepared(self): - aux_factory = self.Factory_T1(dependencies=None) - aux_factories = [aux_factory] - cube = self.Cube(aux_factories=aux_factories) - prepared_factories = [ - _PreparedFactory(container=self.container_T1, dependencies=None), - _PreparedFactory(container=self.container_T2, dependencies=None), - ] - self.resolve.prepared_factories.extend(prepared_factories) - self.resolve._prepare_factory_payload(cube, self.category_local) - self.assertEqual(prepared_factories, self.resolve.prepared_factories) - - def test_factory__dependency_already_prepared(self): - coord_a = self.Coord(metadata=sentinel.coord_a_metadata) - coord_b = self.Coord(metadata=sentinel.coord_b_metadata) - coord_c = self.Coord(metadata=sentinel.coord_c_metadata) - side_effect = (coord_a, coord_b, coord_c) - self.m_get_prepared_item.side_effect = side_effect - dependencies = dict(name_a=coord_a, name_b=coord_b, name_c=coord_c) - aux_factory = self.Factory_T1(dependencies=dependencies) - aux_factories = [aux_factory] - cube = self.Cube(aux_factories=aux_factories) - self.resolve._prepare_factory_payload( - cube, self.category_local, from_src=self.from_src - ) - self.assertEqual(1, len(self.resolve.prepared_factories)) - prepared_dependencies = { - name: coord.metadata for name, coord in dependencies.items() - } - expected = [ - _PreparedFactory( - container=self.container_T1, dependencies=prepared_dependencies - ) - ] - self.assertEqual(expected, self.resolve.prepared_factories) - self.assertEqual(len(side_effect), self.m_get_prepared_item.call_count) - expected = [ - mock.call( - coord_a.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_b.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_c.metadata, self.category_local, from_src=self.from_src - ), - ] - actual = self.m_get_prepared_item.call_args_list - for call in expected: - self.assertIn(call, actual) - - def test_factory__dependency_local_not_prepared(self): - coord_a = self.Coord(metadata=sentinel.coord_a_metadata) - coord_b = self.Coord(metadata=sentinel.coord_b_metadata) - coord_c = self.Coord(metadata=sentinel.coord_c_metadata) - side_effect = (None, coord_a, None, coord_b, None, coord_c) - self.m_get_prepared_item.side_effect = side_effect - dependencies = dict(name_a=coord_a, name_b=coord_b, name_c=coord_c) - aux_factory = self.Factory_T1(dependencies=dependencies) - aux_factories = [aux_factory] - cube = self.Cube(aux_factories=aux_factories) - self.resolve._prepare_factory_payload( - cube, self.category_local, from_src=self.from_src - ) - self.assertEqual(1, len(self.resolve.prepared_factories)) - prepared_dependencies = { - name: coord.metadata for name, coord in dependencies.items() - } - expected = [ - _PreparedFactory( - container=self.container_T1, dependencies=prepared_dependencies - ) - ] - self.assertEqual(expected, self.resolve.prepared_factories) - self.assertEqual(len(side_effect), self.m_get_prepared_item.call_count) - expected = [ - mock.call( - coord_a.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_b.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_c.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_a.metadata, - self.category_local, - from_src=self.from_src, - from_local=True, - ), - mock.call( - coord_b.metadata, - self.category_local, - from_src=self.from_src, - from_local=True, - ), - mock.call( - coord_c.metadata, - self.category_local, - from_src=self.from_src, - from_local=True, - ), - ] - actual = self.m_get_prepared_item.call_args_list - for call in expected: - self.assertIn(call, actual) - - def test_factory__dependency_not_found(self): - coord_a = self.Coord(metadata=sentinel.coord_a_metadata) - coord_b = self.Coord(metadata=sentinel.coord_b_metadata) - coord_c = self.Coord(metadata=sentinel.coord_c_metadata) - side_effect = (None, None) - self.m_get_prepared_item.side_effect = side_effect - dependencies = dict(name_a=coord_a, name_b=coord_b, name_c=coord_c) - aux_factory = self.Factory_T1(dependencies=dependencies) - aux_factories = [aux_factory] - cube = self.Cube(aux_factories=aux_factories) - self.resolve._prepare_factory_payload( - cube, self.category_local, from_src=self.from_src - ) - self.assertEqual(0, len(self.resolve.prepared_factories)) - self.assertEqual(len(side_effect), self.m_get_prepared_item.call_count) - expected = [ - mock.call( - coord_a.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_b.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_c.metadata, self.category_local, from_src=self.from_src - ), - mock.call( - coord_a.metadata, - self.category_local, - from_src=self.from_src, - from_local=True, - ), - mock.call( - coord_b.metadata, - self.category_local, - from_src=self.from_src, - from_local=True, - ), - mock.call( - coord_c.metadata, - self.category_local, - from_src=self.from_src, - from_local=True, - ), - ] - actual = self.m_get_prepared_item.call_args_list - for call in actual: - self.assertIn(call, expected) - - -class Test__get_prepared_item(tests.IrisTest): - def setUp(self): - PreparedItem = namedtuple("PreparedItem", ["metadata"]) - self.resolve = Resolve() - self.prepared_dim_metadata_src = sentinel.prepared_dim_metadata_src - self.prepared_dim_metadata_tgt = sentinel.prepared_dim_metadata_tgt - self.prepared_items_dim = PreparedItem( - metadata=_PreparedMetadata( - combined=None, - src=self.prepared_dim_metadata_src, - tgt=self.prepared_dim_metadata_tgt, - ) - ) - self.prepared_aux_metadata_src = sentinel.prepared_aux_metadata_src - self.prepared_aux_metadata_tgt = sentinel.prepared_aux_metadata_tgt - self.prepared_items_aux = PreparedItem( - metadata=_PreparedMetadata( - combined=None, - src=self.prepared_aux_metadata_src, - tgt=self.prepared_aux_metadata_tgt, - ) - ) - self.prepared_scalar_metadata_src = ( - sentinel.prepared_scalar_metadata_src - ) - self.prepared_scalar_metadata_tgt = ( - sentinel.prepared_scalar_metadata_tgt - ) - self.prepared_items_scalar = PreparedItem( - metadata=_PreparedMetadata( - combined=None, - src=self.prepared_scalar_metadata_src, - tgt=self.prepared_scalar_metadata_tgt, - ) - ) - self.resolve.prepared_category = _CategoryItems( - items_dim=[self.prepared_items_dim], - items_aux=[self.prepared_items_aux], - items_scalar=[self.prepared_items_scalar], - ) - self.resolve.mapping = {0: 10} - self.m_create_prepared_item = self.patch( - "iris.common.resolve.Resolve._create_prepared_item" - ) - self.local_dim_metadata = sentinel.local_dim_metadata - self.local_aux_metadata = sentinel.local_aux_metadata - self.local_scalar_metadata = sentinel.local_scalar_metadata - self.local_coord = sentinel.local_coord - self.local_coord_dims = (0,) - self.local_items_dim = _Item( - metadata=self.local_dim_metadata, - coord=self.local_coord, - dims=self.local_coord_dims, - ) - self.local_items_aux = _Item( - metadata=self.local_aux_metadata, - coord=self.local_coord, - dims=self.local_coord_dims, - ) - self.local_items_scalar = _Item( - metadata=self.local_scalar_metadata, - coord=self.local_coord, - dims=self.local_coord_dims, - ) - self.category_local = _CategoryItems( - items_dim=[self.local_items_dim], - items_aux=[self.local_items_aux], - items_scalar=[self.local_items_scalar], - ) - - def test_missing_prepared_coord__from_src(self): - metadata = sentinel.missing - category_local = None - result = self.resolve._get_prepared_item(metadata, category_local) - self.assertIsNone(result) - - def test_missing_prepared_coord__from_tgt(self): - metadata = sentinel.missing - category_local = None - result = self.resolve._get_prepared_item( - metadata, category_local, from_src=False - ) - self.assertIsNone(result) - - def test_get_prepared_dim_coord__from_src(self): - metadata = self.prepared_dim_metadata_src - category_local = None - result = self.resolve._get_prepared_item(metadata, category_local) - self.assertEqual(self.prepared_items_dim, result) - - def test_get_prepared_dim_coord__from_tgt(self): - metadata = self.prepared_dim_metadata_tgt - category_local = None - result = self.resolve._get_prepared_item( - metadata, category_local, from_src=False - ) - self.assertEqual(self.prepared_items_dim, result) - - def test_get_prepared_aux_coord__from_src(self): - metadata = self.prepared_aux_metadata_src - category_local = None - result = self.resolve._get_prepared_item(metadata, category_local) - self.assertEqual(self.prepared_items_aux, result) - - def test_get_prepared_aux_coord__from_tgt(self): - metadata = self.prepared_aux_metadata_tgt - category_local = None - result = self.resolve._get_prepared_item( - metadata, category_local, from_src=False - ) - self.assertEqual(self.prepared_items_aux, result) - - def test_get_prepared_scalar_coord__from_src(self): - metadata = self.prepared_scalar_metadata_src - category_local = None - result = self.resolve._get_prepared_item(metadata, category_local) - self.assertEqual(self.prepared_items_scalar, result) - - def test_get_prepared_scalar_coord__from_tgt(self): - metadata = self.prepared_scalar_metadata_tgt - category_local = None - result = self.resolve._get_prepared_item( - metadata, category_local, from_src=False - ) - self.assertEqual(self.prepared_items_scalar, result) - - def test_missing_local_coord__from_src(self): - metadata = sentinel.missing - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_local=True - ) - self.assertIsNone(result) - - def test_missing_local_coord__from_tgt(self): - metadata = sentinel.missing - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_src=False, from_local=True - ) - self.assertIsNone(result) - - def test_get_local_dim_coord__from_src(self): - created_local_item = sentinel.created_local_item - self.m_create_prepared_item.return_value = created_local_item - metadata = self.local_dim_metadata - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_local=True - ) - expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_dim)) - self.assertEqual(expected, self.resolve.prepared_category.items_dim[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) - dims = (self.resolve.mapping[self.local_coord_dims[0]],) - expected = [ - mock.call( - self.local_coord, - dims, - src_metadata=metadata, - tgt_metadata=None, - ) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_get_local_dim_coord__from_tgt(self): - created_local_item = sentinel.created_local_item - self.m_create_prepared_item.return_value = created_local_item - metadata = self.local_dim_metadata - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_src=False, from_local=True - ) - expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_dim)) - self.assertEqual(expected, self.resolve.prepared_category.items_dim[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) - dims = self.local_coord_dims - expected = [ - mock.call( - self.local_coord, - dims, - src_metadata=None, - tgt_metadata=metadata, - ) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_get_local_aux_coord__from_src(self): - created_local_item = sentinel.created_local_item - self.m_create_prepared_item.return_value = created_local_item - metadata = self.local_aux_metadata - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_local=True - ) - expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_aux)) - self.assertEqual(expected, self.resolve.prepared_category.items_aux[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) - dims = (self.resolve.mapping[self.local_coord_dims[0]],) - expected = [ - mock.call( - self.local_coord, - dims, - src_metadata=metadata, - tgt_metadata=None, - ) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_get_local_aux_coord__from_tgt(self): - created_local_item = sentinel.created_local_item - self.m_create_prepared_item.return_value = created_local_item - metadata = self.local_aux_metadata - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_src=False, from_local=True - ) - expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_aux)) - self.assertEqual(expected, self.resolve.prepared_category.items_aux[1]) - self.assertEqual(1, self.m_create_prepared_item.call_count) - dims = self.local_coord_dims - expected = [ - mock.call( - self.local_coord, - dims, - src_metadata=None, - tgt_metadata=metadata, - ) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_get_local_scalar_coord__from_src(self): - created_local_item = sentinel.created_local_item - self.m_create_prepared_item.return_value = created_local_item - metadata = self.local_scalar_metadata - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_local=True - ) - expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) - self.assertEqual( - expected, self.resolve.prepared_category.items_scalar[1] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - dims = (self.resolve.mapping[self.local_coord_dims[0]],) - expected = [ - mock.call( - self.local_coord, - dims, - src_metadata=metadata, - tgt_metadata=None, - ) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - def test_get_local_scalar_coord__from_tgt(self): - created_local_item = sentinel.created_local_item - self.m_create_prepared_item.return_value = created_local_item - metadata = self.local_scalar_metadata - result = self.resolve._get_prepared_item( - metadata, self.category_local, from_src=False, from_local=True - ) - expected = created_local_item - self.assertEqual(expected, result) - self.assertEqual(2, len(self.resolve.prepared_category.items_scalar)) - self.assertEqual( - expected, self.resolve.prepared_category.items_scalar[1] - ) - self.assertEqual(1, self.m_create_prepared_item.call_count) - dims = self.local_coord_dims - expected = [ - mock.call( - self.local_coord, - dims, - src_metadata=None, - tgt_metadata=metadata, - ) - ] - self.assertEqual(expected, self.m_create_prepared_item.call_args_list) - - -class Test_cube(tests.IrisTest): - def setUp(self): - self.shape = (2, 3) - self.data = np.zeros(np.multiply(*self.shape), dtype=np.int8).reshape( - self.shape - ) - self.bad_data = np.zeros(np.multiply(*self.shape), dtype=np.int8) - self.resolve = Resolve() - self.resolve.map_rhs_to_lhs = True - self.resolve._broadcast_shape = self.shape - self.cube_metadata = CubeMetadata( - standard_name="air_temperature", - long_name="air temp", - var_name="airT", - units=Unit("K"), - attributes={}, - cell_methods=(), - ) - lhs_cube = Cube(self.data) - lhs_cube.metadata = self.cube_metadata - self.resolve.lhs_cube = lhs_cube - rhs_cube = Cube(self.data) - rhs_cube.metadata = self.cube_metadata - self.resolve.rhs_cube = rhs_cube - self.m_add_dim_coord = self.patch("iris.cube.Cube.add_dim_coord") - self.m_add_aux_coord = self.patch("iris.cube.Cube.add_aux_coord") - self.m_add_aux_factory = self.patch("iris.cube.Cube.add_aux_factory") - self.m_coord = self.patch("iris.cube.Cube.coord") - # - # prepared coordinates - # - prepared_category = _CategoryItems( - items_dim=[], items_aux=[], items_scalar=[] - ) - # prepared dim coordinates - self.prepared_dim_0_metadata = _PreparedMetadata( - combined=sentinel.prepared_dim_0_metadata_combined, - src=None, - tgt=None, - ) - self.prepared_dim_0_points = sentinel.prepared_dim_0_points - self.prepared_dim_0_bounds = sentinel.prepared_dim_0_bounds - self.prepared_dim_0_dims = (0,) - self.prepared_dim_0_coord = mock.Mock(metadata=None) - self.prepared_dim_0_container = mock.Mock( - return_value=self.prepared_dim_0_coord - ) - self.prepared_dim_0 = _PreparedItem( - metadata=self.prepared_dim_0_metadata, - points=self.prepared_dim_0_points, - bounds=self.prepared_dim_0_bounds, - dims=self.prepared_dim_0_dims, - container=self.prepared_dim_0_container, - ) - prepared_category.items_dim.append(self.prepared_dim_0) - self.prepared_dim_1_metadata = _PreparedMetadata( - combined=sentinel.prepared_dim_1_metadata_combined, - src=None, - tgt=None, - ) - self.prepared_dim_1_points = sentinel.prepared_dim_1_points - self.prepared_dim_1_bounds = sentinel.prepared_dim_1_bounds - self.prepared_dim_1_dims = (1,) - self.prepared_dim_1_coord = mock.Mock(metadata=None) - self.prepared_dim_1_container = mock.Mock( - return_value=self.prepared_dim_1_coord - ) - self.prepared_dim_1 = _PreparedItem( - metadata=self.prepared_dim_1_metadata, - points=self.prepared_dim_1_points, - bounds=self.prepared_dim_1_bounds, - dims=self.prepared_dim_1_dims, - container=self.prepared_dim_1_container, - ) - prepared_category.items_dim.append(self.prepared_dim_1) - - # prepared auxiliary coordinates - self.prepared_aux_0_metadata = _PreparedMetadata( - combined=sentinel.prepared_aux_0_metadata_combined, - src=None, - tgt=None, - ) - self.prepared_aux_0_points = sentinel.prepared_aux_0_points - self.prepared_aux_0_bounds = sentinel.prepared_aux_0_bounds - self.prepared_aux_0_dims = (0,) - self.prepared_aux_0_coord = mock.Mock(metadata=None) - self.prepared_aux_0_container = mock.Mock( - return_value=self.prepared_aux_0_coord - ) - self.prepared_aux_0 = _PreparedItem( - metadata=self.prepared_aux_0_metadata, - points=self.prepared_aux_0_points, - bounds=self.prepared_aux_0_bounds, - dims=self.prepared_aux_0_dims, - container=self.prepared_aux_0_container, - ) - prepared_category.items_aux.append(self.prepared_aux_0) - self.prepared_aux_1_metadata = _PreparedMetadata( - combined=sentinel.prepared_aux_1_metadata_combined, - src=None, - tgt=None, - ) - self.prepared_aux_1_points = sentinel.prepared_aux_1_points - self.prepared_aux_1_bounds = sentinel.prepared_aux_1_bounds - self.prepared_aux_1_dims = (1,) - self.prepared_aux_1_coord = mock.Mock(metadata=None) - self.prepared_aux_1_container = mock.Mock( - return_value=self.prepared_aux_1_coord - ) - self.prepared_aux_1 = _PreparedItem( - metadata=self.prepared_aux_1_metadata, - points=self.prepared_aux_1_points, - bounds=self.prepared_aux_1_bounds, - dims=self.prepared_aux_1_dims, - container=self.prepared_aux_1_container, - ) - prepared_category.items_aux.append(self.prepared_aux_1) - - # prepare scalar coordinates - self.prepared_scalar_0_metadata = _PreparedMetadata( - combined=sentinel.prepared_scalar_0_metadata_combined, - src=None, - tgt=None, - ) - self.prepared_scalar_0_points = sentinel.prepared_scalar_0_points - self.prepared_scalar_0_bounds = sentinel.prepared_scalar_0_bounds - self.prepared_scalar_0_dims = () - self.prepared_scalar_0_coord = mock.Mock(metadata=None) - self.prepared_scalar_0_container = mock.Mock( - return_value=self.prepared_scalar_0_coord - ) - self.prepared_scalar_0 = _PreparedItem( - metadata=self.prepared_scalar_0_metadata, - points=self.prepared_scalar_0_points, - bounds=self.prepared_scalar_0_bounds, - dims=self.prepared_scalar_0_dims, - container=self.prepared_scalar_0_container, - ) - prepared_category.items_scalar.append(self.prepared_scalar_0) - self.prepared_scalar_1_metadata = _PreparedMetadata( - combined=sentinel.prepared_scalar_1_metadata_combined, - src=None, - tgt=None, - ) - self.prepared_scalar_1_points = sentinel.prepared_scalar_1_points - self.prepared_scalar_1_bounds = sentinel.prepared_scalar_1_bounds - self.prepared_scalar_1_dims = () - self.prepared_scalar_1_coord = mock.Mock(metadata=None) - self.prepared_scalar_1_container = mock.Mock( - return_value=self.prepared_scalar_1_coord - ) - self.prepared_scalar_1 = _PreparedItem( - metadata=self.prepared_scalar_1_metadata, - points=self.prepared_scalar_1_points, - bounds=self.prepared_scalar_1_bounds, - dims=self.prepared_scalar_1_dims, - container=self.prepared_scalar_1_container, - ) - prepared_category.items_scalar.append(self.prepared_scalar_1) - # - # prepared factories - # - prepared_factories = [] - self.aux_factory = sentinel.aux_factory - self.prepared_factory_container = mock.Mock( - return_value=self.aux_factory - ) - self.prepared_factory_metadata_a = _PreparedMetadata( - combined=sentinel.prepared_factory_metadata_a_combined, - src=None, - tgt=None, - ) - self.prepared_factory_metadata_b = _PreparedMetadata( - combined=sentinel.prepared_factory_metadata_b_combined, - src=None, - tgt=None, - ) - self.prepared_factory_metadata_c = _PreparedMetadata( - combined=sentinel.prepared_factory_metadata_c_combined, - src=None, - tgt=None, - ) - self.prepared_factory_dependencies = dict( - name_a=self.prepared_factory_metadata_a, - name_b=self.prepared_factory_metadata_b, - name_c=self.prepared_factory_metadata_c, - ) - self.prepared_factory = _PreparedFactory( - container=self.prepared_factory_container, - dependencies=self.prepared_factory_dependencies, - ) - prepared_factories.append(self.prepared_factory) - self.prepared_factory_side_effect = ( - sentinel.prepared_factory_coord_a, - sentinel.prepared_factory_coord_b, - sentinel.prepared_factory_coord_c, - ) - self.m_coord.side_effect = self.prepared_factory_side_effect - self.resolve.prepared_category = prepared_category - self.resolve.prepared_factories = prepared_factories - - # Required to stop mock 'containers' failing in an 'issubclass' call. - self.patch( - "iris.common.resolve.issubclass", mock.Mock(return_value=False) - ) - - def test_no_resolved_shape(self): - self.resolve._broadcast_shape = None - data = None - emsg = "Cannot resolve resultant cube, as no candidate cubes have been provided" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve.cube(data) - - def test_bad_data_shape(self): - emsg = "Cannot resolve resultant cube, as the provided data must have shape" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve.cube(self.bad_data) - - def test_bad_data_shape__inplace(self): - self.resolve.lhs_cube = Cube(self.bad_data) - emsg = "Cannot resolve resultant cube in-place" - with self.assertRaisesRegex(ValueError, emsg): - _ = self.resolve.cube(self.data, in_place=True) - - def _check(self): - # check dim coordinate 0 - self.assertEqual(1, self.prepared_dim_0.container.call_count) - expected = [ - mock.call( - self.prepared_dim_0_points, bounds=self.prepared_dim_0_bounds - ) - ] - self.assertEqual( - expected, self.prepared_dim_0.container.call_args_list - ) - self.assertEqual( - self.prepared_dim_0_coord.metadata, - self.prepared_dim_0_metadata.combined, - ) - # check dim coordinate 1 - self.assertEqual(1, self.prepared_dim_1.container.call_count) - expected = [ - mock.call( - self.prepared_dim_1_points, bounds=self.prepared_dim_1_bounds - ) - ] - self.assertEqual( - expected, self.prepared_dim_1.container.call_args_list - ) - self.assertEqual( - self.prepared_dim_1_coord.metadata, - self.prepared_dim_1_metadata.combined, - ) - # check add_dim_coord - self.assertEqual(2, self.m_add_dim_coord.call_count) - expected = [ - mock.call(self.prepared_dim_0_coord, self.prepared_dim_0_dims), - mock.call(self.prepared_dim_1_coord, self.prepared_dim_1_dims), - ] - self.assertEqual(expected, self.m_add_dim_coord.call_args_list) - - # check aux coordinate 0 - self.assertEqual(1, self.prepared_aux_0.container.call_count) - expected = [ - mock.call( - self.prepared_aux_0_points, bounds=self.prepared_aux_0_bounds - ) - ] - self.assertEqual( - expected, self.prepared_aux_0.container.call_args_list - ) - self.assertEqual( - self.prepared_aux_0_coord.metadata, - self.prepared_aux_0_metadata.combined, - ) - # check aux coordinate 1 - self.assertEqual(1, self.prepared_aux_1.container.call_count) - expected = [ - mock.call( - self.prepared_aux_1_points, bounds=self.prepared_aux_1_bounds - ) - ] - self.assertEqual( - expected, self.prepared_aux_1.container.call_args_list - ) - self.assertEqual( - self.prepared_aux_1_coord.metadata, - self.prepared_aux_1_metadata.combined, - ) - # check scalar coordinate 0 - self.assertEqual(1, self.prepared_scalar_0.container.call_count) - expected = [ - mock.call( - self.prepared_scalar_0_points, - bounds=self.prepared_scalar_0_bounds, - ) - ] - self.assertEqual( - expected, self.prepared_scalar_0.container.call_args_list - ) - self.assertEqual( - self.prepared_scalar_0_coord.metadata, - self.prepared_scalar_0_metadata.combined, - ) - # check scalar coordinate 1 - self.assertEqual(1, self.prepared_scalar_1.container.call_count) - expected = [ - mock.call( - self.prepared_scalar_1_points, - bounds=self.prepared_scalar_1_bounds, - ) - ] - self.assertEqual( - expected, self.prepared_scalar_1.container.call_args_list - ) - self.assertEqual( - self.prepared_scalar_1_coord.metadata, - self.prepared_scalar_1_metadata.combined, - ) - # check add_aux_coord - self.assertEqual(4, self.m_add_aux_coord.call_count) - expected = [ - mock.call(self.prepared_aux_0_coord, self.prepared_aux_0_dims), - mock.call(self.prepared_aux_1_coord, self.prepared_aux_1_dims), - mock.call( - self.prepared_scalar_0_coord, self.prepared_scalar_0_dims - ), - mock.call( - self.prepared_scalar_1_coord, self.prepared_scalar_1_dims - ), - ] - self.assertEqual(expected, self.m_add_aux_coord.call_args_list) - - # check auxiliary factories - self.assertEqual(1, self.m_add_aux_factory.call_count) - expected = [mock.call(self.aux_factory)] - self.assertEqual(expected, self.m_add_aux_factory.call_args_list) - self.assertEqual(1, self.prepared_factory_container.call_count) - expected = [ - mock.call( - **{ - name: value - for name, value in zip( - sorted(self.prepared_factory_dependencies.keys()), - self.prepared_factory_side_effect, - ) - } - ) - ] - self.assertEqual( - expected, self.prepared_factory_container.call_args_list - ) - self.assertEqual(3, self.m_coord.call_count) - expected = [ - mock.call(self.prepared_factory_metadata_a.combined), - mock.call(self.prepared_factory_metadata_b.combined), - mock.call(self.prepared_factory_metadata_c.combined), - ] - self.assertEqual(expected, self.m_coord.call_args_list) - - def test_resolve(self): - result = self.resolve.cube(self.data) - self.assertEqual(self.cube_metadata, result.metadata) - self._check() - self.assertIsNot(self.resolve.lhs_cube, result) - - def test_resolve__inplace(self): - result = self.resolve.cube(self.data, in_place=True) - self.assertEqual(self.cube_metadata, result.metadata) - self._check() - self.assertIs(self.resolve.lhs_cube, result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/concatenate/__init__.py b/lib/iris/tests/unit/concatenate/__init__.py deleted file mode 100644 index cf671a6553..0000000000 --- a/lib/iris/tests/unit/concatenate/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._concatenate` package.""" diff --git a/lib/iris/tests/unit/concatenate/test__CubeSignature.py b/lib/iris/tests/unit/concatenate/test__CubeSignature.py deleted file mode 100644 index cc20cdfa1f..0000000000 --- a/lib/iris/tests/unit/concatenate/test__CubeSignature.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test class :class:`iris._concatenate._CubeSignature`.""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -from cf_units import Unit -import numpy as np - -from iris._concatenate import _CubeSignature as CubeSignature -from iris.coords import DimCoord -from iris.cube import Cube -from iris.util import new_axis - - -class Test__coordinate_dim_metadata_equality(tests.IrisTest): - def setUp(self): - nt = 10 - data = np.arange(nt, dtype=np.float32) - cube = Cube(data, standard_name="air_temperature", units="K") - # Temporal coordinate. - t_units = Unit("hours since 1970-01-01 00:00:00", calendar="standard") - t_coord = DimCoord( - points=np.arange(nt), standard_name="time", units=t_units - ) - cube.add_dim_coord(t_coord, 0) - - # Increasing 1D time-series cube. - self.series_inc_cube = cube - self.series_inc = CubeSignature(self.series_inc_cube) - - # Decreasing 1D time-series cube. - self.series_dec_cube = self.series_inc_cube.copy() - self.series_dec_cube.remove_coord("time") - t_tmp = DimCoord( - points=t_coord.points[::-1], standard_name="time", units=t_units - ) - self.series_dec_cube.add_dim_coord(t_tmp, 0) - self.series_dec = CubeSignature(self.series_dec_cube) - - # Scalar 0D time-series cube with scalar time coordinate. - cube = Cube(0, standard_name="air_temperature", units="K") - cube.add_aux_coord( - DimCoord(points=nt, standard_name="time", units=t_units) - ) - self.scalar_cube = cube - - def test_scalar_non_common_axis(self): - scalar = CubeSignature(self.scalar_cube) - self.assertNotEqual(self.series_inc.dim_metadata, scalar.dim_metadata) - self.assertNotEqual(self.series_dec.dim_metadata, scalar.dim_metadata) - - def test_1d_single_value_common_axis(self): - # Manually promote scalar time cube to be a 1d cube. - single = CubeSignature(new_axis(self.scalar_cube, "time")) - self.assertEqual(self.series_inc.dim_metadata, single.dim_metadata) - self.assertEqual(self.series_dec.dim_metadata, single.dim_metadata) - - def test_increasing_common_axis(self): - series_inc = self.series_inc - series_dec = self.series_dec - self.assertEqual(series_inc.dim_metadata, series_inc.dim_metadata) - self.assertNotEqual(series_inc.dim_metadata, series_dec.dim_metadata) - - def test_decreasing_common_axis(self): - series_inc = self.series_inc - series_dec = self.series_dec - self.assertNotEqual(series_dec.dim_metadata, series_inc.dim_metadata) - self.assertEqual(series_dec.dim_metadata, series_dec.dim_metadata) - - def test_circular(self): - series_inc = self.series_inc - circular_cube = self.series_inc_cube.copy() - circular_cube.coord("time").circular = True - circular = CubeSignature(circular_cube) - self.assertNotEqual(circular.dim_metadata, series_inc.dim_metadata) - self.assertEqual(circular.dim_metadata, circular.dim_metadata) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/concatenate/test_concatenate.py b/lib/iris/tests/unit/concatenate/test_concatenate.py deleted file mode 100644 index 96d13d7d15..0000000000 --- a/lib/iris/tests/unit/concatenate/test_concatenate.py +++ /dev/null @@ -1,358 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._concatenate.concatenate.py`.""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -import cf_units -import numpy as np -import numpy.ma as ma - -from iris._concatenate import concatenate -from iris._lazy_data import as_lazy_data -import iris.coords -import iris.cube -from iris.exceptions import ConcatenateError - - -class TestEpoch(tests.IrisTest): - def simple_1d_time_cubes(self, reftimes, coords_points): - cubes = [] - data_points = [273, 275, 278, 277, 274] - for reftime, coord_points in zip(reftimes, coords_points): - cube = iris.cube.Cube( - np.array(data_points, dtype=np.float32), - standard_name="air_temperature", - units="K", - ) - unit = cf_units.Unit(reftime, calendar="standard") - coord = iris.coords.DimCoord( - points=np.array(coord_points, dtype=np.float32), - standard_name="time", - units=unit, - ) - cube.add_dim_coord(coord, 0) - cubes.append(cube) - return cubes - - def test_concat_1d_with_same_time_units(self): - reftimes = [ - "hours since 1970-01-01 00:00:00", - "hours since 1970-01-01 00:00:00", - ] - coords_points = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]] - cubes = self.simple_1d_time_cubes(reftimes, coords_points) - result = concatenate(cubes) - self.assertEqual(len(result), 1) - self.assertEqual(result[0].shape, (10,)) - - -class TestMessages(tests.IrisTest): - def setUp(self): - data = np.arange(24, dtype=np.float32).reshape(2, 3, 4) - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - # Time coord - t_unit = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar="standard" - ) - t_coord = iris.coords.DimCoord( - points=np.arange(2, dtype=np.float32), - standard_name="time", - units=t_unit, - ) - cube.add_dim_coord(t_coord, 0) - # Lats and lons - x_coord = iris.coords.DimCoord( - points=np.arange(3, dtype=np.float32), - standard_name="longitude", - units="degrees", - ) - cube.add_dim_coord(x_coord, 1) - y_coord = iris.coords.DimCoord( - points=np.arange(4, dtype=np.float32), - standard_name="latitude", - units="degrees", - ) - cube.add_dim_coord(y_coord, 2) - # Scalars - cube.add_aux_coord(iris.coords.AuxCoord([0], "height", units="m")) - # Aux Coords - cube.add_aux_coord( - iris.coords.AuxCoord(data, long_name="wibble", units="1"), - data_dims=(0, 1, 2), - ) - cube.add_aux_coord( - iris.coords.AuxCoord([0, 1, 2], long_name="foo", units="1"), - data_dims=(1,), - ) - cube.add_cell_measure( - iris.coords.CellMeasure([0, 1, 2], long_name="bar", units="1"), - data_dims=(1,), - ) - cube.add_ancillary_variable( - iris.coords.AncillaryVariable( - [0, 1, 2], long_name="baz", units="1" - ), - data_dims=(1,), - ) - self.cube = cube - - def test_definition_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.units = "1" - exc_regexp = "Cube metadata differs for phenomenon: *" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_dimensions_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_coord("latitude") - exc_regexp = "Dimension coordinates differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_dimensions_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.coord("latitude").long_name = "bob" - exc_regexp = "Dimension coordinates metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_aux_coords_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_coord("foo") - exc_regexp = "Auxiliary coordinates differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_aux_coords_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.coord("foo").units = "m" - exc_regexp = "Auxiliary coordinates metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_scalar_coords_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_coord("height") - exc_regexp = "Scalar coordinates differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_scalar_coords_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.coord("height").long_name = "alice" - exc_regexp = "Scalar coordinates values or metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_cell_measure_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_cell_measure("bar") - exc_regexp = "Cell measures differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_cell_measure_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.cell_measure("bar").units = "m" - exc_regexp = "Cell measures metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_ancillary_variable_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.remove_ancillary_variable("baz") - exc_regexp = "Ancillary variables differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_ancillary_variable_metadata_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.ancillary_variable("baz").units = "m" - exc_regexp = "Ancillary variables metadata differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_ndim_difference_message(self): - cube_1 = self.cube - cube_2 = iris.cube.Cube( - np.arange(5, dtype=np.float32), - standard_name="air_temperature", - units="K", - ) - x_coord = iris.coords.DimCoord( - points=np.arange(5, dtype=np.float32), - standard_name="longitude", - units="degrees", - ) - cube_2.add_dim_coord(x_coord, 0) - exc_regexp = "Data dimensions differ: [0-9] != [0-9]" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - def test_datatype_difference_message(self): - cube_1 = self.cube - cube_2 = cube_1.copy() - cube_2.data.dtype = np.float64 - exc_regexp = "Data types differ: .* != .*" - with self.assertRaisesRegex(ConcatenateError, exc_regexp): - _ = concatenate([cube_1, cube_2], True) - - -class TestOrder(tests.IrisTest): - def _make_cube(self, points, bounds=None): - nx = 4 - data = np.arange(len(points) * nx).reshape(len(points), nx) - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - lat = iris.coords.DimCoord(points, "latitude", bounds=bounds) - lon = iris.coords.DimCoord(np.arange(nx), "longitude") - cube.add_dim_coord(lat, 0) - cube.add_dim_coord(lon, 1) - return cube - - def test_asc_points(self): - top = self._make_cube([10, 30, 50, 70, 90]) - bottom = self._make_cube([-90, -70, -50, -30, -10]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_asc_bounds(self): - top = self._make_cube([22.5, 67.5], [[0, 45], [45, 90]]) - bottom = self._make_cube([-67.5, -22.5], [[-90, -45], [-45, 0]]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_asc_points_with_singleton_ordered(self): - top = self._make_cube([5]) - bottom = self._make_cube([15, 25]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_asc_points_with_singleton_unordered(self): - top = self._make_cube([25]) - bottom = self._make_cube([5, 15]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_asc_bounds_with_singleton_ordered(self): - top = self._make_cube([5], [[0, 10]]) - bottom = self._make_cube([15, 25], [[10, 20], [20, 30]]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_asc_bounds_with_singleton_unordered(self): - top = self._make_cube([25], [[20, 30]]) - bottom = self._make_cube([5, 15], [[0, 10], [10, 20]]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_desc_points(self): - top = self._make_cube([90, 70, 50, 30, 10]) - bottom = self._make_cube([-10, -30, -50, -70, -90]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_desc_bounds(self): - top = self._make_cube([67.5, 22.5], [[90, 45], [45, 0]]) - bottom = self._make_cube([-22.5, -67.5], [[0, -45], [-45, -90]]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_desc_points_with_singleton_ordered(self): - top = self._make_cube([25]) - bottom = self._make_cube([15, 5]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_desc_points_with_singleton_unordered(self): - top = self._make_cube([5]) - bottom = self._make_cube([25, 15]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_desc_bounds_with_singleton_ordered(self): - top = self._make_cube([25], [[30, 20]]) - bottom = self._make_cube([15, 5], [[20, 10], [10, 0]]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_desc_bounds_with_singleton_unordered(self): - top = self._make_cube([5], [[10, 0]]) - bottom = self._make_cube([25, 15], [[30, 20], [20, 10]]) - result = concatenate([top, bottom]) - self.assertEqual(len(result), 1) - - def test_points_all_singleton(self): - top = self._make_cube([5]) - bottom = self._make_cube([15]) - result1 = concatenate([top, bottom]) - result2 = concatenate([bottom, top]) - self.assertEqual(len(result1), 1) - self.assertEqual(len(result2), 1) - self.assertEqual(result1, result2) - - def test_asc_bounds_all_singleton(self): - top = self._make_cube([5], [0, 10]) - bottom = self._make_cube([15], [10, 20]) - result1 = concatenate([top, bottom]) - result2 = concatenate([bottom, top]) - self.assertEqual(len(result1), 1) - self.assertEqual(len(result2), 1) - self.assertEqual(result1, result2) - - def test_desc_bounds_all_singleton(self): - top = self._make_cube([5], [10, 0]) - bottom = self._make_cube([15], [20, 10]) - result1 = concatenate([top, bottom]) - result2 = concatenate([bottom, top]) - self.assertEqual(len(result1), 1) - self.assertEqual(len(result2), 1) - self.assertEqual(result1, result2) - - -class TestConcatenate__dask(tests.IrisTest): - def build_lazy_cube(self, points, bounds=None, nx=4): - data = np.arange(len(points) * nx).reshape(len(points), nx) - data = as_lazy_data(data) - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - lat = iris.coords.DimCoord(points, "latitude", bounds=bounds) - lon = iris.coords.DimCoord(np.arange(nx), "longitude") - cube.add_dim_coord(lat, 0) - cube.add_dim_coord(lon, 1) - return cube - - def test_lazy_concatenate(self): - c1 = self.build_lazy_cube([1, 2]) - c2 = self.build_lazy_cube([3, 4, 5]) - (cube,) = concatenate([c1, c2]) - self.assertTrue(cube.has_lazy_data()) - self.assertFalse(ma.isMaskedArray(cube.data)) - - def test_lazy_concatenate_masked_array_mixed_deferred(self): - c1 = self.build_lazy_cube([1, 2]) - c2 = self.build_lazy_cube([3, 4, 5]) - c2.data = np.ma.masked_greater(c2.data, 3) - (cube,) = concatenate([c1, c2]) - self.assertTrue(cube.has_lazy_data()) - self.assertTrue(ma.isMaskedArray(cube.data)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/config/__init__.py b/lib/iris/tests/unit/config/__init__.py deleted file mode 100644 index 38806c7db8..0000000000 --- a/lib/iris/tests/unit/config/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.config` module.""" diff --git a/lib/iris/tests/unit/config/test_NetCDF.py b/lib/iris/tests/unit/config/test_NetCDF.py deleted file mode 100644 index c7f7564e4e..0000000000 --- a/lib/iris/tests/unit/config/test_NetCDF.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.config.NetCDF` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import warnings - -import iris.config - - -class Test(tests.IrisTest): - def setUp(self): - self.options = iris.config.NetCDF() - - def test_basic(self): - self.assertFalse(self.options.conventions_override) - - def test_enabled(self): - self.options.conventions_override = True - self.assertTrue(self.options.conventions_override) - - def test_bad_value(self): - # A bad value should be ignored and replaced with the default value. - bad_value = "wibble" - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - self.options.conventions_override = bad_value - self.assertFalse(self.options.conventions_override) - exp_wmsg = "Attempting to set invalid value {!r}".format(bad_value) - self.assertRegex(str(w[0].message), exp_wmsg) - - def test__contextmgr(self): - with self.options.context(conventions_override=True): - self.assertTrue(self.options.conventions_override) - self.assertFalse(self.options.conventions_override) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/constraints/__init__.py b/lib/iris/tests/unit/constraints/__init__.py deleted file mode 100644 index 03a987b1a1..0000000000 --- a/lib/iris/tests/unit/constraints/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._constraints` module.""" diff --git a/lib/iris/tests/unit/constraints/test_Constraint_equality.py b/lib/iris/tests/unit/constraints/test_Constraint_equality.py deleted file mode 100644 index 01e61b70a7..0000000000 --- a/lib/iris/tests/unit/constraints/test_Constraint_equality.py +++ /dev/null @@ -1,274 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for equality testing of different constraint types.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris._constraints import AttributeConstraint, Constraint, NameConstraint - - -class Test_Constraint__hash__(tests.IrisTest): - def test_empty(self): - c1 = Constraint() - c2 = Constraint() - self.assertEqual(hash(c1), hash(c1)) - self.assertNotEqual(hash(c1), hash(c2)) - - -class Test_Constraint__eq__(tests.IrisTest): - def test_empty_same(self): - c1 = Constraint() - c2 = Constraint() - self.assertEqual(c1, c2) - self.assertIsNot(c1, c2) - - def test_emptyname_same(self): - c1 = Constraint("") - c2 = Constraint("") - self.assertEqual(c1, c2) - - def test_empty_emptyname_differ(self): - c1 = Constraint() - c2 = Constraint("") - self.assertNotEqual(c1, c2) - - def test_names_same(self): - c1 = Constraint("a") - c2 = Constraint("a") - self.assertEqual(c1, c2) - - def test_names_differ(self): - c1 = Constraint("a") - c2 = Constraint("b") - self.assertNotEqual(c1, c2) - - def test_funcs_same(self): - # *Same* functions match - def func(cube): - return False - - c1 = Constraint(cube_func=func) - c2 = Constraint(cube_func=func) - self.assertEqual(c1, c2) - - def test_funcs_differ(self): - # Identical but different funcs do not match. - c1 = Constraint(cube_func=lambda c: False) - c2 = Constraint(cube_func=lambda c: False) - self.assertNotEqual(c1, c2) - - def test_coord_names_same(self): - c1 = Constraint(some_coordname=3) - c2 = Constraint(some_coordname=3) - self.assertEqual(c1, c2) - - def test_coord_names_differ(self): - c1 = Constraint(some_coordname_A=3) - c2 = Constraint(some_coordname_B=3) - self.assertNotEqual(c1, c2) - - def test_coord_values_differ(self): - c1 = Constraint(some_coordname=3) - c2 = Constraint(some_coordname=4) - self.assertNotEqual(c1, c2) - - def test_coord_orders_differ(self): - # We *could* maybe ignore Coordinate order, but at present we don't. - c1 = Constraint(coordname_1=1, coordname_2=2) - c2 = Constraint(coordname_2=2, coordname_1=1) - self.assertNotEqual(c1, c2) - - def test_coord_values_functions_same(self): - def func(coord): - return False - - c1 = Constraint(some_coordname=func) - c2 = Constraint(some_coordname=func) - self.assertEqual(c1, c2) - - def test_coord_values_functions_differ(self): - # Identical functions are not the same. - c1 = Constraint(some_coordname=lambda c: True) - c2 = Constraint(some_coordname=lambda c: True) - self.assertNotEqual(c1, c2) - - def test_coord_values_and_keys_same(self): - # **kwargs and 'coord_values=' are combined without distinction. - c1 = Constraint(coord_values={"a": [2, 3]}) - c2 = Constraint(a=[2, 3]) - self.assertEqual(c1, c2) - - -class Test_AttributeConstraint__hash__(tests.IrisTest): - def test_empty(self): - c1 = AttributeConstraint() - c2 = AttributeConstraint() - self.assertEqual(hash(c1), hash(c1)) - self.assertNotEqual(hash(c1), hash(c2)) - - -class Test_AttributeConstraint__eq__(tests.IrisTest): - def test_empty_same(self): - c1 = AttributeConstraint() - c2 = AttributeConstraint() - self.assertEqual(c1, c2) - self.assertIsNot(c1, c2) - - def test_attribute_plain_empty_diff(self): - c1 = AttributeConstraint() - c2 = Constraint() - self.assertNotEqual(c1, c2) - - def test_names_same(self): - c1 = AttributeConstraint(a=1) - c2 = AttributeConstraint(a=1) - self.assertEqual(c1, c2) - - def test_names_diff(self): - c1 = AttributeConstraint(a=1) - c2 = AttributeConstraint(a=1, b=1) - self.assertNotEqual(c1, c2) - - def test_values_diff(self): - c1 = AttributeConstraint(a=1, b=1) - c2 = AttributeConstraint(a=1, b=2) - self.assertNotEqual(c1, c2) - - def test_func_same(self): - def func(attrs): - return False - - c1 = AttributeConstraint(a=func) - c2 = AttributeConstraint(a=func) - self.assertEqual(c1, c2) - - def test_func_diff(self): - c1 = AttributeConstraint(a=lambda a: False) - c2 = AttributeConstraint(a=lambda a: False) - self.assertNotEqual(c1, c2) - - -class Test_NameConstraint__hash__(tests.IrisTest): - def test_empty(self): - c1 = NameConstraint() - c2 = NameConstraint() - self.assertEqual(hash(c1), hash(c1)) - self.assertNotEqual(hash(c1), hash(c2)) - - -class Test_NameConstraint__eq__(tests.IrisTest): - def test_empty_same(self): - c1 = NameConstraint() - c2 = NameConstraint() - self.assertEqual(c1, c2) - self.assertIsNot(c1, c2) - - def test_attribute_plain_empty_diff(self): - c1 = NameConstraint() - c2 = Constraint() - self.assertNotEqual(c1, c2) - - def test_names_same(self): - c1 = NameConstraint(standard_name="air_temperature") - c2 = NameConstraint(standard_name="air_temperature") - self.assertEqual(c1, c2) - - def test_full_same(self): - c1 = NameConstraint( - standard_name="air_temperature", - long_name="temp", - var_name="tair", - STASH="m01s02i003", - ) - c2 = NameConstraint( - standard_name="air_temperature", - long_name="temp", - var_name="tair", - STASH="m01s02i003", - ) - self.assertEqual(c1, c2) - - def test_missing_diff(self): - c1 = NameConstraint(standard_name="air_temperature", var_name="tair") - c2 = NameConstraint(standard_name="air_temperature") - self.assertNotEqual(c1, c2) - - def test_standard_name_diff(self): - c1 = NameConstraint(standard_name="air_temperature") - c2 = NameConstraint(standard_name="height") - self.assertNotEqual(c1, c2) - - def test_long_name_diff(self): - c1 = NameConstraint(long_name="temp") - c2 = NameConstraint(long_name="t3") - self.assertNotEqual(c1, c2) - - def test_var_name_diff(self): - c1 = NameConstraint(var_name="tair") - c2 = NameConstraint(var_name="xxx") - self.assertNotEqual(c1, c2) - - def test_stash_diff(self): - c1 = NameConstraint(STASH="m01s02i003") - c2 = NameConstraint(STASH="m01s02i777") - self.assertNotEqual(c1, c2) - - def test_func_same(self): - def func(name): - return True - - c1 = NameConstraint(STASH="m01s02i003", long_name=func) - c2 = NameConstraint(STASH="m01s02i003", long_name=func) - self.assertEqual(c1, c2) - - def test_func_diff(self): - c1 = NameConstraint(STASH="m01s02i003", long_name=lambda n: True) - c2 = NameConstraint(STASH="m01s02i003", long_name=lambda n: True) - self.assertNotEqual(c1, c2) - - -class Test_ConstraintCombination__hash__(tests.IrisTest): - def test_empty(self): - c1 = Constraint() & Constraint() - c2 = Constraint() & Constraint() - self.assertEqual(hash(c1), hash(c1)) - self.assertNotEqual(hash(c1), hash(c2)) - - def test_identical_construction(self): - c1, c2 = Constraint(a=1), Constraint(b=1) - cc1 = c1 & c2 - cc2 = c1 & c2 - self.assertNotEqual(hash(cc1), hash(cc2)) - - -class Test_ConstraintCombination__eq__(tests.IrisTest): - def test_empty_same(self): - c1 = Constraint() & Constraint() - c2 = Constraint() & Constraint() - self.assertEqual(c1, c2) - self.assertIsNot(c1, c2) - - def test_multi_components_same(self): - c1 = Constraint("a") & Constraint(b=1) - c2 = Constraint("a") & Constraint(b=1) - self.assertEqual(c1, c2) - - def test_multi_components_diff(self): - c1 = Constraint("a") & Constraint(b=1, c=2) - c2 = Constraint("a") & Constraint(b=1) - self.assertNotEqual(c1, c2) - - def test_different_component_order(self): - c1, c2 = Constraint("a"), Constraint(b=1) - cc1 = c1 & c2 - cc2 = c2 & c1 - self.assertNotEqual(cc1, cc2) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/constraints/test_NameConstraint.py b/lib/iris/tests/unit/constraints/test_NameConstraint.py deleted file mode 100644 index 46aea25331..0000000000 --- a/lib/iris/tests/unit/constraints/test_NameConstraint.py +++ /dev/null @@ -1,229 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris._constraints.NameConstraint` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest.mock import Mock, sentinel - -from iris._constraints import NameConstraint - - -class Test___init__(tests.IrisTest): - def setUp(self): - self.default = "none" - - def test_default(self): - constraint = NameConstraint() - self.assertEqual(constraint.standard_name, self.default) - self.assertEqual(constraint.long_name, self.default) - self.assertEqual(constraint.var_name, self.default) - self.assertEqual(constraint.STASH, self.default) - - def test_standard_name(self): - standard_name = sentinel.standard_name - constraint = NameConstraint(standard_name=standard_name) - self.assertEqual(constraint.standard_name, standard_name) - constraint = NameConstraint(standard_name=standard_name) - self.assertEqual(constraint.standard_name, standard_name) - - def test_long_name(self): - long_name = sentinel.long_name - constraint = NameConstraint(long_name=long_name) - self.assertEqual(constraint.standard_name, self.default) - self.assertEqual(constraint.long_name, long_name) - constraint = NameConstraint(standard_name=None, long_name=long_name) - self.assertIsNone(constraint.standard_name) - self.assertEqual(constraint.long_name, long_name) - - def test_var_name(self): - var_name = sentinel.var_name - constraint = NameConstraint(var_name=var_name) - self.assertEqual(constraint.standard_name, self.default) - self.assertEqual(constraint.long_name, self.default) - self.assertEqual(constraint.var_name, var_name) - constraint = NameConstraint( - standard_name=None, long_name=None, var_name=var_name - ) - self.assertIsNone(constraint.standard_name) - self.assertIsNone(constraint.long_name) - self.assertEqual(constraint.var_name, var_name) - - def test_STASH(self): - STASH = sentinel.STASH - constraint = NameConstraint(STASH=STASH) - self.assertEqual(constraint.standard_name, self.default) - self.assertEqual(constraint.long_name, self.default) - self.assertEqual(constraint.var_name, self.default) - self.assertEqual(constraint.STASH, STASH) - constraint = NameConstraint( - standard_name=None, long_name=None, var_name=None, STASH=STASH - ) - self.assertIsNone(constraint.standard_name) - self.assertIsNone(constraint.long_name) - self.assertIsNone(constraint.var_name) - self.assertEqual(constraint.STASH, STASH) - - -class Test__cube_func(tests.IrisTest): - def setUp(self): - self.standard_name = sentinel.standard_name - self.long_name = sentinel.long_name - self.var_name = sentinel.var_name - self.STASH = sentinel.STASH - self.cube = Mock( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - attributes=dict(STASH=self.STASH), - ) - - def test_standard_name(self): - # Match. - constraint = NameConstraint(standard_name=self.standard_name) - self.assertTrue(constraint._cube_func(self.cube)) - # Match. - constraint = NameConstraint(standard_name=self.standard_name) - self.assertTrue(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint(standard_name="wibble") - self.assertFalse(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint(standard_name="wibble") - self.assertFalse(constraint._cube_func(self.cube)) - - def test_long_name(self): - # Match. - constraint = NameConstraint(long_name=self.long_name) - self.assertTrue(constraint._cube_func(self.cube)) - # Match. - constraint = NameConstraint( - standard_name=self.standard_name, long_name=self.long_name - ) - self.assertTrue(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint(long_name=None) - self.assertFalse(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint( - standard_name=None, long_name=self.long_name - ) - self.assertFalse(constraint._cube_func(self.cube)) - - def test_var_name(self): - # Match. - constraint = NameConstraint(var_name=self.var_name) - self.assertTrue(constraint._cube_func(self.cube)) - # Match. - constraint = NameConstraint( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - ) - self.assertTrue(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint(var_name=None) - self.assertFalse(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint( - standard_name=None, long_name=None, var_name=self.var_name - ) - self.assertFalse(constraint._cube_func(self.cube)) - - def test_STASH(self): - # Match. - constraint = NameConstraint(STASH=self.STASH) - self.assertTrue(constraint._cube_func(self.cube)) - # Match. - constraint = NameConstraint( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - STASH=self.STASH, - ) - self.assertTrue(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint(STASH=None) - self.assertFalse(constraint._cube_func(self.cube)) - # No match. - constraint = NameConstraint( - standard_name=None, long_name=None, var_name=None, STASH=self.STASH - ) - self.assertFalse(constraint._cube_func(self.cube)) - - -class Test___repr__(tests.IrisTest): - def setUp(self): - self.standard_name = sentinel.standard_name - self.long_name = sentinel.long_name - self.var_name = sentinel.var_name - self.STASH = sentinel.STASH - self.msg = "NameConstraint({})" - self.f_standard_name = "standard_name={!r}".format(self.standard_name) - self.f_long_name = "long_name={!r}".format(self.long_name) - self.f_var_name = "var_name={!r}".format(self.var_name) - self.f_STASH = "STASH={!r}".format(self.STASH) - - def test(self): - constraint = NameConstraint() - expected = self.msg.format("") - self.assertEqual(repr(constraint), expected) - - def test_standard_name(self): - constraint = NameConstraint(standard_name=self.standard_name) - expected = self.msg.format(self.f_standard_name) - self.assertEqual(repr(constraint), expected) - - def test_long_name(self): - constraint = NameConstraint(long_name=self.long_name) - expected = self.msg.format(self.f_long_name) - self.assertEqual(repr(constraint), expected) - constraint = NameConstraint( - standard_name=self.standard_name, long_name=self.long_name - ) - args = "{}, {}".format(self.f_standard_name, self.f_long_name) - expected = self.msg.format(args) - self.assertEqual(repr(constraint), expected) - - def test_var_name(self): - constraint = NameConstraint(var_name=self.var_name) - expected = self.msg.format(self.f_var_name) - self.assertEqual(repr(constraint), expected) - constraint = NameConstraint( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - ) - args = "{}, {}, {}".format( - self.f_standard_name, self.f_long_name, self.f_var_name - ) - expected = self.msg.format(args) - self.assertEqual(repr(constraint), expected) - - def test_STASH(self): - constraint = NameConstraint(STASH=self.STASH) - expected = self.msg.format(self.f_STASH) - self.assertEqual(repr(constraint), expected) - constraint = NameConstraint( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - STASH=self.STASH, - ) - args = "{}, {}, {}, {}".format( - self.f_standard_name, - self.f_long_name, - self.f_var_name, - self.f_STASH, - ) - expected = self.msg.format(args) - self.assertEqual(repr(constraint), expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_categorisation/__init__.py b/lib/iris/tests/unit/coord_categorisation/__init__.py deleted file mode 100644 index 18fe8f2482..0000000000 --- a/lib/iris/tests/unit/coord_categorisation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.coord_categorisation` module.""" diff --git a/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py b/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py deleted file mode 100644 index 0c20f16f5a..0000000000 --- a/lib/iris/tests/unit/coord_categorisation/test_add_categorised_coord.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.coord_categorisation.add_categorised_coord`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import CALENDARS as calendars -from cf_units import Unit -import numpy as np - -from iris.coord_categorisation import add_categorised_coord, add_day_of_year -from iris.coords import DimCoord -from iris.cube import Cube - - -class Test_add_categorised_coord(tests.IrisTest): - def setUp(self): - # Factor out common variables and objects. - self.cube = mock.Mock(name="cube", coords=mock.Mock(return_value=[])) - self.coord = mock.Mock( - name="coord", points=np.arange(12).reshape(3, 4) - ) - self.units = "units" - self.vectorised = mock.Mock(name="vectorized_result") - - def test_vectorise_call(self): - # Check that the function being passed through gets called with - # numpy.vectorize, before being applied to the points array. - # The reason we use numpy.vectorize is to support multi-dimensional - # coordinate points. - def fn(coord, v): - return v**2 - - with mock.patch( - "numpy.vectorize", return_value=self.vectorised - ) as vectorise_patch: - with mock.patch("iris.coords.AuxCoord") as aux_coord_constructor: - add_categorised_coord( - self.cube, "foobar", self.coord, fn, units=self.units - ) - - # Check the constructor of AuxCoord gets called with the - # appropriate arguments. - # Start with the vectorised function. - vectorise_patch.assert_called_once_with(fn) - # Check the vectorize wrapper gets called with the appropriate args. - self.vectorised.assert_called_once_with(self.coord, self.coord.points) - # Check the AuxCoord constructor itself. - aux_coord_constructor.assert_called_once_with( - self.vectorised(self.coord, self.coord.points), - units=self.units, - attributes=self.coord.attributes.copy(), - ) - # And check adding the aux coord to the cube mock. - self.cube.add_aux_coord.assert_called_once_with( - aux_coord_constructor(), self.cube.coord_dims(self.coord) - ) - - def test_string_vectorised(self): - # Check that special case handling of a vectorized string returning - # function is taking place. - def fn(coord, v): - return "0123456789"[:v] - - with mock.patch( - "numpy.vectorize", return_value=self.vectorised - ) as vectorise_patch: - with mock.patch("iris.coords.AuxCoord") as aux_coord_constructor: - add_categorised_coord( - self.cube, "foobar", self.coord, fn, units=self.units - ) - - self.assertEqual( - aux_coord_constructor.call_args[0][0], - vectorise_patch(fn, otypes=[object])( - self.coord, self.coord.points - ).astype("|S64"), - ) - - -class Test_add_day_of_year(tests.IrisTest): - def setUp(self): - self.expected = { - "standard": np.array(list(range(360, 367)) + list(range(1, 4))), - "gregorian": np.array(list(range(360, 367)) + list(range(1, 4))), - "proleptic_gregorian": np.array( - list(range(360, 367)) + list(range(1, 4)) - ), - "noleap": np.array(list(range(359, 366)) + list(range(1, 4))), - "julian": np.array(list(range(360, 367)) + list(range(1, 4))), - "all_leap": np.array(list(range(360, 367)) + list(range(1, 4))), - "365_day": np.array(list(range(359, 366)) + list(range(1, 4))), - "366_day": np.array(list(range(360, 367)) + list(range(1, 4))), - "360_day": np.array(list(range(355, 361)) + list(range(1, 5))), - } - - def make_cube(self, calendar): - n_times = 10 - cube = Cube(np.arange(n_times)) - time_coord = DimCoord( - np.arange(n_times), - standard_name="time", - units=Unit("days since 1980-12-25", calendar=calendar), - ) - cube.add_dim_coord(time_coord, 0) - return cube - - def test_calendars(self): - for calendar in calendars: - # Skip the Julian calendar due to - # https://github.com/Unidata/netcdftime/issues/13 - # Remove this if block once the issue is resolved. - if calendar == "julian": - continue - cube = self.make_cube(calendar) - add_day_of_year(cube, "time") - points = cube.coord("day_of_year").points - expected_points = self.expected[calendar] - msg = "Test failed for the following calendar: {}." - self.assertArrayEqual( - points, expected_points, err_msg=msg.format(calendar) - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_categorisation/test_add_hour.py b/lib/iris/tests/unit/coord_categorisation/test_add_hour.py deleted file mode 100644 index 418ac72557..0000000000 --- a/lib/iris/tests/unit/coord_categorisation/test_add_hour.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test coordinate categorisation function add_hour. -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import cf_units -import numpy as np - -import iris -import iris.coord_categorisation as ccat - - -class Test_add_hour(tests.IrisTest): - def setUp(self): - # make a series of 'hour numbers' for the time - hour_numbers = np.arange(0, 200, 5, dtype=np.int32) - - # use hour numbers as data values also (don't actually use this for - # anything) - cube = iris.cube.Cube( - hour_numbers, long_name="test cube", units="metres" - ) - - time_coord = iris.coords.DimCoord( - hour_numbers, - standard_name="time", - units=cf_units.Unit("hours since epoch", "standard"), - ) - cube.add_dim_coord(time_coord, 0) - - self.hour_numbers = hour_numbers - self.cube = cube - self.time_coord = time_coord - - def test_bad_coord(self): - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): - ccat.add_hour(self.cube, "DOES NOT EXIST", name="my_hour") - - def test_explicit_result_name_specify_coord_by_name(self): - coord_name = "my_hour" - msg = "Missing/incorrectly named result for add_hour" - - # Specify source coordinate by name - cube = self.cube - ccat.add_hour(cube, "time", name=coord_name) - result_coords = cube.coords(coord_name) - self.assertEqual(len(result_coords), 1, msg) - - def test_explicit_result_name_specify_coord_by_reference(self): - coord_name = "my_hour" - msg = "Missing/incorrectly named result for add_hour" - - # Specify source coordinate by coordinate reference - cube = self.cube - time = cube.coord("time") - ccat.add_hour(cube, time, name=coord_name) - result_coords = cube.coords(coord_name) - self.assertEqual(len(result_coords), 1, msg) - - def test_basic(self): - coord_name = "my_hour" - cube = self.cube - time_coord = self.time_coord - expected_coord = iris.coords.AuxCoord( - self.hour_numbers % 24, long_name=coord_name, units="1" - ) - - ccat.add_hour(cube, time_coord, coord_name) - - self.assertEqual(cube.coord(coord_name), expected_coord) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/__init__.py b/lib/iris/tests/unit/coord_systems/__init__.py deleted file mode 100644 index 39d4d25f73..0000000000 --- a/lib/iris/tests/unit/coord_systems/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.coord_systems` module.""" diff --git a/lib/iris/tests/unit/coord_systems/test_AlbersEqualArea.py b/lib/iris/tests/unit/coord_systems/test_AlbersEqualArea.py deleted file mode 100644 index 99a7c9f59b..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_AlbersEqualArea.py +++ /dev/null @@ -1,152 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.coord_systems.AlbersEqualArea` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import AlbersEqualArea, GeogCS - - -class Test_as_cartopy_crs(tests.IrisTest): - def setUp(self): - self.latitude_of_projection_origin = 0.0 - self.longitude_of_central_meridian = 0.0 - self.semi_major_axis = 6377563.396 - self.semi_minor_axis = 6356256.909 - self.false_easting = 0.0 - self.false_northing = 0.0 - self.standard_parallels = (-18.0, -36.0) - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.aea_cs = AlbersEqualArea( - self.latitude_of_projection_origin, - self.longitude_of_central_meridian, - self.false_easting, - self.false_northing, - self.standard_parallels, - ellipsoid=self.ellipsoid, - ) - - def test_crs_creation(self): - res = self.aea_cs.as_cartopy_crs() - globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - expected = ccrs.AlbersEqualArea( - self.longitude_of_central_meridian, - self.latitude_of_projection_origin, - self.false_easting, - self.false_northing, - self.standard_parallels, - globe=globe, - ) - self.assertEqual(res, expected) - - def test_fail_too_few_parallels(self): - emsg = "parallels" - with self.assertRaisesRegex(ValueError, emsg): - AlbersEqualArea(standard_parallels=()) - - def test_fail_too_many_parallels(self): - emsg = "parallels" - with self.assertRaisesRegex(ValueError, emsg): - AlbersEqualArea(standard_parallels=(1, 2, 3)) - - -class Test_as_cartopy_projection(tests.IrisTest): - def setUp(self): - self.latitude_of_projection_origin = 0.0 - self.longitude_of_central_meridian = 0.0 - self.semi_major_axis = 6377563.396 - self.semi_minor_axis = 6356256.909 - self.false_easting = 0.0 - self.false_northing = 0.0 - self.standard_parallels = (-18.0, -36.0) - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.aea_cs = AlbersEqualArea( - self.latitude_of_projection_origin, - self.longitude_of_central_meridian, - self.false_easting, - self.false_northing, - self.standard_parallels, - ellipsoid=self.ellipsoid, - ) - - def test_projection_creation(self): - res = self.aea_cs.as_cartopy_projection() - globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - expected = ccrs.AlbersEqualArea( - self.latitude_of_projection_origin, - self.longitude_of_central_meridian, - self.false_easting, - self.false_northing, - self.standard_parallels, - globe=globe, - ) - self.assertEqual(res, expected) - - -class Test_init_defaults(tests.IrisTest): - def test_set_optional_args(self): - # Check that setting optional arguments works as expected. - crs = AlbersEqualArea( - longitude_of_central_meridian=123, - latitude_of_projection_origin=-17, - false_easting=100, - false_northing=-200, - standard_parallels=(-37, 21.4), - ) - - self.assertEqualAndKind(crs.longitude_of_central_meridian, 123.0) - self.assertEqualAndKind(crs.latitude_of_projection_origin, -17.0) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -200.0) - self.assertEqual(len(crs.standard_parallels), 2) - self.assertEqualAndKind(crs.standard_parallels[0], -37.0) - self.assertEqualAndKind(crs.standard_parallels[1], 21.4) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.longitude_of_central_meridian, 0.0) - self.assertEqualAndKind(crs.latitude_of_projection_origin, 0.0) - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - self.assertEqual(len(crs.standard_parallels), 2) - self.assertEqualAndKind(crs.standard_parallels[0], 20.0) - self.assertEqualAndKind(crs.standard_parallels[1], 50.0) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = AlbersEqualArea() - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = AlbersEqualArea( - longitude_of_central_meridian=None, - latitude_of_projection_origin=None, - standard_parallels=None, - false_easting=None, - false_northing=None, - ) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_GeogCS.py b/lib/iris/tests/unit/coord_systems/test_GeogCS.py deleted file mode 100644 index f3f9531dbb..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_GeogCS.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.GeogCS` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coord_systems import GeogCS - - -class Test_init_defaults(tests.IrisTest): - # NOTE: most of the testing for GeogCS is in the legacy test module - # 'iris.tests.test_coordsystem'. - # This class *only* tests the defaults for optional constructor args. - - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) argument works. - crs = GeogCS(1.0, longitude_of_prime_meridian=-85) - self.assertEqualAndKind(crs.longitude_of_prime_meridian, -85.0) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - radius = float(crs.semi_major_axis) - self.assertEqualAndKind(crs.semi_major_axis, radius) # just the kind - self.assertEqualAndKind(crs.semi_minor_axis, radius) - self.assertEqualAndKind(crs.inverse_flattening, 0.0) - self.assertEqualAndKind(crs.longitude_of_prime_meridian, 0.0) - - def test_no_optional_args(self): - # Check expected properties with no optional args. - crs = GeogCS(1.0) - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected properties with optional args=None. - crs = GeogCS( - 1.0, - semi_minor_axis=None, - inverse_flattening=None, - longitude_of_prime_meridian=None, - ) - self._check_crs_defaults(crs) - - def test_zero_inverse_flattening_on_perfect_sphere(self): - # allow inverse_flattening to be 0 for a perfect sphere - # i.e. semi-major axis defined, semi-minor is None. - crs = GeogCS( - 1.0, - semi_minor_axis=None, - inverse_flattening=0.0, - longitude_of_prime_meridian=None, - ) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_Geostationary.py b/lib/iris/tests/unit/coord_systems/test_Geostationary.py deleted file mode 100644 index cc3c8384db..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_Geostationary.py +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.Geostationary` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import GeogCS, Geostationary - - -class Test(tests.IrisTest): - def setUp(self): - # Set everything to non-default values. - self.latitude_of_projection_origin = 0 # For now, Cartopy needs =0. - self.longitude_of_projection_origin = 123.0 - self.perspective_point_height = 9999.0 - self.sweep_angle_axis = "x" - self.false_easting = 100.0 - self.false_northing = -200.0 - - self.semi_major_axis = 4000.0 - self.semi_minor_axis = 3900.0 - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - - # Actual and expected coord system can be re-used for - # Geostationary.test_crs_creation and test_projection_creation. - self.expected = ccrs.Geostationary( - central_longitude=self.longitude_of_projection_origin, - satellite_height=self.perspective_point_height, - false_easting=self.false_easting, - false_northing=self.false_northing, - globe=self.globe, - sweep_axis=self.sweep_angle_axis, - ) - self.geo_cs = Geostationary( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - self.perspective_point_height, - self.sweep_angle_axis, - self.false_easting, - self.false_northing, - self.ellipsoid, - ) - - def test_crs_creation(self): - res = self.geo_cs.as_cartopy_crs() - self.assertEqual(res, self.expected) - - def test_projection_creation(self): - res = self.geo_cs.as_cartopy_projection() - self.assertEqual(res, self.expected) - - def test_non_zero_lat(self): - with self.assertRaisesRegex(ValueError, "Non-zero latitude"): - Geostationary( - 0.1, - self.longitude_of_projection_origin, - self.perspective_point_height, - self.sweep_angle_axis, - self.false_easting, - self.false_northing, - self.ellipsoid, - ) - - def test_invalid_sweep(self): - with self.assertRaisesRegex(ValueError, "Invalid sweep_angle_axis"): - Geostationary( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - self.perspective_point_height, - "a", - self.false_easting, - self.false_northing, - self.ellipsoid, - ) - - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = Geostationary( - 0, 0, 1000, "y", false_easting=100, false_northing=-200 - ) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -200.0) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = Geostationary(0, 0, 1000, "y") - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = Geostationary( - 0, 0, 1000, "y", false_easting=None, false_northing=None - ) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_LambertAzimuthalEqualArea.py b/lib/iris/tests/unit/coord_systems/test_LambertAzimuthalEqualArea.py deleted file mode 100644 index 971ee06293..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_LambertAzimuthalEqualArea.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.coord_systems.LambertAzimuthalEqualArea` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import GeogCS, LambertAzimuthalEqualArea - - -class Test_as_cartopy_crs(tests.IrisTest): - def setUp(self): - self.latitude_of_projection_origin = 90.0 - self.longitude_of_projection_origin = 0.0 - self.semi_major_axis = 6377563.396 - self.semi_minor_axis = 6356256.909 - self.false_easting = 0.0 - self.false_northing = 0.0 - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.laea_cs = LambertAzimuthalEqualArea( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - self.false_easting, - self.false_northing, - ellipsoid=self.ellipsoid, - ) - - def test_crs_creation(self): - res = self.laea_cs.as_cartopy_crs() - globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - expected = ccrs.LambertAzimuthalEqualArea( - self.longitude_of_projection_origin, - self.latitude_of_projection_origin, - self.false_easting, - self.false_northing, - globe=globe, - ) - self.assertEqual(res, expected) - - -class Test_as_cartopy_projection(tests.IrisTest): - def setUp(self): - self.latitude_of_projection_origin = 0.0 - self.longitude_of_projection_origin = 0.0 - self.semi_major_axis = 6377563.396 - self.semi_minor_axis = 6356256.909 - self.false_easting = 0.0 - self.false_northing = 0.0 - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.laea_cs = LambertAzimuthalEqualArea( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - self.false_easting, - self.false_northing, - ellipsoid=self.ellipsoid, - ) - - def test_projection_creation(self): - res = self.laea_cs.as_cartopy_projection() - globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - expected = ccrs.LambertAzimuthalEqualArea( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - self.false_easting, - self.false_northing, - globe=globe, - ) - self.assertEqual(res, expected) - - -class Test_init_defaults(tests.IrisTest): - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = LambertAzimuthalEqualArea( - longitude_of_projection_origin=123, - latitude_of_projection_origin=-37, - false_easting=100, - false_northing=-200, - ) - self.assertEqualAndKind(crs.longitude_of_projection_origin, 123.0) - self.assertEqualAndKind(crs.latitude_of_projection_origin, -37.0) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -200.0) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.longitude_of_projection_origin, 0.0) - self.assertEqualAndKind(crs.latitude_of_projection_origin, 0.0) - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = LambertAzimuthalEqualArea() - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = LambertAzimuthalEqualArea( - longitude_of_projection_origin=None, - latitude_of_projection_origin=None, - false_easting=None, - false_northing=None, - ) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_LambertConformal.py b/lib/iris/tests/unit/coord_systems/test_LambertConformal.py deleted file mode 100644 index 7ba89208b1..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_LambertConformal.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.LambertConformal` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coord_systems import LambertConformal - - -class Test_init_defaults(tests.IrisTest): - # NOTE: most of the testing for LambertConformal is in the legacy test - # module 'iris.tests.test_coordsystem'. - # This class *only* tests the defaults for optional constructor args. - - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - # (Except secant_latitudes, which are done separately). - crs = LambertConformal( - central_lat=25.3, - central_lon=-172, - false_easting=100, - false_northing=-200, - ) - self.assertEqualAndKind(crs.central_lat, 25.3) - self.assertEqualAndKind(crs.central_lon, -172.0) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -200.0) - - def test_set_one_parallel(self): - # Check that setting the optional (non-ellipse) args works. - # (Except secant_latitudes, which are done separately). - crs = LambertConformal(secant_latitudes=-44) - self.assertEqual(len(crs.secant_latitudes), 1) - self.assertEqualAndKind(crs.secant_latitudes[0], -44.0) - - def test_set_two_parallels(self): - # Check that setting the optional (non-ellipse) args works. - # (Except secant_latitudes, which are done separately). - crs = LambertConformal(secant_latitudes=[43, -7]) - self.assertEqual(len(crs.secant_latitudes), 2) - self.assertEqualAndKind(crs.secant_latitudes[0], 43.0) - self.assertEqualAndKind(crs.secant_latitudes[1], -7.0) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.central_lat, 39.0) - self.assertEqualAndKind(crs.central_lon, -96.0) - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - self.assertEqual(len(crs.secant_latitudes), 2) - self.assertEqualAndKind(crs.secant_latitudes[0], 33.0) - self.assertEqualAndKind(crs.secant_latitudes[1], 45.0) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = LambertConformal() - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = LambertConformal( - central_lat=None, - central_lon=None, - false_easting=None, - false_northing=None, - secant_latitudes=None, - ) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_Mercator.py b/lib/iris/tests/unit/coord_systems/test_Mercator.py deleted file mode 100644 index ba04c77d57..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_Mercator.py +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.Mercator` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import GeogCS, Mercator - - -class Test_Mercator__basics(tests.IrisTest): - def setUp(self): - self.tm = Mercator( - longitude_of_projection_origin=90.0, - ellipsoid=GeogCS(6377563.396, 6356256.909), - ) - - def test_construction(self): - self.assertXMLElement(self.tm, ("coord_systems", "Mercator.xml")) - - def test_repr(self): - expected = ( - "Mercator(longitude_of_projection_origin=90.0, " - "ellipsoid=GeogCS(semi_major_axis=6377563.396, " - "semi_minor_axis=6356256.909), " - "standard_parallel=0.0, " - "scale_factor_at_projection_origin=None, " - "false_easting=0.0, false_northing=0.0)" - ) - self.assertEqual(expected, repr(self.tm)) - - -class Test_init_defaults(tests.IrisTest): - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = Mercator( - longitude_of_projection_origin=27, - standard_parallel=157.4, - false_easting=13, - false_northing=12, - ) - self.assertEqualAndKind(crs.longitude_of_projection_origin, 27.0) - self.assertEqualAndKind(crs.standard_parallel, 157.4) - self.assertEqualAndKind(crs.false_easting, 13.0) - self.assertEqualAndKind(crs.false_northing, 12.0) - - def test_set_optional_scale_factor_alternative(self): - # Check that setting the optional (non-ellipse) args works. - crs = Mercator( - scale_factor_at_projection_origin=1.3, - ) - self.assertEqualAndKind(crs.scale_factor_at_projection_origin, 1.3) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.longitude_of_projection_origin, 0.0) - self.assertEqualAndKind(crs.standard_parallel, 0.0) - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - self.assertEqualAndKind(crs.scale_factor_at_projection_origin, None) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = Mercator() - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = Mercator( - longitude_of_projection_origin=None, - standard_parallel=None, - scale_factor_at_projection_origin=None, - false_easting=None, - false_northing=None, - ) - self._check_crs_defaults(crs) - - -class Test_Mercator__as_cartopy_crs(tests.IrisTest): - def test_simple(self): - # Check that a projection set up with all the defaults is correctly - # converted to a cartopy CRS. - merc_cs = Mercator() - res = merc_cs.as_cartopy_crs() - # expected = ccrs.Mercator(globe=ccrs.Globe()) - expected = ccrs.Mercator(globe=ccrs.Globe(), latitude_true_scale=0.0) - self.assertEqual(res, expected) - - def test_extra_kwargs(self): - # Check that a projection with non-default values is correctly - # converted to a cartopy CRS. - longitude_of_projection_origin = 90.0 - true_scale_lat = 14.0 - false_easting = 13 - false_northing = 12 - ellipsoid = GeogCS( - semi_major_axis=6377563.396, semi_minor_axis=6356256.909 - ) - - merc_cs = Mercator( - longitude_of_projection_origin, - ellipsoid=ellipsoid, - standard_parallel=true_scale_lat, - false_easting=false_easting, - false_northing=false_northing, - ) - - expected = ccrs.Mercator( - central_longitude=longitude_of_projection_origin, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - latitude_true_scale=true_scale_lat, - false_easting=false_easting, - false_northing=false_northing, - ) - - res = merc_cs.as_cartopy_crs() - self.assertEqual(res, expected) - - def test_extra_kwargs_scale_factor_alternative(self): - # Check that a projection with non-default values is correctly - # converted to a cartopy CRS. - scale_factor_at_projection_origin = 1.3 - ellipsoid = GeogCS( - semi_major_axis=6377563.396, semi_minor_axis=6356256.909 - ) - - merc_cs = Mercator( - ellipsoid=ellipsoid, - scale_factor_at_projection_origin=scale_factor_at_projection_origin, - ) - - expected = ccrs.Mercator( - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - scale_factor=scale_factor_at_projection_origin, - ) - - res = merc_cs.as_cartopy_crs() - self.assertEqual(res, expected) - - -class Test_as_cartopy_projection(tests.IrisTest): - def test_simple(self): - # Check that a projection set up with all the defaults is correctly - # converted to a cartopy projection. - merc_cs = Mercator() - res = merc_cs.as_cartopy_projection() - expected = ccrs.Mercator(globe=ccrs.Globe(), latitude_true_scale=0.0) - self.assertEqual(res, expected) - - def test_extra_kwargs(self): - longitude_of_projection_origin = 90.0 - true_scale_lat = 14.0 - false_easting = 13 - false_northing = 12 - ellipsoid = GeogCS( - semi_major_axis=6377563.396, semi_minor_axis=6356256.909 - ) - - merc_cs = Mercator( - longitude_of_projection_origin, - ellipsoid=ellipsoid, - standard_parallel=true_scale_lat, - false_easting=false_easting, - false_northing=false_northing, - ) - - expected = ccrs.Mercator( - central_longitude=longitude_of_projection_origin, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - latitude_true_scale=true_scale_lat, - false_easting=false_easting, - false_northing=false_northing, - ) - - res = merc_cs.as_cartopy_projection() - self.assertEqual(res, expected) - - def test_extra_kwargs_scale_factor_alternative(self): - ellipsoid = GeogCS( - semi_major_axis=6377563.396, semi_minor_axis=6356256.909 - ) - scale_factor_at_projection_origin = 1.3 - - merc_cs = Mercator( - ellipsoid=ellipsoid, - scale_factor_at_projection_origin=scale_factor_at_projection_origin, - ) - - expected = ccrs.Mercator( - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - scale_factor=scale_factor_at_projection_origin, - ) - - res = merc_cs.as_cartopy_projection() - self.assertEqual(res, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_Orthographic.py b/lib/iris/tests/unit/coord_systems/test_Orthographic.py deleted file mode 100644 index ffcbecf55c..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_Orthographic.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.Orthographic` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import GeogCS, Orthographic - - -class Test_as_cartopy_crs(tests.IrisTest): - def setUp(self): - self.latitude_of_projection_origin = 0.0 - self.longitude_of_projection_origin = 0.0 - self.semi_major_axis = 6377563.396 - self.semi_minor_axis = 6356256.909 - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.ortho_cs = Orthographic( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - ellipsoid=self.ellipsoid, - ) - - def test_crs_creation(self): - res = self.ortho_cs.as_cartopy_crs() - globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - expected = ccrs.Orthographic( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - globe=globe, - ) - self.assertEqual(res, expected) - - -class Test_as_cartopy_projection(tests.IrisTest): - def setUp(self): - self.latitude_of_projection_origin = 0.0 - self.longitude_of_projection_origin = 0.0 - self.semi_major_axis = 6377563.396 - self.semi_minor_axis = 6356256.909 - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.ortho_cs = Orthographic( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - ellipsoid=self.ellipsoid, - ) - - def test_projection_creation(self): - res = self.ortho_cs.as_cartopy_projection() - globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - expected = ccrs.Orthographic( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - globe=globe, - ) - self.assertEqual(res, expected) - - -class Test_init_defaults(tests.IrisTest): - # NOTE: most of the testing for Orthographic.__init__ is elsewhere. - # This class *only* tests the defaults for optional constructor args. - - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = Orthographic(0, 0, false_easting=100, false_northing=-203.7) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -203.7) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = Orthographic(0, 0) - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = Orthographic(0, 0, false_easting=None, false_northing=None) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_PolarStereographic.py b/lib/iris/tests/unit/coord_systems/test_PolarStereographic.py deleted file mode 100755 index 25f5d24800..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_PolarStereographic.py +++ /dev/null @@ -1,251 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.PolarStereographic` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import GeogCS, PolarStereographic - - -class Test_PolarStereographic__basics(tests.IrisTest): - def setUp(self): - self.ps_blank = PolarStereographic( - central_lat=90.0, - central_lon=0, - ellipsoid=GeogCS(6377563.396, 6356256.909), - ) - self.ps_standard_parallel = PolarStereographic( - central_lat=90.0, - central_lon=0, - true_scale_lat=30, - ellipsoid=GeogCS(6377563.396, 6356256.909), - ) - self.ps_scale_factor = PolarStereographic( - central_lat=90.0, - central_lon=0, - scale_factor_at_projection_origin=1.1, - ellipsoid=GeogCS(6377563.396, 6356256.909), - ) - - def test_construction(self): - self.assertXMLElement( - self.ps_blank, ("coord_systems", "PolarStereographic.xml") - ) - - def test_construction_sp(self): - self.assertXMLElement( - self.ps_standard_parallel, - ("coord_systems", "PolarStereographicStandardParallel.xml"), - ) - - def test_construction_sf(self): - self.assertXMLElement( - self.ps_scale_factor, - ("coord_systems", "PolarStereographicScaleFactor.xml"), - ) - - def test_repr_blank(self): - expected = ( - "PolarStereographic(central_lat=90.0, central_lon=0.0, " - "false_easting=0.0, false_northing=0.0, " - "true_scale_lat=None, " - "ellipsoid=GeogCS(semi_major_axis=6377563.396, " - "semi_minor_axis=6356256.909))" - ) - self.assertEqual(expected, repr(self.ps_blank)) - - def test_repr_standard_parallel(self): - expected = ( - "PolarStereographic(central_lat=90.0, central_lon=0.0, " - "false_easting=0.0, false_northing=0.0, " - "true_scale_lat=30.0, " - "ellipsoid=GeogCS(semi_major_axis=6377563.396, " - "semi_minor_axis=6356256.909))" - ) - self.assertEqual(expected, repr(self.ps_standard_parallel)) - - def test_repr_scale_factor(self): - expected = ( - "PolarStereographic(central_lat=90.0, central_lon=0.0, " - "false_easting=0.0, false_northing=0.0, " - "scale_factor_at_projection_origin=1.1, " - "ellipsoid=GeogCS(semi_major_axis=6377563.396, " - "semi_minor_axis=6356256.909))" - ) - self.assertEqual(expected, repr(self.ps_scale_factor)) - - -class Test_init_defaults(tests.IrisTest): - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = PolarStereographic( - central_lat=90, - central_lon=50, - false_easting=13, - false_northing=12, - true_scale_lat=32, - ) - self.assertEqualAndKind(crs.central_lat, 90.0) - self.assertEqualAndKind(crs.central_lon, 50.0) - self.assertEqualAndKind(crs.false_easting, 13.0) - self.assertEqualAndKind(crs.false_northing, 12.0) - self.assertEqualAndKind(crs.true_scale_lat, 32.0) - - def test_set_optional_scale_factor_alternative(self): - # Check that setting the optional (non-ellipse) args works. - crs = PolarStereographic( - central_lat=-90, - central_lon=50, - false_easting=13, - false_northing=12, - scale_factor_at_projection_origin=3.1, - ) - self.assertEqualAndKind(crs.central_lat, -90.0) - self.assertEqualAndKind(crs.central_lon, 50.0) - self.assertEqualAndKind(crs.false_easting, 13.0) - self.assertEqualAndKind(crs.false_northing, 12.0) - self.assertEqualAndKind(crs.scale_factor_at_projection_origin, 3.1) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - self.assertEqualAndKind(crs.true_scale_lat, None) - self.assertEqualAndKind(crs.scale_factor_at_projection_origin, None) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = PolarStereographic( - central_lat=-90, - central_lon=50, - ) - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = PolarStereographic( - central_lat=-90, - central_lon=50, - true_scale_lat=None, - scale_factor_at_projection_origin=None, - false_easting=None, - false_northing=None, - ) - self._check_crs_defaults(crs) - - -class AsCartopyMixin: - def test_simple(self): - # Check that a projection set up with all the defaults is correctly - # converted to a cartopy CRS. - central_lat = -90 - central_lon = 50 - polar_cs = PolarStereographic( - central_lat=central_lat, - central_lon=central_lon, - ) - res = self.as_cartopy_method(polar_cs) - expected = ccrs.Stereographic( - central_latitude=central_lat, - central_longitude=central_lon, - globe=ccrs.Globe(), - ) - self.assertEqual(res, expected) - - def test_extra_kwargs_scale_factor(self): - # Check that a projection with non-default values is correctly - # converted to a cartopy CRS. - central_lat = -90 - central_lon = 50 - scale_factor_at_projection_origin = 1.3 - false_easting = 13 - false_northing = 15 - ellipsoid = GeogCS( - semi_major_axis=6377563.396, semi_minor_axis=6356256.909 - ) - - polar_cs = PolarStereographic( - central_lat=central_lat, - central_lon=central_lon, - scale_factor_at_projection_origin=scale_factor_at_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - ellipsoid=ellipsoid, - ) - - expected = ccrs.Stereographic( - central_latitude=central_lat, - central_longitude=central_lon, - false_easting=false_easting, - false_northing=false_northing, - scale_factor=scale_factor_at_projection_origin, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - ) - - res = self.as_cartopy_method(polar_cs) - self.assertEqual(res, expected) - - def test_extra_kwargs_true_scale_lat_alternative(self): - # Check that a projection with non-default values is correctly - # converted to a cartopy CRS. - central_lat = -90 - central_lon = 50 - true_scale_lat = 80 - false_easting = 13 - false_northing = 15 - ellipsoid = GeogCS( - semi_major_axis=6377563.396, semi_minor_axis=6356256.909 - ) - - polar_cs = PolarStereographic( - central_lat=central_lat, - central_lon=central_lon, - true_scale_lat=true_scale_lat, - false_easting=false_easting, - false_northing=false_northing, - ellipsoid=ellipsoid, - ) - - expected = ccrs.Stereographic( - central_latitude=central_lat, - central_longitude=central_lon, - false_easting=false_easting, - false_northing=false_northing, - true_scale_latitude=true_scale_lat, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - ) - - res = self.as_cartopy_method(polar_cs) - self.assertEqual(res, expected) - - -class Test_PolarStereographic__as_cartopy_crs(tests.IrisTest, AsCartopyMixin): - def setUp(self): - self.as_cartopy_method = PolarStereographic.as_cartopy_crs - - -class Test_PolarStereographic__as_cartopy_projection( - tests.IrisTest, AsCartopyMixin -): - def setUp(self): - self.as_cartopy_method = PolarStereographic.as_cartopy_projection - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_RotatedPole.py b/lib/iris/tests/unit/coord_systems/test_RotatedPole.py deleted file mode 100644 index dbb7a05bca..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_RotatedPole.py +++ /dev/null @@ -1,85 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.RotatedPole` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import cartopy -import cartopy.crs as ccrs - -from iris.coord_systems import RotatedGeogCS - - -class Test_init(tests.IrisTest): - def setUp(self): - self.pole_lon = 171.77 - self.pole_lat = 49.55 - self.rotation_about_new_pole = 180.0 - self.rp_crs = RotatedGeogCS( - self.pole_lat, self.pole_lon, self.rotation_about_new_pole - ) - - def test_crs_creation(self): - self.assertEqual(self.pole_lon, self.rp_crs.grid_north_pole_longitude) - self.assertEqual(self.pole_lat, self.rp_crs.grid_north_pole_latitude) - self.assertEqual( - self.rotation_about_new_pole, self.rp_crs.north_pole_grid_longitude - ) - - def test_as_cartopy_crs(self): - if cartopy.__version__ < "0.12": - with mock.patch("warnings.warn") as warn: - accrs = self.rp_crs.as_cartopy_crs() - self.assertEqual(warn.call_count, 1) - else: - accrs = self.rp_crs.as_cartopy_crs() - expected = ccrs.RotatedGeodetic( - self.pole_lon, self.pole_lat, self.rotation_about_new_pole - ) - self.assertEqual( - sorted(accrs.proj4_init.split(" +")), - sorted(expected.proj4_init.split(" +")), - ) - - def test_as_cartopy_projection(self): - if cartopy.__version__ < "0.12": - with mock.patch("warnings.warn") as warn: - _ = self.rp_crs.as_cartopy_projection() - self.assertEqual(warn.call_count, 1) - else: - accrsp = self.rp_crs.as_cartopy_projection() - expected = ccrs.RotatedPole( - self.pole_lon, self.pole_lat, self.rotation_about_new_pole - ) - self.assertEqual( - sorted(accrsp.proj4_init.split(" +")), - sorted(expected.proj4_init.split(" +")), - ) - - def _check_crs_default(self, crs): - # Check for property defaults when no kwargs options are set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.north_pole_grid_longitude, 0.0) - - def test_optional_args_missing(self): - # Check that unused 'north_pole_grid_longitude' defaults to 0.0. - crs = RotatedGeogCS(self.pole_lon, self.pole_lat) - self._check_crs_default(crs) - - def test_optional_args_None(self): - # Check that 'north_pole_grid_longitude=None' defaults to 0.0. - crs = RotatedGeogCS( - self.pole_lon, self.pole_lat, north_pole_grid_longitude=None - ) - self._check_crs_default(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_Stereographic.py b/lib/iris/tests/unit/coord_systems/test_Stereographic.py deleted file mode 100644 index acd77112c1..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_Stereographic.py +++ /dev/null @@ -1,212 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.Stereographic` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import GeogCS, Stereographic - - -def stereo(**kwargs): - return Stereographic( - central_lat=-90, - central_lon=-45, - false_easting=100, - false_northing=200, - ellipsoid=GeogCS(6377563.396, 6356256.909), - **kwargs, - ) - - -class Test_Stereographic_construction(tests.IrisTest): - def test_stereo(self): - st = stereo() - self.assertXMLElement(st, ("coord_systems", "Stereographic.xml")) - - -class Test_init_defaults(tests.IrisTest): - # This class *only* tests the defaults for optional constructor args. - - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = Stereographic( - 0, 0, false_easting=100, false_northing=-203.7, true_scale_lat=77 - ) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -203.7) - self.assertEqualAndKind(crs.true_scale_lat, 77.0) - - def test_set_optional_args_scale_factor_alternative(self): - # Check that setting the optional (non-ellipse) args works. - crs = Stereographic( - 0, - 0, - false_easting=100, - false_northing=-203.7, - scale_factor_at_projection_origin=1.3, - ) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -203.7) - self.assertEqualAndKind(crs.scale_factor_at_projection_origin, 1.3) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - self.assertIsNone(crs.true_scale_lat) - self.assertIsNone(crs.scale_factor_at_projection_origin) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = Stereographic(0, 0) - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = Stereographic( - 0, - 0, - false_easting=None, - false_northing=None, - true_scale_lat=None, - scale_factor_at_projection_origin=None, - ) - self._check_crs_defaults(crs) - - -class Test_Stereographic_repr(tests.IrisTest): - def test_stereo(self): - st = stereo() - expected = ( - "Stereographic(central_lat=-90.0, central_lon=-45.0, " - "false_easting=100.0, false_northing=200.0, true_scale_lat=None, " - "ellipsoid=GeogCS(semi_major_axis=6377563.396, semi_minor_axis=6356256.909))" - ) - self.assertEqual(expected, repr(st)) - - def test_stereo_scale_factor(self): - st = stereo(scale_factor_at_projection_origin=0.9) - expected = ( - "Stereographic(central_lat=-90.0, central_lon=-45.0, " - "false_easting=100.0, false_northing=200.0, " - "scale_factor_at_projection_origin=0.9, " - "ellipsoid=GeogCS(semi_major_axis=6377563.396, semi_minor_axis=6356256.909))" - ) - self.assertEqual(expected, repr(st)) - - -class AsCartopyMixin: - def test_basic(self): - latitude_of_projection_origin = -90.0 - longitude_of_projection_origin = -45.0 - false_easting = 100.0 - false_northing = 200.0 - ellipsoid = GeogCS(6377563.396, 6356256.909) - - st = Stereographic( - central_lat=latitude_of_projection_origin, - central_lon=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - ellipsoid=ellipsoid, - ) - expected = ccrs.Stereographic( - central_latitude=latitude_of_projection_origin, - central_longitude=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - ) - - res = self.as_cartopy_method(st) - self.assertEqual(res, expected) - - def test_true_scale_lat(self): - latitude_of_projection_origin = -90.0 - longitude_of_projection_origin = -45.0 - false_easting = 100.0 - false_northing = 200.0 - true_scale_lat = 30 - ellipsoid = GeogCS(6377563.396, 6356256.909) - - st = Stereographic( - central_lat=latitude_of_projection_origin, - central_lon=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - true_scale_lat=true_scale_lat, - ellipsoid=ellipsoid, - ) - expected = ccrs.Stereographic( - central_latitude=latitude_of_projection_origin, - central_longitude=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - true_scale_latitude=true_scale_lat, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - ) - - res = self.as_cartopy_method(st) - self.assertEqual(res, expected) - - def test_scale_factor(self): - latitude_of_projection_origin = -90.0 - longitude_of_projection_origin = -45.0 - false_easting = 100.0 - false_northing = 200.0 - scale_factor_at_projection_origin = 0.9 - ellipsoid = GeogCS(6377563.396, 6356256.909) - - st = Stereographic( - central_lat=latitude_of_projection_origin, - central_lon=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - scale_factor_at_projection_origin=scale_factor_at_projection_origin, - ellipsoid=ellipsoid, - ) - expected = ccrs.Stereographic( - central_latitude=latitude_of_projection_origin, - central_longitude=longitude_of_projection_origin, - false_easting=false_easting, - false_northing=false_northing, - scale_factor=scale_factor_at_projection_origin, - globe=ccrs.Globe( - semimajor_axis=6377563.396, - semiminor_axis=6356256.909, - ellipse=None, - ), - ) - - res = self.as_cartopy_method(st) - self.assertEqual(res, expected) - - -class Test_Stereographic_as_cartopy_crs(tests.IrisTest, AsCartopyMixin): - def setUp(self): - self.as_cartopy_method = Stereographic.as_cartopy_crs - - -class Test_Stereographic_as_cartopy_projection(tests.IrisTest, AsCartopyMixin): - def setUp(self): - self.as_cartopy_method = Stereographic.as_cartopy_projection - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_TransverseMercator.py b/lib/iris/tests/unit/coord_systems/test_TransverseMercator.py deleted file mode 100644 index 95b80333c2..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_TransverseMercator.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.TransverseMercator` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coord_systems import TransverseMercator - - -class Test_init_defaults(tests.IrisTest): - # NOTE: most of the testing for TransverseMercator is in the legacy test - # module 'iris.tests.test_coordsystem'. - # This class *only* tests the defaults for optional constructor args. - - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = TransverseMercator( - 0, - 50, - false_easting=100, - false_northing=-203.7, - scale_factor_at_central_meridian=1.057, - ) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -203.7) - self.assertEqualAndKind(crs.scale_factor_at_central_meridian, 1.057) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - self.assertEqualAndKind(crs.scale_factor_at_central_meridian, 1.0) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = TransverseMercator(0, 50) - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = TransverseMercator( - 0, - 50, - false_easting=None, - false_northing=None, - scale_factor_at_central_meridian=None, - ) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coord_systems/test_VerticalPerspective.py b/lib/iris/tests/unit/coord_systems/test_VerticalPerspective.py deleted file mode 100644 index 56498e40fa..0000000000 --- a/lib/iris/tests/unit/coord_systems/test_VerticalPerspective.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coord_systems.VerticalPerspective` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs - -from iris.coord_systems import GeogCS, VerticalPerspective - - -class Test(tests.IrisTest): - def setUp(self): - self.latitude_of_projection_origin = 0.0 - self.longitude_of_projection_origin = 0.0 - self.perspective_point_height = 38204820000.0 - self.false_easting = 0.0 - self.false_northing = 0.0 - - self.semi_major_axis = 6377563.396 - self.semi_minor_axis = 6356256.909 - self.ellipsoid = GeogCS(self.semi_major_axis, self.semi_minor_axis) - self.globe = ccrs.Globe( - semimajor_axis=self.semi_major_axis, - semiminor_axis=self.semi_minor_axis, - ellipse=None, - ) - - # Actual and expected coord system can be re-used for - # VerticalPerspective.test_crs_creation and test_projection_creation. - self.expected = ccrs.NearsidePerspective( - central_longitude=self.longitude_of_projection_origin, - central_latitude=self.latitude_of_projection_origin, - satellite_height=self.perspective_point_height, - false_easting=self.false_easting, - false_northing=self.false_northing, - globe=self.globe, - ) - self.vp_cs = VerticalPerspective( - self.latitude_of_projection_origin, - self.longitude_of_projection_origin, - self.perspective_point_height, - self.false_easting, - self.false_northing, - self.ellipsoid, - ) - - def test_crs_creation(self): - res = self.vp_cs.as_cartopy_crs() - self.assertEqual(res, self.expected) - - def test_projection_creation(self): - res = self.vp_cs.as_cartopy_projection() - self.assertEqual(res, self.expected) - - def test_set_optional_args(self): - # Check that setting the optional (non-ellipse) args works. - crs = VerticalPerspective( - 0, 0, 1000, false_easting=100, false_northing=-203.7 - ) - self.assertEqualAndKind(crs.false_easting, 100.0) - self.assertEqualAndKind(crs.false_northing, -203.7) - - def _check_crs_defaults(self, crs): - # Check for property defaults when no kwargs options were set. - # NOTE: except ellipsoid, which is done elsewhere. - self.assertEqualAndKind(crs.false_easting, 0.0) - self.assertEqualAndKind(crs.false_northing, 0.0) - - def test_no_optional_args(self): - # Check expected defaults with no optional args. - crs = VerticalPerspective(0, 0, 1000) - self._check_crs_defaults(crs) - - def test_optional_args_None(self): - # Check expected defaults with optional args=None. - crs = VerticalPerspective( - 0, 0, 1000, false_easting=None, false_northing=None - ) - self._check_crs_defaults(crs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/__init__.py b/lib/iris/tests/unit/coords/__init__.py deleted file mode 100644 index 10cee9db8b..0000000000 --- a/lib/iris/tests/unit/coords/__init__.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :mod:`iris.coords` module. - -Provides test methods and classes common to -:class:`~iris.tests.unit.coords.test_AuxCoord` and -:class:`~iris.tests.unit.coords.test_DimCoord`. - -""" - -import dask.array as da -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import is_lazy_data - - -def setup_test_arrays(self, shape, masked=False): - # Create concrete and lazy coordinate points and bounds test arrays, - # given a desired coord shape. - # If masked=True, also add masked arrays with some or no masked data, - # for both points and bounds, lazy and real. - n_pts = np.prod(shape) - # Note: the values must be integral for testing integer dtypes. - points = 10.0 * np.arange(n_pts, dtype=float).reshape(shape) - lower = points - 2.0 - upper = points + 2.0 - bounds = np.stack((lower, upper), axis=-1) - self.pts_real = points - self.pts_lazy = da.from_array(points, points.shape) - self.bds_real = bounds - self.bds_lazy = da.from_array(bounds, bounds.shape) - if masked: - mpoints = ma.array(points) - self.no_masked_pts_real = mpoints - self.no_masked_pts_lazy = da.from_array( - mpoints, mpoints.shape, asarray=False - ) - mpoints = ma.array(mpoints, copy=True) - mpoints[0] = ma.masked - self.masked_pts_real = mpoints - self.masked_pts_lazy = da.from_array( - mpoints, mpoints.shape, asarray=False - ) - mbounds = ma.array(bounds) - self.no_masked_bds_real = mbounds - self.no_masked_bds_lazy = da.from_array( - mbounds, mbounds.shape, asarray=False - ) - mbounds = ma.array(mbounds, copy=True) - mbounds[0] = ma.masked - self.masked_bds_real = mbounds - self.masked_bds_lazy = da.from_array( - mbounds, mbounds.shape, asarray=False - ) - - -def is_real_data(array): - # A parallel to :func:`iris._lazy_data.is_lazy_data`. - # Not just "not lazy" : ensure it is a 'real' array (i.e. numpy). - return isinstance(array, np.ndarray) - - -def arrays_share_data(a1, a2): - # Check whether 2 real arrays with the same content view the same data. - # For an ndarray x, x.base will either be None (if x owns its data) or a - # reference to the array which owns its data (if x is a view). - return ( - a1 is a2 - or a1.base is a2 - or a2.base is a1 - or a1.base is a2.base - and a1.base is not None - ) - - -def lazyness_string(data): - # Represent the lazyness of an array as a string. - return "lazy" if is_lazy_data(data) else "real" - - -def coords_all_dtypes_and_lazynesses(self, coord_class): - # Generate coords with all possible types of points and bounds, and all - # of the given dtypes. - points_types = ["real", "lazy"] - bounds_types = ["no", "real", "lazy"] - # Test a few specific combinations of points+bounds dtypes, including - # cases where they are different. - dtype_pairs = [ - (np.float64, np.float64), - (np.int16, np.int16), - (np.int16, np.float32), - (np.float64, np.int32), - ] - for pts_dtype, bds_dtype in dtype_pairs: - for points_type_name in points_types: - for bounds_type_name in bounds_types: - pts = np.asarray(self.pts_real, dtype=pts_dtype) - bds = np.asarray(self.bds_real, dtype=bds_dtype) - if points_type_name == "lazy": - pts = da.from_array(pts, pts.shape) - if bounds_type_name == "lazy": - bds = da.from_array(bds, bds.shape) - elif bounds_type_name == "no": - bds = None - coord = coord_class(pts, bounds=bds) - result = (coord, points_type_name, bounds_type_name) - yield result - - -class CoordTestMixin: - def setupTestArrays(self, shape=(3,), masked=False): - setup_test_arrays(self, shape=shape, masked=masked) - - def assertArraysShareData(self, a1, a2, *args, **kwargs): - # Check that two arrays are both real, same dtype, and based on the - # same underlying data (so changing one will change the other). - self.assertIsRealArray(a1) - self.assertIsRealArray(a2) - self.assertEqual(a1.dtype, a2.dtype) - self.assertTrue(arrays_share_data(a1, a2), *args, **kwargs) - - def assertArraysDoNotShareData(self, a1, a2, *args, **kwargs): - self.assertFalse(arrays_share_data(a1, a2), *args, **kwargs) - - def assertIsRealArray(self, array, *args, **kwargs): - # Check that the arg is a real array. - self.assertTrue(is_real_data(array), *args, **kwargs) - - def assertIsLazyArray(self, array, *args, **kwargs): - # Check that the arg is a lazy array. - self.assertTrue(is_lazy_data(array), *args, **kwargs) - - def assertEqualRealArraysAndDtypes(self, a1, a2, *args, **kwargs): - # Check that two arrays are real, equal, and have same dtype. - self.assertIsRealArray(a1) - self.assertIsRealArray(a2) - self.assertEqual(a1.dtype, a2.dtype) - self.assertArrayEqual(a1, a2) - - def assertEqualLazyArraysAndDtypes(self, a1, a2, *args, **kwargs): - # Check that two arrays are lazy, equal, and have same dtype. - self.assertIsLazyArray(a1) - self.assertIsLazyArray(a2) - self.assertEqual(a1.dtype, a2.dtype) - self.assertArrayEqual(a1.compute(), a2.compute()) diff --git a/lib/iris/tests/unit/coords/test_AncillaryVariable.py b/lib/iris/tests/unit/coords/test_AncillaryVariable.py deleted file mode 100644 index e5fc8fd28a..0000000000 --- a/lib/iris/tests/unit/coords/test_AncillaryVariable.py +++ /dev/null @@ -1,700 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coords.AncillaryVariable` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import dask.array as da -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_lazy_data -from iris.coords import AncillaryVariable -from iris.cube import Cube -from iris.tests.unit.coords import CoordTestMixin, lazyness_string - - -def data_all_dtypes_and_lazynesses(self): - # Generate ancillary variables with real and lazy data, and a few different - # dtypes. - data_types = ["real", "lazy"] - dtypes = [np.int16, np.int32, np.float32, np.float64] - for dtype in dtypes: - for data_type_name in data_types: - data = np.asarray(self.data_real, dtype=dtype) - if data_type_name == "lazy": - data = da.from_array(data, data.shape) - ancill_var = AncillaryVariable(data) - result = (ancill_var, data_type_name) - yield result - - -class AncillaryVariableTestMixin(CoordTestMixin): - # Define a 2-D default array shape. - def setupTestArrays(self, shape=(2, 3), masked=False): - # Create concrete and lazy data test arrays, given a desired shape. - # If masked=True, also add masked arrays with some or no masked data. - n_vals = np.prod(shape) - # Note: the values must be integral for testing integer dtypes. - values = 100.0 + 10.0 * np.arange(n_vals, dtype=float).reshape(shape) - self.data_real = values - self.data_lazy = da.from_array(values, values.shape) - - if masked: - mvalues = ma.array(values) - self.no_masked_data_real = mvalues - self.no_masked_data_lazy = da.from_array( - mvalues, mvalues.shape, asarray=False - ) - mvalues = ma.array(mvalues, copy=True) - mvalues[0] = ma.masked - self.masked_data_real = mvalues - self.masked_data_lazy = da.from_array( - mvalues, mvalues.shape, asarray=False - ) - - -class Test__init__(tests.IrisTest, AncillaryVariableTestMixin): - # Test for AncillaryVariable creation, with real / lazy data - def setUp(self): - self.setupTestArrays(masked=True) - - def test_lazyness_and_dtype_combinations(self): - for ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( - self, - ): - data = ancill_var.core_data() - # Check properties of data. - if data_lazyness == "real": - # Real data. - if ancill_var.dtype == self.data_real.dtype: - self.assertArraysShareData( - data, - self.data_real, - "Data values are not the same " - "data as the provided array.", - ) - self.assertIsNot( - data, - self.data_real, - "Data array is the same instance as the provided " - "array.", - ) - else: - # the original data values were cast to a test dtype. - check_data = self.data_real.astype(ancill_var.dtype) - self.assertEqualRealArraysAndDtypes(data, check_data) - else: - # Lazy data : the core data may be promoted to float. - check_data = self.data_lazy.astype(data.dtype) - self.assertEqualLazyArraysAndDtypes(data, check_data) - # The realisation type should be correct, though. - target_dtype = ancill_var.dtype - self.assertEqual(ancill_var.data.dtype, target_dtype) - - def test_no_masked_data_real(self): - data = self.no_masked_data_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertEqual(ma.count_masked(data), 0) - ancill_var = AncillaryVariable(data) - self.assertFalse(ancill_var.has_lazy_data()) - self.assertTrue(ma.isMaskedArray(ancill_var.data)) - self.assertEqual(ma.count_masked(ancill_var.data), 0) - - def test_no_masked_data_lazy(self): - data = self.no_masked_data_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertEqual(ma.count_masked(computed), 0) - ancill_var = AncillaryVariable(data) - self.assertTrue(ancill_var.has_lazy_data()) - self.assertTrue(ma.isMaskedArray(ancill_var.data)) - self.assertEqual(ma.count_masked(ancill_var.data), 0) - - def test_masked_data_real(self): - data = self.masked_data_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertTrue(ma.count_masked(data)) - ancill_var = AncillaryVariable(data) - self.assertFalse(ancill_var.has_lazy_data()) - self.assertTrue(ma.isMaskedArray(ancill_var.data)) - self.assertTrue(ma.count_masked(ancill_var.data)) - - def test_masked_data_lazy(self): - data = self.masked_data_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertTrue(ma.count_masked(computed)) - ancill_var = AncillaryVariable(data) - self.assertTrue(ancill_var.has_lazy_data()) - self.assertTrue(ma.isMaskedArray(ancill_var.data)) - self.assertTrue(ma.count_masked(ancill_var.data)) - - -class Test_core_data(tests.IrisTest, AncillaryVariableTestMixin): - # Test for AncillaryVariable.core_data() with various lazy/real data. - def setUp(self): - self.setupTestArrays() - - def test_real_data(self): - ancill_var = AncillaryVariable(self.data_real) - result = ancill_var.core_data() - self.assertArraysShareData( - result, - self.data_real, - "core_data() do not share data with the internal array.", - ) - - def test_lazy_data(self): - ancill_var = AncillaryVariable(self.data_lazy) - result = ancill_var.core_data() - self.assertEqualLazyArraysAndDtypes(result, self.data_lazy) - - def test_lazy_points_realise(self): - ancill_var = AncillaryVariable(self.data_lazy) - real_data = ancill_var.data - result = ancill_var.core_data() - self.assertEqualRealArraysAndDtypes(result, real_data) - - -class Test_lazy_data(tests.IrisTest, AncillaryVariableTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - ancill_var = AncillaryVariable(self.data_real) - result = ancill_var.lazy_data() - self.assertEqualLazyArraysAndDtypes(result, self.data_lazy) - - def test_lazy_core(self): - ancill_var = AncillaryVariable(self.data_lazy) - result = ancill_var.lazy_data() - self.assertIs(result, self.data_lazy) - - -class Test_has_lazy_data(tests.IrisTest, AncillaryVariableTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - ancill_var = AncillaryVariable(self.data_real) - result = ancill_var.has_lazy_data() - self.assertFalse(result) - - def test_lazy_core(self): - ancill_var = AncillaryVariable(self.data_lazy) - result = ancill_var.has_lazy_data() - self.assertTrue(result) - - def test_lazy_core_realise(self): - ancill_var = AncillaryVariable(self.data_lazy) - ancill_var.data - result = ancill_var.has_lazy_data() - self.assertFalse(result) - - -class Test__getitem__(tests.IrisTest, AncillaryVariableTestMixin): - # Test for AncillaryVariable indexing with various types of data. - def setUp(self): - self.setupTestArrays() - - def test_partial_slice_data_copy(self): - parent_ancill_var = AncillaryVariable([1.0, 2.0, 3.0]) - sub_ancill_var = parent_ancill_var[:1] - values_before_change = sub_ancill_var.data.copy() - parent_ancill_var.data[:] = -999.9 - self.assertArrayEqual(sub_ancill_var.data, values_before_change) - - def test_full_slice_data_copy(self): - parent_ancill_var = AncillaryVariable([1.0, 2.0, 3.0]) - sub_ancill_var = parent_ancill_var[:] - values_before_change = sub_ancill_var.data.copy() - parent_ancill_var.data[:] = -999.9 - self.assertArrayEqual(sub_ancill_var.data, values_before_change) - - def test_dtypes(self): - # Index ancillary variables with real+lazy data, and either an int or - # floating dtype. - # Check that dtypes remain the same in all cases, taking the dtypes - # directly from the core data as we have no masking). - for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( - self - ): - sub_ancill_var = main_ancill_var[:2, 1] - - ancill_var_dtype = main_ancill_var.dtype - msg = ( - "Indexing main_ancill_var of dtype {} with {} data changed" - "dtype of {} to {}." - ) - - sub_data = sub_ancill_var.core_data() - self.assertEqual( - sub_data.dtype, - ancill_var_dtype, - msg.format( - ancill_var_dtype, data_lazyness, "data", sub_data.dtype - ), - ) - - def test_lazyness(self): - # Index ancillary variables with real+lazy data, and either an int or - # floating dtype. - # Check that lazy data stays lazy and real stays real, in all cases. - for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( - self - ): - sub_ancill_var = main_ancill_var[:2, 1] - - msg = ( - "Indexing main_ancill_var of dtype {} with {} data " - "changed laziness of {} from {!r} to {!r}." - ) - ancill_var_dtype = main_ancill_var.dtype - sub_data_lazyness = lazyness_string(sub_ancill_var.core_data()) - self.assertEqual( - sub_data_lazyness, - data_lazyness, - msg.format( - ancill_var_dtype, - data_lazyness, - "data", - data_lazyness, - sub_data_lazyness, - ), - ) - - def test_real_data_copies(self): - # Index ancillary variables with real+lazy data. - # In all cases, check that any real arrays are copied by the indexing. - for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( - self - ): - sub_ancill_var = main_ancill_var[:2, 1] - - msg = ( - "Indexed ancillary variable with {} data " - "does not have its own separate {} array." - ) - if data_lazyness == "real": - main_data = main_ancill_var.core_data() - sub_data = sub_ancill_var.core_data() - sub_main_data = main_data[:2, 1] - self.assertEqualRealArraysAndDtypes(sub_data, sub_main_data) - self.assertArraysDoNotShareData( - sub_data, - sub_main_data, - msg.format(data_lazyness, "points"), - ) - - -class Test_copy(tests.IrisTest, AncillaryVariableTestMixin): - # Test for AncillaryVariable.copy() with various types of data. - def setUp(self): - self.setupTestArrays() - - def test_lazyness(self): - # Copy ancillary variables with real+lazy data, and either an int or - # floating dtype. - # Check that lazy data stays lazy and real stays real, in all cases. - for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( - self - ): - ancill_var_dtype = main_ancill_var.dtype - copied_ancill_var = main_ancill_var.copy() - - msg = ( - "Copying main_ancill_var of dtype {} with {} data " - "changed lazyness of {} from {!r} to {!r}." - ) - - copied_data_lazyness = lazyness_string( - copied_ancill_var.core_data() - ) - self.assertEqual( - copied_data_lazyness, - data_lazyness, - msg.format( - ancill_var_dtype, - data_lazyness, - "points", - data_lazyness, - copied_data_lazyness, - ), - ) - - def test_realdata_copies(self): - # Copy ancillary variables with real+lazy data. - # In all cases, check that any real arrays are copies, not views. - for main_ancill_var, data_lazyness in data_all_dtypes_and_lazynesses( - self - ): - copied_ancill_var = main_ancill_var.copy() - - msg = ( - "Copied ancillary variable with {} data " - "does not have its own separate {} array." - ) - - if data_lazyness == "real": - main_data = main_ancill_var.core_data() - copied_data = copied_ancill_var.core_data() - self.assertEqualRealArraysAndDtypes(main_data, copied_data) - self.assertArraysDoNotShareData( - main_data, copied_data, msg.format(data_lazyness, "points") - ) - - -class Test_data__getter(tests.IrisTest, AncillaryVariableTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_mutable_real_data(self): - # Check that ancill_var.data returns a modifiable array, and changes - # to it are reflected to the ancillary_var. - data = np.array([1.0, 2.0, 3.0, 4.0]) - ancill_var = AncillaryVariable(data) - initial_values = data.copy() - ancill_var.data[1:2] += 33.1 - result = ancill_var.data - self.assertFalse(np.all(result == initial_values)) - - def test_real_data(self): - # Getting real data does not change or copy them. - ancill_var = AncillaryVariable(self.data_real) - result = ancill_var.data - self.assertArraysShareData( - result, - self.data_real, - "Data values do not share data with the provided array.", - ) - - def test_lazy_data(self): - # Getting lazy data realises them. - ancill_var = AncillaryVariable(self.data_lazy) - self.assertTrue(ancill_var.has_lazy_data()) - result = ancill_var.data - self.assertFalse(ancill_var.has_lazy_data()) - self.assertEqualRealArraysAndDtypes(result, self.data_real) - - -class Test_data__setter(tests.IrisTest, AncillaryVariableTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_set_real(self): - # Setting new real data does not make a copy. - ancill_var = AncillaryVariable(self.data_real) - new_data = self.data_real + 102.3 - ancill_var.data = new_data - result = ancill_var.core_data() - self.assertArraysShareData( - result, - new_data, - "Data values do not share data with the assigned array.", - ) - - def test_fail_bad_shape(self): - # Setting real data requires matching shape. - ancill_var = AncillaryVariable([1.0, 2.0]) - msg = r"Require data with shape \(2,\), got \(3,\)" - with self.assertRaisesRegex(ValueError, msg): - ancill_var.data = np.array([1.0, 2.0, 3.0]) - - def test_real_set_lazy(self): - # Setting new lazy data does not make a copy. - ancill_var = AncillaryVariable(self.data_real) - new_data = self.data_lazy + 102.3 - ancill_var.data = new_data - result = ancill_var.core_data() - self.assertEqualLazyArraysAndDtypes(result, new_data) - - -class Test__str__(tests.IrisTest): - def test_non_time_values(self): - ancillary_var = AncillaryVariable( - np.array([2, 5, 9]), - standard_name="height", - long_name="height of detector", - var_name="height", - units="m", - attributes={"notes": "Measured from sea level"}, - ) - expected = "\n".join( - [ - "AncillaryVariable : height / (m)", - " data: [2, 5, 9]", - " shape: (3,)", - " dtype: int64", - " standard_name: 'height'", - " long_name: 'height of detector'", - " var_name: 'height'", - " attributes:", - " notes 'Measured from sea level'", - ] - ) - self.assertEqual(expected, ancillary_var.__str__()) - - def test_time_values(self): - ancillary_var = AncillaryVariable( - np.array([2, 5, 9]), - units="hours since 1970-01-01 01:00", - long_name="time of previous valid detection", - ) - expected = "\n".join( - [ - ( - "AncillaryVariable : time of previous valid detection / " - "(hours since 1970-01-01 01:00, standard calendar)" - ), - ( - " data: [1970-01-01 03:00:00, 1970-01-01 06:00:00, " - "1970-01-01 10:00:00]" - ), - " shape: (3,)", - " dtype: int64", - " long_name: 'time of previous valid detection'", - ] - ) - self.assertEqual(expected, ancillary_var.__str__()) - - -class Test__repr__(tests.IrisTest): - def test_non_time_values(self): - ancillary_var = AncillaryVariable( - np.array([2, 5, 9]), - standard_name="height", - long_name="height of detector", - var_name="height", - units="m", - attributes={"notes": "Measured from sea level"}, - ) - expected = "" - self.assertEqual(expected, ancillary_var.__repr__()) - - def test_time_values(self): - ancillary_var = AncillaryVariable( - np.array([2, 5, 9]), - units="hours since 1970-01-01 01:00", - long_name="time of previous valid detection", - ) - expected = ( - "" - ) - self.assertEqual(expected, ancillary_var.__repr__()) - - -class Test___binary_operator__(tests.IrisTest, AncillaryVariableTestMixin): - # Test maths operations on on real+lazy data. - def setUp(self): - self.setupTestArrays() - - self.real_ancill_var = AncillaryVariable(self.data_real) - self.lazy_ancill_var = AncillaryVariable(self.data_lazy) - - self.test_combinations = [ - (self.real_ancill_var, self.data_real, "real"), - (self.lazy_ancill_var, self.data_lazy, "lazy"), - ] - - def _check(self, result_ancill_var, expected_data, lazyness): - # Test each operation on - data = result_ancill_var.core_data() - if lazyness == "real": - self.assertEqualRealArraysAndDtypes(expected_data, data) - else: - self.assertEqualLazyArraysAndDtypes(expected_data, data) - - def test_add(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = ancill_var + 10 - expected_data = orig_data + 10 - self._check(result, expected_data, data_lazyness) - - def test_add_inplace(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - ancill_var += 10 - expected_data = orig_data + 10 - self._check(ancill_var, expected_data, data_lazyness) - - def test_right_add(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = 10 + ancill_var - expected_data = 10 + orig_data - self._check(result, expected_data, data_lazyness) - - def test_subtract(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = ancill_var - 10 - expected_data = orig_data - 10 - self._check(result, expected_data, data_lazyness) - - def test_subtract_inplace(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - ancill_var -= 10 - expected_data = orig_data - 10 - self._check(ancill_var, expected_data, data_lazyness) - - def test_right_subtract(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = 10 - ancill_var - expected_data = 10 - orig_data - self._check(result, expected_data, data_lazyness) - - def test_multiply(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = ancill_var * 10 - expected_data = orig_data * 10 - self._check(result, expected_data, data_lazyness) - - def test_multiply_inplace(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - ancill_var *= 10 - expected_data = orig_data * 10 - self._check(ancill_var, expected_data, data_lazyness) - - def test_right_multiply(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = 10 * ancill_var - expected_data = 10 * orig_data - self._check(result, expected_data, data_lazyness) - - def test_divide(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = ancill_var / 10 - expected_data = orig_data / 10 - self._check(result, expected_data, data_lazyness) - - def test_divide_inplace(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - ancill_var /= 10 - expected_data = orig_data / 10 - self._check(ancill_var, expected_data, data_lazyness) - - def test_right_divide(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = 10 / ancill_var - expected_data = 10 / orig_data - self._check(result, expected_data, data_lazyness) - - def test_negative(self): - for ancill_var, orig_data, data_lazyness in self.test_combinations: - result = -ancill_var - expected_data = -orig_data - self._check(result, expected_data, data_lazyness) - - -class Test_has_bounds(tests.IrisTest): - def test(self): - ancillary_var = AncillaryVariable(np.array([2, 9, 5])) - self.assertFalse(ancillary_var.has_bounds()) - - -class Test_convert_units(tests.IrisTest): - def test_preserves_lazy(self): - test_data = np.array([[11.1, 12.2, 13.3], [21.4, 22.5, 23.6]]) - lazy_data = as_lazy_data(test_data) - ancill_var = AncillaryVariable(data=lazy_data, units="m") - ancill_var.convert_units("ft") - self.assertTrue(ancill_var.has_lazy_data()) - test_data_ft = Unit("m").convert(test_data, "ft") - self.assertArrayAllClose(ancill_var.data, test_data_ft) - - -class Test_is_compatible(tests.IrisTest): - def setUp(self): - self.ancill_var = AncillaryVariable( - [1.0, 8.0, 22.0], standard_name="number_of_observations", units="1" - ) - self.modified_ancill_var = self.ancill_var.copy() - - def test_not_compatible_diff_name(self): - # Different name() - not compatible - self.modified_ancill_var.rename("air_temperature") - self.assertFalse( - self.ancill_var.is_compatible(self.modified_ancill_var) - ) - - def test_not_compatible_diff_units(self): - # Different units- not compatible - self.modified_ancill_var.units = "m" - self.assertFalse( - self.ancill_var.is_compatible(self.modified_ancill_var) - ) - - def test_not_compatible_diff_common_attrs(self): - # Different common attributes - not compatible. - self.ancill_var.attributes["source"] = "A" - self.modified_ancill_var.attributes["source"] = "B" - self.assertFalse( - self.ancill_var.is_compatible(self.modified_ancill_var) - ) - - def test_compatible_diff_data(self): - # Different data values - compatible. - self.modified_ancill_var.data = [10.0, 20.0, 100.0] - self.assertTrue( - self.ancill_var.is_compatible(self.modified_ancill_var) - ) - - def test_compatible_diff_var_name(self): - # Different var_name (but same name()) - compatible. - self.modified_ancill_var.var_name = "obs_num" - self.assertTrue( - self.ancill_var.is_compatible(self.modified_ancill_var) - ) - - def test_compatible_diff_non_common_attributes(self): - # Different non-common attributes - compatible. - self.ancill_var.attributes["source"] = "A" - self.modified_ancill_var.attributes["origin"] = "B" - self.assertTrue( - self.ancill_var.is_compatible(self.modified_ancill_var) - ) - - def test_compatible_ignore_common_attribute(self): - # ignore different common attributes - compatible. - self.ancill_var.attributes["source"] = "A" - self.modified_ancill_var.attributes["source"] = "B" - self.assertTrue( - self.ancill_var.is_compatible( - self.modified_ancill_var, ignore="source" - ) - ) - - -class TestEquality(tests.IrisTest): - def test_nanpoints_eq_self(self): - av1 = AncillaryVariable([1.0, np.nan, 2.0]) - self.assertEqual(av1, av1) - - def test_nanpoints_eq_copy(self): - av1 = AncillaryVariable([1.0, np.nan, 2.0]) - av2 = av1.copy() - self.assertEqual(av1, av2) - - -class Test_cube_dims(tests.IrisTest): - def test(self): - # Check that "coord.cube_dims(cube)" calls "cube.coord_dims(coord)". - mock_dims_result = mock.sentinel.AV_DIMS - mock_dims_call = mock.Mock(return_value=mock_dims_result) - mock_cube = mock.Mock(Cube, ancillary_variable_dims=mock_dims_call) - test_var = AncillaryVariable([1], long_name="test_name") - - result = test_var.cube_dims(mock_cube) - self.assertEqual(result, mock_dims_result) - self.assertEqual(mock_dims_call.call_args_list, [mock.call(test_var)]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/test_AuxCoord.py b/lib/iris/tests/unit/coords/test_AuxCoord.py deleted file mode 100644 index e5147659fc..0000000000 --- a/lib/iris/tests/unit/coords/test_AuxCoord.py +++ /dev/null @@ -1,804 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.coords.AuxCoord` class. - -Note: a lot of these methods are actually defined by the :class:`Coord` class, -but can only be tested on concrete instances (DimCoord or AuxCoord). - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from cf_units import Unit -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_lazy_data -from iris.coords import AuxCoord -from iris.tests.unit.coords import ( - CoordTestMixin, - coords_all_dtypes_and_lazynesses, - lazyness_string, -) - - -class AuxCoordTestMixin(CoordTestMixin): - # Define a 2-D default array shape. - def setupTestArrays(self, shape=(2, 3), masked=False): - super().setupTestArrays(shape, masked=masked) - - -class Test__init__(tests.IrisTest, AuxCoordTestMixin): - # Test for AuxCoord creation, with various combinations of points and - # bounds = real / lazy / None. - def setUp(self): - self.setupTestArrays(masked=True) - - def test_lazyness_and_dtype_combinations(self): - for ( - coord, - points_type_name, - bounds_type_name, - ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - pts = coord.core_points() - bds = coord.core_bounds() - # Check properties of points. - if points_type_name == "real": - # Real points. - if coord.dtype == self.pts_real.dtype: - self.assertArraysShareData( - pts, - self.pts_real, - "Points are not the same data as the provided array.", - ) - self.assertIsNot( - pts, - self.pts_real, - "Points array is the same instance as the provided " - "array.", - ) - else: - # the original points were cast to a test dtype. - check_pts = self.pts_real.astype(coord.dtype) - self.assertEqualRealArraysAndDtypes(pts, check_pts) - else: - # Lazy points : the core data may be promoted to float. - check_pts = self.pts_lazy.astype(pts.dtype) - self.assertEqualLazyArraysAndDtypes(pts, check_pts) - # The realisation type should be correct, though. - target_dtype = coord.dtype - self.assertEqual(coord.points.dtype, target_dtype) - - # Check properties of bounds. - if bounds_type_name == "real": - # Real bounds. - if coord.bounds_dtype == self.bds_real.dtype: - self.assertArraysShareData( - bds, - self.bds_real, - "Bounds are not the same data as the provided array.", - ) - self.assertIsNot( - pts, - self.pts_real, - "Bounds array is the same instance as the provided " - "array.", - ) - else: - # the original bounds were cast to a test dtype. - check_bds = self.bds_real.astype(coord.bounds_dtype) - self.assertEqualRealArraysAndDtypes(bds, check_bds) - elif bounds_type_name == "lazy": - # Lazy points : the core data may be promoted to float. - check_bds = self.bds_lazy.astype(bds.dtype) - self.assertEqualLazyArraysAndDtypes(bds, check_bds) - # The realisation type should be correct, though. - target_dtype = coord.bounds_dtype - self.assertEqual(coord.bounds.dtype, target_dtype) - - def test_fail_bounds_shape_mismatch(self): - bds_shape = list(self.bds_real.shape) - bds_shape[0] += 1 - bds_wrong = np.zeros(bds_shape) - msg = "Bounds shape must be compatible with points shape" - with self.assertRaisesRegex(ValueError, msg): - AuxCoord(self.pts_real, bounds=bds_wrong) - - def test_no_masked_pts_real(self): - data = self.no_masked_pts_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertEqual(ma.count_masked(data), 0) - coord = AuxCoord(data) - self.assertFalse(coord.has_lazy_points()) - self.assertTrue(ma.isMaskedArray(coord.points)) - self.assertEqual(ma.count_masked(coord.points), 0) - - def test_no_masked_pts_lazy(self): - data = self.no_masked_pts_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertEqual(ma.count_masked(computed), 0) - coord = AuxCoord(data) - self.assertTrue(coord.has_lazy_points()) - self.assertTrue(ma.isMaskedArray(coord.points)) - self.assertEqual(ma.count_masked(coord.points), 0) - - def test_masked_pts_real(self): - data = self.masked_pts_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertTrue(ma.count_masked(data)) - coord = AuxCoord(data) - self.assertFalse(coord.has_lazy_points()) - self.assertTrue(ma.isMaskedArray(coord.points)) - self.assertTrue(ma.count_masked(coord.points)) - - def test_masked_pts_lazy(self): - data = self.masked_pts_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertTrue(ma.count_masked(computed)) - coord = AuxCoord(data) - self.assertTrue(coord.has_lazy_points()) - self.assertTrue(ma.isMaskedArray(coord.points)) - self.assertTrue(ma.count_masked(coord.points)) - - def test_no_masked_bds_real(self): - data = self.no_masked_bds_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertEqual(ma.count_masked(data), 0) - coord = AuxCoord(self.pts_real, bounds=data) - self.assertFalse(coord.has_lazy_bounds()) - self.assertTrue(ma.isMaskedArray(coord.bounds)) - self.assertEqual(ma.count_masked(coord.bounds), 0) - - def test_no_masked_bds_lazy(self): - data = self.no_masked_bds_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertEqual(ma.count_masked(computed), 0) - coord = AuxCoord(self.pts_real, bounds=data) - self.assertTrue(coord.has_lazy_bounds()) - self.assertTrue(ma.isMaskedArray(coord.bounds)) - self.assertEqual(ma.count_masked(coord.bounds), 0) - - def test_masked_bds_real(self): - data = self.masked_bds_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertTrue(ma.count_masked(data)) - coord = AuxCoord(self.pts_real, bounds=data) - self.assertFalse(coord.has_lazy_bounds()) - self.assertTrue(ma.isMaskedArray(coord.bounds)) - self.assertTrue(ma.count_masked(coord.bounds)) - - def test_masked_bds_lazy(self): - data = self.masked_bds_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertTrue(ma.count_masked(computed)) - coord = AuxCoord(self.pts_real, bounds=data) - self.assertTrue(coord.has_lazy_bounds()) - self.assertTrue(ma.isMaskedArray(coord.bounds)) - self.assertTrue(ma.count_masked(coord.bounds)) - - -class Test_core_points(tests.IrisTest, AuxCoordTestMixin): - # Test for AuxCoord.core_points() with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_real_points(self): - coord = AuxCoord(self.pts_real) - result = coord.core_points() - self.assertArraysShareData( - result, - self.pts_real, - "core_points() do not share data with the internal array.", - ) - - def test_lazy_points(self): - coord = AuxCoord(self.pts_lazy) - result = coord.core_points() - self.assertEqualLazyArraysAndDtypes(result, self.pts_lazy) - - def test_lazy_points_realise(self): - coord = AuxCoord(self.pts_lazy) - real_points = coord.points - result = coord.core_points() - self.assertEqualRealArraysAndDtypes(result, real_points) - - -class Test_core_bounds(tests.IrisTest, AuxCoordTestMixin): - # Test for AuxCoord.core_bounds() with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_no_bounds(self): - coord = AuxCoord(self.pts_real) - result = coord.core_bounds() - self.assertIsNone(result) - - def test_real_bounds(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - result = coord.core_bounds() - self.assertArraysShareData( - result, - self.bds_real, - "core_bounds() do not share data with the internal array.", - ) - - def test_lazy_bounds(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - result = coord.core_bounds() - self.assertEqualLazyArraysAndDtypes(result, self.bds_lazy) - - def test_lazy_bounds_realise(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - real_bounds = coord.bounds - result = coord.core_bounds() - self.assertEqualRealArraysAndDtypes(result, real_bounds) - - -class Test_lazy_points(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - coord = AuxCoord(self.pts_real) - result = coord.lazy_points() - self.assertEqualLazyArraysAndDtypes(result, self.pts_lazy) - - def test_lazy_core(self): - coord = AuxCoord(self.pts_lazy) - result = coord.lazy_points() - self.assertIs(result, self.pts_lazy) - - -class Test_lazy_bounds(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_no_bounds(self): - coord = AuxCoord(self.pts_real) - result = coord.lazy_bounds() - self.assertIsNone(result) - - def test_real_core(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - result = coord.lazy_bounds() - self.assertEqualLazyArraysAndDtypes(result, self.bds_lazy) - - def test_lazy_core(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - result = coord.lazy_bounds() - self.assertIs(result, self.bds_lazy) - - -class Test_has_lazy_points(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - coord = AuxCoord(self.pts_real) - result = coord.has_lazy_points() - self.assertFalse(result) - - def test_lazy_core(self): - coord = AuxCoord(self.pts_lazy) - result = coord.has_lazy_points() - self.assertTrue(result) - - def test_lazy_core_realise(self): - coord = AuxCoord(self.pts_lazy) - coord.points - result = coord.has_lazy_points() - self.assertFalse(result) - - -class Test_has_lazy_bounds(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - result = coord.has_lazy_bounds() - self.assertFalse(result) - - def test_lazy_core(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - result = coord.has_lazy_bounds() - self.assertTrue(result) - - def test_lazy_core_realise(self): - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - coord.bounds - result = coord.has_lazy_bounds() - self.assertFalse(result) - - -class Test_bounds_dtype(tests.IrisTest, AuxCoordTestMixin): - def test_i16(self): - test_dtype = np.int16 - coord = AuxCoord([1], bounds=np.array([[0, 4]], dtype=test_dtype)) - result = coord.bounds_dtype - self.assertEqual(result, test_dtype) - - def test_u16(self): - test_dtype = np.uint16 - coord = AuxCoord([1], bounds=np.array([[0, 4]], dtype=test_dtype)) - result = coord.bounds_dtype - self.assertEqual(result, test_dtype) - - def test_f16(self): - test_dtype = np.float16 - coord = AuxCoord([1], bounds=np.array([[0, 4]], dtype=test_dtype)) - result = coord.bounds_dtype - self.assertEqual(result, test_dtype) - - -class Test__getitem__(tests.IrisTest, AuxCoordTestMixin): - # Test for AuxCoord indexing with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_partial_slice_data_copy(self): - parent_coord = AuxCoord([1.0, 2.0, 3.0]) - sub_coord = parent_coord[:1] - values_before_change = sub_coord.points.copy() - parent_coord.points[:] = -999.9 - self.assertArrayEqual(sub_coord.points, values_before_change) - - def test_full_slice_data_copy(self): - parent_coord = AuxCoord([1.0, 2.0, 3.0]) - sub_coord = parent_coord[:] - values_before_change = sub_coord.points.copy() - parent_coord.points[:] = -999.9 - self.assertArrayEqual(sub_coord.points, values_before_change) - - def test_dtypes(self): - # Index coords with all combinations of real+lazy points+bounds, and - # either an int or floating dtype. - # Check that dtypes remain the same in all cases, taking the dtypes - # directly from the core points and bounds (as we have no masking). - for ( - main_coord, - points_type_name, - bounds_type_name, - ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - sub_coord = main_coord[:2, 1] - - coord_dtype = main_coord.dtype - msg = ( - "Indexing main_coord of dtype {} " - "with {} points and {} bounds " - "changed dtype of {} to {}." - ) - - sub_points = sub_coord.core_points() - self.assertEqual( - sub_points.dtype, - coord_dtype, - msg.format( - coord_dtype, - points_type_name, - bounds_type_name, - "points", - sub_points.dtype, - ), - ) - - if bounds_type_name != "no": - sub_bounds = sub_coord.core_bounds() - main_bounds_dtype = main_coord.bounds_dtype - self.assertEqual( - sub_bounds.dtype, - main_bounds_dtype, - msg.format( - main_bounds_dtype, - points_type_name, - bounds_type_name, - "bounds", - sub_bounds.dtype, - ), - ) - - def test_lazyness(self): - # Index coords with all combinations of real+lazy points+bounds, and - # either an int or floating dtype. - # Check that lazy data stays lazy and real stays real, in all cases. - for ( - main_coord, - points_type_name, - bounds_type_name, - ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - sub_coord = main_coord[:2, 1] - - msg = ( - "Indexing coord of dtype {} " - "with {} points and {} bounds " - "changed laziness of {} from {!r} to {!r}." - ) - coord_dtype = main_coord.dtype - sub_points_lazyness = lazyness_string(sub_coord.core_points()) - self.assertEqual( - sub_points_lazyness, - points_type_name, - msg.format( - coord_dtype, - points_type_name, - bounds_type_name, - "points", - points_type_name, - sub_points_lazyness, - ), - ) - - if bounds_type_name != "no": - sub_bounds_lazy = lazyness_string(sub_coord.core_bounds()) - self.assertEqual( - sub_bounds_lazy, - bounds_type_name, - msg.format( - coord_dtype, - points_type_name, - bounds_type_name, - "bounds", - bounds_type_name, - sub_bounds_lazy, - ), - ) - - def test_real_data_copies(self): - # Index coords with all combinations of real+lazy points+bounds. - # In all cases, check that any real arrays are copied by the indexing. - for ( - main_coord, - points_lazyness, - bounds_lazyness, - ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - sub_coord = main_coord[:2, 1] - - msg = ( - "Indexed coord with {} points and {} bounds " - "does not have its own separate {} array." - ) - if points_lazyness == "real": - main_points = main_coord.core_points() - sub_points = sub_coord.core_points() - sub_main_points = main_points[:2, 1] - self.assertEqualRealArraysAndDtypes( - sub_points, sub_main_points - ) - self.assertArraysDoNotShareData( - sub_points, - sub_main_points, - msg.format(points_lazyness, bounds_lazyness, "points"), - ) - - if bounds_lazyness == "real": - main_bounds = main_coord.core_bounds() - sub_bounds = sub_coord.core_bounds() - sub_main_bounds = main_bounds[:2, 1] - self.assertEqualRealArraysAndDtypes( - sub_bounds, sub_main_bounds - ) - self.assertArraysDoNotShareData( - sub_bounds, - sub_main_bounds, - msg.format(points_lazyness, bounds_lazyness, "bounds"), - ) - - -class Test_copy(tests.IrisTest, AuxCoordTestMixin): - # Test for AuxCoord.copy() with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_lazyness(self): - # Copy coords with all combinations of real+lazy points+bounds, and - # either an int or floating dtype. - # Check that lazy data stays lazy and real stays real, in all cases. - for ( - main_coord, - points_lazyness, - bounds_lazyness, - ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - coord_dtype = main_coord.dtype - copied_coord = main_coord.copy() - - msg = ( - "Copying main_coord of dtype {} " - "with {} points and {} bounds " - "changed lazyness of {} from {!r} to {!r}." - ) - - copied_pts_lazyness = lazyness_string(copied_coord.core_points()) - self.assertEqual( - copied_pts_lazyness, - points_lazyness, - msg.format( - coord_dtype, - points_lazyness, - bounds_lazyness, - "points", - points_lazyness, - copied_pts_lazyness, - ), - ) - - if bounds_lazyness != "no": - copied_bds_lazy = lazyness_string(copied_coord.core_bounds()) - self.assertEqual( - copied_bds_lazy, - bounds_lazyness, - msg.format( - coord_dtype, - points_lazyness, - bounds_lazyness, - "bounds", - bounds_lazyness, - copied_bds_lazy, - ), - ) - - def test_realdata_copies(self): - # Copy coords with all combinations of real+lazy points+bounds. - # In all cases, check that any real arrays are copies, not views. - for ( - main_coord, - points_lazyness, - bounds_lazyness, - ) in coords_all_dtypes_and_lazynesses(self, AuxCoord): - copied_coord = main_coord.copy() - - msg = ( - "Copied coord with {} points and {} bounds " - "does not have its own separate {} array." - ) - - if points_lazyness == "real": - main_points = main_coord.core_points() - copied_points = copied_coord.core_points() - self.assertEqualRealArraysAndDtypes(main_points, copied_points) - self.assertArraysDoNotShareData( - main_points, - copied_points, - msg.format(points_lazyness, bounds_lazyness, "points"), - ) - - if bounds_lazyness == "real": - main_bounds = main_coord.core_bounds() - copied_bounds = copied_coord.core_bounds() - self.assertEqualRealArraysAndDtypes(main_bounds, copied_bounds) - self.assertArraysDoNotShareData( - main_bounds, - copied_bounds, - msg.format(points_lazyness, bounds_lazyness, "bounds"), - ) - - -class Test_points__getter(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_mutable_real_points(self): - # Check that coord.points returns a modifiable array, and changes to it - # are reflected to the coord. - data = np.array([1.0, 2.0, 3.0, 4.0]) - coord = AuxCoord(data) - initial_values = data.copy() - coord.points[1:2] += 33.1 - result = coord.points - self.assertFalse(np.all(result == initial_values)) - - def test_real_points(self): - # Getting real points does not change or copy them. - coord = AuxCoord(self.pts_real) - result = coord.points - self.assertArraysShareData( - result, - self.pts_real, - "Points do not share data with the provided array.", - ) - - def test_lazy_points(self): - # Getting lazy points realises them. - coord = AuxCoord(self.pts_lazy) - self.assertTrue(coord.has_lazy_points()) - result = coord.points - self.assertFalse(coord.has_lazy_points()) - self.assertEqualRealArraysAndDtypes(result, self.pts_real) - - def test_real_points_with_real_bounds(self): - # Getting real points does not change real bounds. - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - coord.points - result = coord.core_bounds() - self.assertArraysShareData( - result, - self.bds_real, - "Bounds do not share data with the provided array.", - ) - - def test_real_points_with_lazy_bounds(self): - # Getting real points does not touch lazy bounds. - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - coord.points - self.assertTrue(coord.has_lazy_bounds()) - - def test_lazy_points_with_real_bounds(self): - # Getting lazy points does not affect real bounds. - coord = AuxCoord(self.pts_lazy, bounds=self.bds_real) - coord.points - result = coord.core_bounds() - self.assertEqualRealArraysAndDtypes(result, self.bds_real) - - def test_lazy_points_with_lazy_bounds(self): - # Getting lazy points does not touch lazy bounds. - coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy) - coord.points - self.assertTrue(coord.has_lazy_bounds()) - - -class Test_points__setter(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_set_real(self): - # Setting new real points does not make a copy. - coord = AuxCoord(self.pts_real) - new_pts = self.pts_real + 102.3 - coord.points = new_pts - result = coord.core_points() - self.assertArraysShareData( - result, - new_pts, - "Points do not share data with the assigned array.", - ) - - def test_fail_bad_shape(self): - # Setting real points requires matching shape. - coord = AuxCoord([1.0, 2.0]) - msg = r"Require data with shape \(2,\), got \(3,\)" - with self.assertRaisesRegex(ValueError, msg): - coord.points = np.array([1.0, 2.0, 3.0]) - - def test_real_set_lazy(self): - # Setting new lazy points does not make a copy. - coord = AuxCoord(self.pts_real) - new_pts = self.pts_lazy + 102.3 - coord.points = new_pts - result = coord.core_points() - self.assertEqualLazyArraysAndDtypes(result, new_pts) - - def test_set_points_with_lazy_bounds(self): - # Setting points does not touch lazy bounds. - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - new_pts = self.pts_real + 102.3 - coord.points = new_pts - result = coord.core_bounds() - self.assertEqualLazyArraysAndDtypes(result, self.bds_lazy) - - -class Test_bounds__getter(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_mutable_real_bounds(self): - # Check that coord.bounds returns a modifiable array, and changes to it - # are reflected to the coord. - pts_data = np.array([1.5, 2.5]) - bds_data = np.array([[1.4, 1.6], [2.4, 2.6]]) - coord = AuxCoord(pts_data, bounds=bds_data) - initial_values = bds_data.copy() - coord.bounds[1:2] += 33.1 - result = coord.bounds - self.assertFalse(np.all(result == initial_values)) - - def test_real_bounds(self): - # Getting real bounds does not change or copy them. - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - result = coord.bounds - self.assertArraysShareData( - result, - self.bds_real, - "Bounds do not share data with the provided array.", - ) - - def test_lazy_bounds(self): - # Getting lazy bounds realises them. - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - self.assertTrue(coord.has_lazy_bounds()) - result = coord.bounds - self.assertFalse(coord.has_lazy_bounds()) - self.assertEqualRealArraysAndDtypes(result, self.bds_real) - - def test_lazy_bounds_with_lazy_points(self): - # Getting lazy bounds does not fetch the points. - coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy) - coord.bounds - self.assertTrue(coord.has_lazy_points()) - - -class Test_bounds__setter(tests.IrisTest, AuxCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_set_real_bounds(self): - # Setting new real bounds does not make a copy. - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - new_bounds = self.bds_real + 102.3 - coord.bounds = new_bounds - result = coord.core_bounds() - self.assertArraysShareData( - result, - new_bounds, - "Bounds do not share data with the assigned array.", - ) - - def test_fail_bad_shape(self): - # Setting real points requires matching shape. - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - msg = "must be compatible with points shape" - with self.assertRaisesRegex(ValueError, msg): - coord.bounds = np.array([1.0, 2.0, 3.0]) - - def test_set_lazy_bounds(self): - # Setting new lazy bounds. - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - new_bounds = self.bds_lazy + 102.3 - coord.bounds = new_bounds - result = coord.core_bounds() - self.assertEqualLazyArraysAndDtypes(result, new_bounds) - - def test_set_bounds_with_lazy_points(self): - # Setting bounds does not change lazy points. - coord = AuxCoord(self.pts_lazy, bounds=self.bds_real) - new_bounds = self.bds_real + 102.3 - coord.bounds = new_bounds - self.assertTrue(coord.has_lazy_points()) - - -class Test_convert_units(tests.IrisTest): - def test_preserves_lazy(self): - test_bounds = np.array( - [ - [[11.0, 12.0], [12.0, 13.0], [13.0, 14.0]], - [[21.0, 22.0], [22.0, 23.0], [23.0, 24.0]], - ] - ) - test_points = np.array([[11.1, 12.2, 13.3], [21.4, 22.5, 23.6]]) - lazy_points = as_lazy_data(test_points) - lazy_bounds = as_lazy_data(test_bounds) - coord = AuxCoord(points=lazy_points, bounds=lazy_bounds, units="m") - coord.convert_units("ft") - self.assertTrue(coord.has_lazy_points()) - self.assertTrue(coord.has_lazy_bounds()) - test_points_ft = Unit("m").convert(test_points, "ft") - test_bounds_ft = Unit("m").convert(test_bounds, "ft") - self.assertArrayAllClose(coord.points, test_points_ft) - self.assertArrayAllClose(coord.bounds, test_bounds_ft) - - -class TestEquality(tests.IrisTest): - def test_nanpoints_eq_self(self): - co1 = AuxCoord([1.0, np.nan, 2.0]) - self.assertEqual(co1, co1) - - def test_nanpoints_eq_copy(self): - co1 = AuxCoord([1.0, np.nan, 2.0]) - co2 = co1.copy() - self.assertEqual(co1, co2) - - def test_nanbounds_eq_self(self): - co1 = AuxCoord([15.0, 25.0], bounds=[[14.0, 16.0], [24.0, np.nan]]) - self.assertEqual(co1, co1) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/test_Cell.py b/lib/iris/tests/unit/coords/test_Cell.py deleted file mode 100644 index 81370bd0de..0000000000 --- a/lib/iris/tests/unit/coords/test_Cell.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coords.Cell` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import datetime -from unittest import mock - -import cftime -import numpy as np - -from iris.coords import Cell -from iris.time import PartialDateTime - - -class Test___common_cmp__(tests.IrisTest): - def assert_raises_on_comparison(self, cell, other, exception_type, regexp): - with self.assertRaisesRegex(exception_type, regexp): - cell < other - with self.assertRaisesRegex(exception_type, regexp): - cell <= other - with self.assertRaisesRegex(exception_type, regexp): - cell > other - with self.assertRaisesRegex(exception_type, regexp): - cell >= other - - def test_PartialDateTime_bounded_cell(self): - # Check that bounded comparisions to a PartialDateTime - # raise an exception. These are not supported as they - # depend on the calendar. - dt = PartialDateTime(month=6) - cell = Cell( - datetime.datetime(2010, 1, 1), - bound=[ - datetime.datetime(2010, 1, 1), - datetime.datetime(2011, 1, 1), - ], - ) - self.assert_raises_on_comparison( - cell, dt, TypeError, "bounded region for datetime" - ) - - def test_PartialDateTime_unbounded_cell(self): - # Check that cell comparison works with PartialDateTimes. - dt = PartialDateTime(month=6) - cell = Cell(cftime.datetime(2010, 3, 1)) - self.assertLess(cell, dt) - self.assertGreater(dt, cell) - self.assertLessEqual(cell, dt) - self.assertGreaterEqual(dt, cell) - - def test_datetime_unbounded_cell(self): - # Check that cell comparison works with datetimes. - dt = datetime.datetime(2000, 6, 15) - cell = Cell(cftime.datetime(2000, 1, 1)) - self.assertGreater(dt, cell) - self.assertGreaterEqual(dt, cell) - self.assertLess(cell, dt) - self.assertLessEqual(cell, dt) - - def test_0D_numpy_array(self): - # Check that cell comparison works with 0D numpy arrays - - cell = Cell(1.3) - - self.assertGreater(np.array(1.5), cell) - self.assertLess(np.array(1.1), cell) - self.assertGreaterEqual(np.array(1.3), cell) - self.assertLessEqual(np.array(1.3), cell) - - def test_len_1_numpy_array(self): - # Check that cell comparison works with numpy arrays of len=1 - - cell = Cell(1.3) - - self.assertGreater(np.array([1.5]), cell) - self.assertLess(np.array([1.1]), cell) - self.assertGreaterEqual(np.array([1.3]), cell) - self.assertLessEqual(np.array([1.3]), cell) - - -class Test___eq__(tests.IrisTest): - def test_datetimelike(self): - # Check that cell equality works with objects with a "timetuple". - dt = mock.Mock(timetuple=mock.Mock()) - cell = mock.MagicMock( - spec=Cell, point=datetime.datetime(2010, 3, 21), bound=None - ) - _ = cell == dt - cell.__eq__.assert_called_once_with(dt) - - def test_datetimelike_bounded_cell(self): - # Check that equality with a datetime-like bounded cell - # raises an error. This is not supported as it - # depends on the calendar which is not always known from - # the datetime-like bound objects. - other = mock.Mock(timetuple=mock.Mock()) - cell = Cell( - point=object(), - bound=[ - mock.Mock(timetuple=mock.Mock()), - mock.Mock(timetuple=mock.Mock()), - ], - ) - with self.assertRaisesRegex(TypeError, "bounded region for datetime"): - cell == other - - def test_PartialDateTime_other(self): - cell = Cell(datetime.datetime(2010, 3, 2)) - # A few simple cases. - self.assertEqual(cell, PartialDateTime(month=3)) - self.assertNotEqual(cell, PartialDateTime(month=3, hour=12)) - self.assertNotEqual(cell, PartialDateTime(month=4)) - - -class Test_contains_point(tests.IrisTest): - def test_datetimelike_bounded_cell(self): - point = object() - cell = Cell( - point=object(), - bound=[ - mock.Mock(timetuple=mock.Mock()), - mock.Mock(timetuple=mock.Mock()), - ], - ) - with self.assertRaisesRegex(TypeError, "bounded region for datetime"): - cell.contains_point(point) - - def test_datetimelike_point(self): - point = mock.Mock(timetuple=mock.Mock()) - cell = Cell(point=object(), bound=[object(), object()]) - with self.assertRaisesRegex(TypeError, "bounded region for datetime"): - cell.contains_point(point) - - -class Test_numpy_comparison(tests.IrisTest): - """ - Unit tests to check that the results of comparisons with numpy types can be - used as truth values.""" - - def test_cell_lhs(self): - cell = Cell(point=1.5) - n = np.float64(1.2) - - try: - bool(cell < n) - bool(cell <= n) - bool(cell > n) - bool(cell >= n) - bool(cell == n) - bool(cell != n) - except: # noqa - self.fail( - "Result of comparison could not be used as a truth value" - ) - - def test_cell_rhs(self): - cell = Cell(point=1.5) - n = np.float64(1.2) - - try: - bool(n < cell) - bool(n <= cell) - bool(n > cell) - bool(n >= cell) - bool(n == cell) - bool(n != cell) - except: # noqa - self.fail( - "Result of comparison could not be used as a truth value" - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/test_CellMeasure.py b/lib/iris/tests/unit/coords/test_CellMeasure.py deleted file mode 100644 index 0bd66c6e98..0000000000 --- a/lib/iris/tests/unit/coords/test_CellMeasure.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coords.CellMeasure` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris._lazy_data import as_lazy_data -from iris.coords import CellMeasure -from iris.cube import Cube - - -class Tests(tests.IrisTest): - def setUp(self): - self.values = np.array((10.0, 12.0, 16.0, 9.0)) - self.measure = CellMeasure( - self.values, - units="m^2", - standard_name="cell_area", - long_name="measured_area", - var_name="area", - attributes={"notes": "1m accuracy"}, - ) - - def test_invalid_measure(self): - msg = "measure must be 'area' or 'volume', got 'length'" - with self.assertRaisesRegex(ValueError, msg): - self.measure.measure = "length" - - def test_set_measure(self): - v = "volume" - self.measure.measure = v - self.assertEqual(self.measure.measure, v) - - def test_data(self): - self.assertArrayEqual(self.measure.data, self.values) - - def test_set_data(self): - new_vals = np.array((1.0, 2.0, 3.0, 4.0)) - self.measure.data = new_vals - self.assertArrayEqual(self.measure.data, new_vals) - - def test_set_data__int(self): - new_vals = np.array((1, 2, 3, 4), dtype=np.int32) - self.measure.data = new_vals - self.assertArrayEqual(self.measure.data, new_vals) - - def test_set_data__uint(self): - new_vals = np.array((1, 2, 3, 4), dtype=np.uint32) - self.measure.data = new_vals - self.assertArrayEqual(self.measure.data, new_vals) - - def test_set_data__lazy(self): - new_vals = as_lazy_data(np.array((1.0, 2.0, 3.0, 4.0))) - self.measure.data = new_vals - self.assertArrayEqual(self.measure.data, new_vals) - - def test_data_different_shape(self): - new_vals = np.array((1.0, 2.0, 3.0)) - msg = "Require data with shape." - with self.assertRaisesRegex(ValueError, msg): - self.measure.data = new_vals - - def test_shape(self): - self.assertEqual(self.measure.shape, (4,)) - - def test_ndim(self): - self.assertEqual(self.measure.ndim, 1) - - def test___getitem__(self): - sub_measure = self.measure[2] - self.assertArrayEqual(self.values[2], sub_measure.data) - - def test___getitem__data_copy(self): - # Check that a sliced cell measure has independent data. - sub_measure = self.measure[1:3] - old_values = sub_measure.data.copy() - # Change the original one. - self.measure.data[:] = 0.0 - # Check the new one has not changed. - self.assertArrayEqual(sub_measure.data, old_values) - - def test_copy(self): - new_vals = np.array((7.0, 8.0)) - copy_measure = self.measure.copy(new_vals) - self.assertArrayEqual(copy_measure.data, new_vals) - - def test___str__(self): - expected = "\n".join( - [ - "CellMeasure : cell_area / (m^2)", - " data: [10., 12., 16., 9.]", - " shape: (4,)", - " dtype: float64", - " standard_name: 'cell_area'", - " long_name: 'measured_area'", - " var_name: 'area'", - " attributes:", - " notes '1m accuracy'", - " measure: 'area'", - ] - ) - self.assertEqual(self.measure.__str__(), expected) - - def test___repr__(self): - expected = ( - "" - ) - self.assertEqual(expected, self.measure.__repr__()) - - def test__eq__(self): - self.assertEqual(self.measure, self.measure) - - -class Test_cube_dims(tests.IrisTest): - def test(self): - # Check that "coord.cube_dims(cube)" calls "cube.coord_dims(coord)". - mock_dims_result = mock.sentinel.CM_DIMS - mock_dims_call = mock.Mock(return_value=mock_dims_result) - mock_cube = mock.Mock(Cube, cell_measure_dims=mock_dims_call) - test_cm = CellMeasure([1], long_name="test_name") - - result = test_cm.cube_dims(mock_cube) - self.assertEqual(result, mock_dims_result) - self.assertEqual(mock_dims_call.call_args_list, [mock.call(test_cm)]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/test_CellMethod.py b/lib/iris/tests/unit/coords/test_CellMethod.py deleted file mode 100644 index b10fd41834..0000000000 --- a/lib/iris/tests/unit/coords/test_CellMethod.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.coords.CellMethod`. -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.common import BaseMetadata -from iris.coords import AuxCoord, CellMethod - - -class Test(tests.IrisTest): - def setUp(self): - self.method = "mean" - - def _check(self, token, coord, default=False): - result = CellMethod(self.method, coords=coord) - token = token if not default else BaseMetadata.DEFAULT_NAME - expected = "{}: {}".format(self.method, token) - self.assertEqual(str(result), expected) - - def test_coord_standard_name(self): - token = "air_temperature" - coord = AuxCoord(1, standard_name=token) - self._check(token, coord) - - def test_coord_long_name(self): - token = "long_name" - coord = AuxCoord(1, long_name=token) - self._check(token, coord) - - def test_coord_long_name_default(self): - token = "long name" # includes space - coord = AuxCoord(1, long_name=token) - self._check(token, coord, default=True) - - def test_coord_var_name(self): - token = "var_name" - coord = AuxCoord(1, var_name=token) - self._check(token, coord) - - def test_coord_var_name_fail(self): - token = "var name" # includes space - emsg = "is not a valid NetCDF variable name" - with self.assertRaisesRegex(ValueError, emsg): - AuxCoord(1, var_name=token) - - def test_coord_stash(self): - token = "stash" - coord = AuxCoord(1, attributes=dict(STASH=token)) - self._check(token, coord, default=True) - - def test_coord_stash_default(self): - token = "_stash" # includes leading underscore - coord = AuxCoord(1, attributes=dict(STASH=token)) - self._check(token, coord, default=True) - - def test_string(self): - token = "air_temperature" - result = CellMethod(self.method, coords=token) - expected = "{}: {}".format(self.method, token) - self.assertEqual(str(result), expected) - - def test_string_default(self): - token = "air temperature" # includes space - result = CellMethod(self.method, coords=token) - expected = "{}: unknown".format(self.method) - self.assertEqual(str(result), expected) - - def test_mixture(self): - token = "air_temperature" - coord = AuxCoord(1, standard_name=token) - result = CellMethod(self.method, coords=[coord, token]) - expected = "{}: {}, {}".format(self.method, token, token) - self.assertEqual(str(result), expected) - - def test_mixture_default(self): - token = "air temperature" # includes space - coord = AuxCoord(1, long_name=token) - result = CellMethod(self.method, coords=[coord, token]) - expected = "{}: unknown, unknown".format(self.method) - self.assertEqual(str(result), expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/test_Coord.py b/lib/iris/tests/unit/coords/test_Coord.py deleted file mode 100644 index 72a48437ec..0000000000 --- a/lib/iris/tests/unit/coords/test_Coord.py +++ /dev/null @@ -1,1179 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coords.Coord` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import collections -from unittest import mock -import warnings - -import dask.array as da -import numpy as np - -import iris -from iris.coords import AuxCoord, Coord, DimCoord -from iris.cube import Cube -from iris.exceptions import UnitConversionError -from iris.tests.unit.coords import CoordTestMixin - -Pair = collections.namedtuple("Pair", "points bounds") - - -class Test_nearest_neighbour_index__ascending(tests.IrisTest): - def setUp(self): - points = [0.0, 90.0, 180.0, 270.0] - self.coord = DimCoord(points, circular=False, units="degrees") - - def _test_nearest_neighbour_index( - self, target, bounds=None, circular=False - ): - _bounds = [[-20, 10], [10, 100], [100, 260], [260, 340]] - ext_pnts = [-70, -10, 110, 275, 370] - if bounds is True: - self.coord.bounds = _bounds - else: - self.coord.bounds = bounds - self.coord.circular = circular - results = [self.coord.nearest_neighbour_index(ind) for ind in ext_pnts] - self.assertEqual(results, target) - - def test_nobounds(self): - target = [0, 0, 1, 3, 3] - self._test_nearest_neighbour_index(target) - - def test_nobounds_circular(self): - target = [3, 0, 1, 3, 0] - self._test_nearest_neighbour_index(target, circular=True) - - def test_bounded(self): - target = [0, 0, 2, 3, 3] - self._test_nearest_neighbour_index(target, bounds=True) - - def test_bounded_circular(self): - target = [3, 0, 2, 3, 0] - self._test_nearest_neighbour_index(target, bounds=True, circular=True) - - def test_bounded_overlapping(self): - _bounds = [[-20, 50], [10, 150], [100, 300], [260, 340]] - target = [0, 0, 1, 2, 3] - self._test_nearest_neighbour_index(target, bounds=_bounds) - - def test_bounded_disjointed(self): - _bounds = [[-20, 10], [80, 170], [180, 190], [240, 340]] - target = [0, 0, 1, 3, 3] - self._test_nearest_neighbour_index(target, bounds=_bounds) - - def test_scalar(self): - self.coord = DimCoord([0], circular=False, units="degrees") - target = [0, 0, 0, 0, 0] - self._test_nearest_neighbour_index(target) - - def test_bounded_float_point(self): - coord = DimCoord(1, bounds=[0, 2]) - result = coord.nearest_neighbour_index(2.5) - self.assertEqual(result, 0) - - -class Test_nearest_neighbour_index__descending(tests.IrisTest): - def setUp(self): - points = [270.0, 180.0, 90.0, 0.0] - self.coord = DimCoord(points, circular=False, units="degrees") - - def _test_nearest_neighbour_index( - self, target, bounds=False, circular=False - ): - _bounds = [[340, 260], [260, 100], [100, 10], [10, -20]] - ext_pnts = [-70, -10, 110, 275, 370] - if bounds: - self.coord.bounds = _bounds - self.coord.circular = circular - results = [self.coord.nearest_neighbour_index(ind) for ind in ext_pnts] - self.assertEqual(results, target) - - def test_nobounds(self): - target = [3, 3, 2, 0, 0] - self._test_nearest_neighbour_index(target) - - def test_nobounds_circular(self): - target = [0, 3, 2, 0, 3] - self._test_nearest_neighbour_index(target, circular=True) - - def test_bounded(self): - target = [3, 3, 1, 0, 0] - self._test_nearest_neighbour_index(target, bounds=True) - - def test_bounded_circular(self): - target = [0, 3, 1, 0, 3] - self._test_nearest_neighbour_index(target, bounds=True, circular=True) - - -class Test_guess_bounds(tests.IrisTest): - def setUp(self): - self.coord = DimCoord( - np.array([-160, -120, 0, 30, 150, 170]), - units="degree", - standard_name="longitude", - circular=True, - ) - - def test_non_circular(self): - self.coord.circular = False - self.coord.guess_bounds() - target = np.array( - [ - [-180.0, -140.0], - [-140.0, -60.0], - [-60.0, 15.0], - [15.0, 90.0], - [90.0, 160.0], - [160.0, 180.0], - ] - ) - self.assertArrayEqual(target, self.coord.bounds) - - def test_circular_increasing(self): - self.coord.guess_bounds() - target = np.array( - [ - [-175.0, -140.0], - [-140.0, -60.0], - [-60.0, 15.0], - [15.0, 90.0], - [90.0, 160.0], - [160.0, 185.0], - ] - ) - self.assertArrayEqual(target, self.coord.bounds) - - def test_circular_decreasing(self): - self.coord.points = self.coord.points[::-1] - self.coord.guess_bounds() - target = np.array( - [ - [185.0, 160.0], - [160.0, 90.0], - [90.0, 15.0], - [15.0, -60.0], - [-60.0, -140.0], - [-140.0, -175.0], - ] - ) - self.assertArrayEqual(target, self.coord.bounds) - - def test_circular_increasing_alt_range(self): - self.coord.points = np.array([10, 30, 90, 150, 210, 220]) - self.coord.guess_bounds() - target = np.array( - [ - [-65.0, 20.0], - [20.0, 60.0], - [60.0, 120.0], - [120.0, 180.0], - [180.0, 215.0], - [215.0, 295.0], - ] - ) - self.assertArrayEqual(target, self.coord.bounds) - - def test_circular_decreasing_alt_range(self): - self.coord.points = np.array([10, 30, 90, 150, 210, 220])[::-1] - self.coord.guess_bounds() - target = np.array( - [ - [295, 215], - [215, 180], - [180, 120], - [120, 60], - [60, 20], - [20, -65], - ] - ) - self.assertArrayEqual(target, self.coord.bounds) - - -class Test_guess_bounds__default_enabled_latitude_clipping(tests.IrisTest): - def test_all_inside(self): - lat = DimCoord([-10, 0, 20], units="degree", standard_name="latitude") - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-15, -5], [-5, 10], [10, 30]]) - - def test_points_inside_bounds_outside(self): - lat = DimCoord([-80, 0, 70], units="degree", standard_name="latitude") - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-90, -40], [-40, 35], [35, 90]]) - - def test_points_inside_bounds_outside_grid_latitude(self): - lat = DimCoord( - [-80, 0, 70], units="degree", standard_name="grid_latitude" - ) - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-90, -40], [-40, 35], [35, 90]]) - - def test_points_to_edges_bounds_outside(self): - lat = DimCoord([-90, 0, 90], units="degree", standard_name="latitude") - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-90, -45], [-45, 45], [45, 90]]) - - def test_points_outside(self): - lat = DimCoord( - [-100, 0, 120], units="degree", standard_name="latitude" - ) - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-150, -50], [-50, 60], [60, 180]]) - - def test_points_inside_bounds_outside_wrong_unit(self): - lat = DimCoord([-80, 0, 70], units="feet", standard_name="latitude") - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]]) - - def test_points_inside_bounds_outside_wrong_name(self): - lat = DimCoord([-80, 0, 70], units="degree", standard_name="longitude") - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]]) - - def test_points_inside_bounds_outside_wrong_name_2(self): - lat = DimCoord( - [-80, 0, 70], units="degree", long_name="other_latitude" - ) - lat.guess_bounds() - self.assertArrayEqual(lat.bounds, [[-120, -40], [-40, 35], [35, 105]]) - - -class Test_cell(tests.IrisTest): - def _mock_coord(self): - coord = mock.Mock( - spec=Coord, - ndim=1, - points=np.array([mock.sentinel.time]), - bounds=np.array([[mock.sentinel.lower, mock.sentinel.upper]]), - ) - return coord - - def test_time_as_object(self): - # Ensure Coord.cell() converts the point/bound values to - # "datetime" objects. - coord = self._mock_coord() - coord.units.num2date = mock.Mock( - side_effect=[ - mock.sentinel.datetime, - (mock.sentinel.datetime_lower, mock.sentinel.datetime_upper), - ] - ) - cell = Coord.cell(coord, 0) - self.assertIs(cell.point, mock.sentinel.datetime) - self.assertEqual( - cell.bound, - (mock.sentinel.datetime_lower, mock.sentinel.datetime_upper), - ) - self.assertEqual( - coord.units.num2date.call_args_list, - [ - mock.call((mock.sentinel.time,)), - mock.call((mock.sentinel.lower, mock.sentinel.upper)), - ], - ) - - -class Test_collapsed(tests.IrisTest, CoordTestMixin): - def test_serialize(self): - # Collapse a string AuxCoord, causing it to be serialised. - string = Pair( - np.array(["two", "four", "six", "eight"]), - np.array( - [ - ["one", "three"], - ["three", "five"], - ["five", "seven"], - ["seven", "nine"], - ] - ), - ) - string_nobounds = Pair(np.array(["ecks", "why", "zed"]), None) - string_multi = Pair( - np.array(["three", "six", "nine"]), - np.array( - [ - ["one", "two", "four", "five"], - ["four", "five", "seven", "eight"], - ["seven", "eight", "ten", "eleven"], - ] - ), - ) - - def _serialize(data): - return "|".join(str(item) for item in data.flatten()) - - for units in ["unknown", "no_unit"]: - for points, bounds in [string, string_nobounds, string_multi]: - coord = AuxCoord(points=points, bounds=bounds, units=units) - collapsed_coord = coord.collapsed() - self.assertArrayEqual( - collapsed_coord.points, _serialize(points) - ) - if bounds is not None: - for index in np.ndindex(bounds.shape[1:]): - index_slice = (slice(None),) + tuple(index) - self.assertArrayEqual( - collapsed_coord.bounds[index_slice], - _serialize(bounds[index_slice]), - ) - - def test_dim_1d(self): - # Numeric coords should not be serialised. - coord = DimCoord( - points=np.array([2, 4, 6, 8]), - bounds=np.array([[1, 3], [3, 5], [5, 7], [7, 9]]), - ) - for units in ["unknown", "no_unit", 1, "K"]: - coord.units = units - with self.assertNoWarningsRegexp(): - collapsed_coord = coord.collapsed() - self.assertArrayEqual( - collapsed_coord.points, np.mean(coord.points) - ) - self.assertArrayEqual( - collapsed_coord.bounds, - [[coord.bounds.min(), coord.bounds.max()]], - ) - - def test_lazy_points(self): - # Lazy points should stay lazy after collapse. - coord = AuxCoord(points=da.from_array(np.arange(5), chunks=5)) - collapsed_coord = coord.collapsed() - self.assertTrue(collapsed_coord.has_lazy_bounds()) - self.assertTrue(collapsed_coord.has_lazy_points()) - - def test_numeric_nd(self): - coord = AuxCoord( - points=np.array([[1, 2, 4, 5], [4, 5, 7, 8], [7, 8, 10, 11]]) - ) - - collapsed_coord = coord.collapsed() - self.assertArrayEqual(collapsed_coord.points, np.array([6])) - self.assertArrayEqual(collapsed_coord.bounds, np.array([[1, 11]])) - - # Test partially collapsing one dimension... - collapsed_coord = coord.collapsed(1) - self.assertArrayEqual( - collapsed_coord.points, np.array([3.0, 6.0, 9.0]) - ) - self.assertArrayEqual( - collapsed_coord.bounds, np.array([[1, 5], [4, 8], [7, 11]]) - ) - - # ... and the other - collapsed_coord = coord.collapsed(0) - self.assertArrayEqual(collapsed_coord.points, np.array([4, 5, 7, 8])) - self.assertArrayEqual( - collapsed_coord.bounds, - np.array([[1, 7], [2, 8], [4, 10], [5, 11]]), - ) - - def test_numeric_nd_bounds_all(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - - collapsed_coord = coord.collapsed() - self.assertArrayEqual(collapsed_coord.points, np.array([55])) - self.assertArrayEqual(collapsed_coord.bounds, np.array([[-2, 112]])) - - def test_numeric_nd_bounds_second(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - collapsed_coord = coord.collapsed(1) - self.assertArrayEqual(collapsed_coord.points, np.array([15, 55, 95])) - self.assertArrayEqual( - collapsed_coord.bounds, np.array([[-2, 32], [38, 72], [78, 112]]) - ) - - def test_numeric_nd_bounds_first(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - # ... and the other.. - collapsed_coord = coord.collapsed(0) - self.assertArrayEqual( - collapsed_coord.points, np.array([40, 50, 60, 70]) - ) - self.assertArrayEqual( - collapsed_coord.bounds, - np.array([[-2, 82], [8, 92], [18, 102], [28, 112]]), - ) - - def test_numeric_nd_bounds_last(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_real) - # ... and again with -ve dimension specification. - collapsed_coord = coord.collapsed(-1) - self.assertArrayEqual(collapsed_coord.points, np.array([15, 55, 95])) - self.assertArrayEqual( - collapsed_coord.bounds, np.array([[-2, 32], [38, 72], [78, 112]]) - ) - - def test_lazy_nd_bounds_all(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - - collapsed_coord = coord.collapsed() - - # Note that the new points get recalculated from the lazy bounds - # and so end up as lazy - self.assertTrue(collapsed_coord.has_lazy_points()) - self.assertTrue(collapsed_coord.has_lazy_bounds()) - - self.assertArrayEqual(collapsed_coord.points, np.array([55])) - self.assertArrayEqual(collapsed_coord.bounds, da.array([[-2, 112]])) - - def test_lazy_nd_bounds_second(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - - collapsed_coord = coord.collapsed(1) - self.assertArrayEqual(collapsed_coord.points, np.array([15, 55, 95])) - self.assertArrayEqual( - collapsed_coord.bounds, np.array([[-2, 32], [38, 72], [78, 112]]) - ) - - def test_lazy_nd_bounds_first(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - - collapsed_coord = coord.collapsed(0) - self.assertArrayEqual( - collapsed_coord.points, np.array([40, 50, 60, 70]) - ) - self.assertArrayEqual( - collapsed_coord.bounds, - np.array([[-2, 82], [8, 92], [18, 102], [28, 112]]), - ) - - def test_lazy_nd_bounds_last(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_lazy) - - collapsed_coord = coord.collapsed(-1) - self.assertArrayEqual(collapsed_coord.points, np.array([15, 55, 95])) - self.assertArrayEqual( - collapsed_coord.bounds, np.array([[-2, 32], [38, 72], [78, 112]]) - ) - - def test_lazy_nd_points_and_bounds(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy) - - collapsed_coord = coord.collapsed() - - self.assertTrue(collapsed_coord.has_lazy_points()) - self.assertTrue(collapsed_coord.has_lazy_bounds()) - - self.assertArrayEqual(collapsed_coord.points, da.array([55])) - self.assertArrayEqual(collapsed_coord.bounds, da.array([[-2, 112]])) - - def test_numeric_nd_multidim_bounds_warning(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y") - - msg = ( - "Collapsing a multi-dimensional coordinate. " - "Metadata may not be fully descriptive for 'y'." - ) - with self.assertWarnsRegex(UserWarning, msg): - coord.collapsed() - - def test_lazy_nd_multidim_bounds_warning(self): - self.setupTestArrays((3, 4)) - coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y") - - msg = ( - "Collapsing a multi-dimensional coordinate. " - "Metadata may not be fully descriptive for 'y'." - ) - with self.assertWarnsRegex(UserWarning, msg): - coord.collapsed() - - def test_numeric_nd_noncontiguous_bounds_warning(self): - self.setupTestArrays((3)) - coord = AuxCoord(self.pts_real, bounds=self.bds_real, long_name="y") - - msg = ( - "Collapsing a non-contiguous coordinate. " - "Metadata may not be fully descriptive for 'y'." - ) - with self.assertWarnsRegex(UserWarning, msg): - coord.collapsed() - - def test_lazy_nd_noncontiguous_bounds_warning(self): - self.setupTestArrays((3)) - coord = AuxCoord(self.pts_lazy, bounds=self.bds_lazy, long_name="y") - - msg = ( - "Collapsing a non-contiguous coordinate. " - "Metadata may not be fully descriptive for 'y'." - ) - with self.assertWarnsRegex(UserWarning, msg): - coord.collapsed() - - def test_numeric_3_bounds(self): - points = np.array([2.0, 6.0, 4.0]) - bounds = np.array([[1.0, 0.0, 3.0], [5.0, 4.0, 7.0], [3.0, 2.0, 5.0]]) - - coord = AuxCoord(points, bounds=bounds, long_name="x") - - msg = ( - r"Cannot check if coordinate is contiguous: Invalid operation for " - r"'x', with 3 bound\(s\). Contiguous bounds are only defined for " - r"1D coordinates with 2 bounds. Metadata may not be fully " - r"descriptive for 'x'. Ignoring bounds." - ) - with self.assertWarnsRegex(UserWarning, msg): - collapsed_coord = coord.collapsed() - - self.assertFalse(collapsed_coord.has_lazy_points()) - self.assertFalse(collapsed_coord.has_lazy_bounds()) - - self.assertArrayAlmostEqual(collapsed_coord.points, np.array([4.0])) - self.assertArrayAlmostEqual( - collapsed_coord.bounds, np.array([[2.0, 6.0]]) - ) - - def test_lazy_3_bounds(self): - points = da.arange(3) * 2.0 - bounds = da.arange(3 * 3).reshape(3, 3) - - coord = AuxCoord(points, bounds=bounds, long_name="x") - - msg = ( - r"Cannot check if coordinate is contiguous: Invalid operation for " - r"'x', with 3 bound\(s\). Contiguous bounds are only defined for " - r"1D coordinates with 2 bounds. Metadata may not be fully " - r"descriptive for 'x'. Ignoring bounds." - ) - with self.assertWarnsRegex(UserWarning, msg): - collapsed_coord = coord.collapsed() - - self.assertTrue(collapsed_coord.has_lazy_points()) - self.assertTrue(collapsed_coord.has_lazy_bounds()) - - self.assertArrayAlmostEqual(collapsed_coord.points, da.array([2.0])) - self.assertArrayAlmostEqual( - collapsed_coord.bounds, da.array([[0.0, 4.0]]) - ) - - -class Test_is_compatible(tests.IrisTest): - def setUp(self): - self.test_coord = AuxCoord([1.0]) - self.other_coord = self.test_coord.copy() - - def test_noncommon_array_attrs_compatible(self): - # Non-common array attributes should be ok. - self.test_coord.attributes["array_test"] = np.array([1.0, 2, 3]) - self.assertTrue(self.test_coord.is_compatible(self.other_coord)) - - def test_matching_array_attrs_compatible(self): - # Matching array attributes should be ok. - self.test_coord.attributes["array_test"] = np.array([1.0, 2, 3]) - self.other_coord.attributes["array_test"] = np.array([1.0, 2, 3]) - self.assertTrue(self.test_coord.is_compatible(self.other_coord)) - - def test_different_array_attrs_incompatible(self): - # Differing array attributes should make coords incompatible. - self.test_coord.attributes["array_test"] = np.array([1.0, 2, 3]) - self.other_coord.attributes["array_test"] = np.array([1.0, 2, 777.7]) - self.assertFalse(self.test_coord.is_compatible(self.other_coord)) - - -class Test_contiguous_bounds(tests.IrisTest): - def test_1d_coord_no_bounds_warning(self): - coord = DimCoord([0, 1, 2], standard_name="latitude") - msg = ( - "Coordinate 'latitude' is not bounded, guessing contiguous " - "bounds." - ) - with warnings.catch_warnings(): - # Cause all warnings to raise Exceptions - warnings.simplefilter("error") - with self.assertRaisesRegex(Warning, msg): - coord.contiguous_bounds() - - def test_2d_coord_no_bounds_error(self): - coord = AuxCoord(np.array([[0, 0], [5, 5]]), standard_name="latitude") - emsg = "Guessing bounds of 2D coords is not currently supported" - with self.assertRaisesRegex(ValueError, emsg): - coord.contiguous_bounds() - - def test__sanity_check_bounds_call(self): - coord = DimCoord([5, 15, 25], bounds=[[0, 10], [10, 20], [20, 30]]) - with mock.patch( - "iris.coords.Coord._sanity_check_bounds" - ) as bounds_check: - coord.contiguous_bounds() - bounds_check.assert_called_once() - - def test_1d_coord(self): - coord = DimCoord( - [2, 4, 6], - standard_name="latitude", - bounds=[[1, 3], [3, 5], [5, 7]], - ) - expected = np.array([1, 3, 5, 7]) - result = coord.contiguous_bounds() - self.assertArrayEqual(result, expected) - - def test_1d_coord_discontiguous(self): - coord = DimCoord( - [2, 4, 6], - standard_name="latitude", - bounds=[[1, 3], [4, 5], [5, 7]], - ) - expected = np.array([1, 4, 5, 7]) - result = coord.contiguous_bounds() - self.assertArrayEqual(result, expected) - - def test_2d_lon_bounds(self): - coord = AuxCoord( - np.array([[1, 3], [1, 3]]), - bounds=np.array( - [[[0, 2, 2, 0], [2, 4, 4, 2]], [[0, 2, 2, 0], [2, 4, 4, 2]]] - ), - ) - expected = np.array([[0, 2, 4], [0, 2, 4], [0, 2, 4]]) - result = coord.contiguous_bounds() - self.assertArrayEqual(result, expected) - - def test_2d_lat_bounds(self): - coord = AuxCoord( - np.array([[1, 1], [3, 3]]), - bounds=np.array( - [[[0, 0, 2, 2], [0, 0, 2, 2]], [[2, 2, 4, 4], [2, 2, 4, 4]]] - ), - ) - expected = np.array([[0, 0, 0], [2, 2, 2], [4, 4, 4]]) - result = coord.contiguous_bounds() - self.assertArrayEqual(result, expected) - - -class Test_is_contiguous(tests.IrisTest): - def test_no_bounds(self): - coord = DimCoord([1, 3]) - result = coord.is_contiguous() - self.assertFalse(result) - - def test__discontiguity_in_bounds_call(self): - # Check that :meth:`iris.coords.Coord._discontiguity_in_bounds` is - # called. - coord = DimCoord([1, 3], bounds=[[0, 2], [2, 4]]) - with mock.patch( - "iris.coords.Coord._discontiguity_in_bounds" - ) as discontiguity_check: - # Discontiguity returns two objects that are unpacked in - # `coord.is_contiguous`. - discontiguity_check.return_value = [None, None] - coord.is_contiguous(rtol=1e-1, atol=1e-3) - discontiguity_check.assert_called_with(rtol=1e-1, atol=1e-3) - - -class Test__discontiguity_in_bounds(tests.IrisTest): - def setUp(self): - self.points_3by3 = np.array([[1, 2, 3], [1, 2, 3], [1, 2, 3]]) - self.lon_bounds_3by3 = np.array( - [ - [[0, 2, 2, 0], [2, 4, 4, 2], [4, 6, 6, 4]], - [[0, 2, 2, 0], [2, 4, 4, 2], [4, 6, 6, 4]], - [[0, 2, 2, 0], [2, 4, 4, 2], [4, 6, 6, 4]], - ] - ) - self.lat_bounds_3by3 = np.array( - [ - [[0, 0, 2, 2], [0, 0, 2, 2], [0, 0, 2, 2]], - [[2, 2, 4, 4], [2, 2, 4, 4], [2, 2, 4, 4]], - [[4, 4, 6, 6], [4, 4, 6, 6], [4, 4, 6, 6]], - ] - ) - - def test_1d_contiguous(self): - coord = DimCoord( - [-20, 0, 20], bounds=[[-30, -10], [-10, 10], [10, 30]] - ) - contiguous, diffs = coord._discontiguity_in_bounds() - self.assertTrue(contiguous) - self.assertArrayEqual(diffs, np.zeros(2)) - - def test_1d_discontiguous(self): - coord = DimCoord([10, 20, 40], bounds=[[5, 15], [15, 25], [35, 45]]) - contiguous, diffs = coord._discontiguity_in_bounds() - self.assertFalse(contiguous) - self.assertArrayEqual(diffs, np.array([0, 10])) - - def test_1d_one_cell(self): - # Test a 1D coord with a single cell. - coord = DimCoord(20, bounds=[[10, 30]]) - contiguous, diffs = coord._discontiguity_in_bounds() - self.assertTrue(contiguous) - self.assertArrayEqual(diffs, np.array([])) - - def test_2d_contiguous_both_dirs(self): - coord = AuxCoord(self.points_3by3, bounds=self.lon_bounds_3by3) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertTrue(contiguous) - self.assertTrue(not diffs_along_x.any()) - self.assertTrue(not diffs_along_y.any()) - - def test_2d_discontiguous_along_x(self): - coord = AuxCoord( - self.points_3by3[:, ::2], bounds=self.lon_bounds_3by3[:, ::2, :] - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertFalse(contiguous) - self.assertArrayEqual( - diffs_along_x, np.array([True, True, True]).reshape(3, 1) - ) - self.assertTrue(not diffs_along_y.any()) - - def test_2d_discontiguous_along_y(self): - coord = AuxCoord( - self.points_3by3[::2, :], bounds=self.lat_bounds_3by3[::2, :, :] - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertFalse(contiguous) - self.assertTrue(not diffs_along_x.any()) - self.assertArrayEqual(diffs_along_y, np.array([[True, True, True]])) - - def test_2d_discontiguous_along_x_and_y(self): - coord = AuxCoord( - np.array([[1, 5], [3, 5]]), - bounds=np.array( - [[[0, 2, 2, 0], [4, 6, 6, 4]], [[2, 4, 4, 2], [4, 6, 6, 4]]] - ), - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - exp_x_diffs = np.array([True, False]).reshape(2, 1) - exp_y_diffs = np.array([True, False]).reshape(1, 2) - self.assertFalse(contiguous) - self.assertArrayEqual(diffs_along_x, exp_x_diffs) - self.assertArrayEqual(diffs_along_y, exp_y_diffs) - - def test_2d_contiguous_along_x_atol(self): - coord = AuxCoord( - self.points_3by3[:, ::2], bounds=self.lon_bounds_3by3[:, ::2, :] - ) - # Set a high atol that allows small discontiguities. - contiguous, diffs = coord._discontiguity_in_bounds(atol=5) - diffs_along_x, diffs_along_y = diffs - self.assertTrue(contiguous) - self.assertArrayEqual( - diffs_along_x, np.array([False, False, False]).reshape(3, 1) - ) - self.assertTrue(not diffs_along_y.any()) - - def test_2d_one_cell(self): - # Test a 2D coord with a single cell, where the coord has shape (1, 1). - coord = AuxCoord( - self.points_3by3[:1, :1], bounds=self.lon_bounds_3by3[:1, :1, :] - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - expected_diffs = np.array([], dtype=np.int64) - self.assertTrue(contiguous) - self.assertArrayEqual(diffs_along_x, expected_diffs.reshape(1, 0)) - self.assertArrayEqual(diffs_along_y, expected_diffs.reshape(0, 1)) - - def test_2d_one_cell_along_x(self): - # Test a 2D coord with a single cell along the x axis, where the coord - # has shape (2, 1). - coord = AuxCoord( - self.points_3by3[:, :1], bounds=self.lat_bounds_3by3[:, :1, :] - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertTrue(contiguous) - self.assertTrue(not diffs_along_x.any()) - self.assertArrayEqual(diffs_along_y, np.array([0, 0]).reshape(2, 1)) - - def test_2d_one_cell_along_y(self): - # Test a 2D coord with a single cell along the y axis, where the coord - # has shape (1, 2). - coord = AuxCoord( - self.points_3by3[:1, :], bounds=self.lon_bounds_3by3[:1, :, :] - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertTrue(contiguous) - self.assertTrue(not diffs_along_x.any()) - self.assertTrue(not diffs_along_y.any()) - - def test_2d_contiguous_mod_360(self): - # Test that longitude coordinates are adjusted by the 360 modulus when - # calculating the discontiguities in contiguous bounds. - coord = AuxCoord( - [[175, -175], [175, -175]], - standard_name="longitude", - bounds=np.array( - [ - [[170, 180, 180, 170], [-180, -170, -170, -180]], - [[170, 180, 180, 170], [-180, -170, -170, -180]], - ] - ), - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertTrue(contiguous) - self.assertTrue(not diffs_along_x.any()) - self.assertTrue(not diffs_along_y.any()) - - def test_2d_discontiguous_mod_360(self): - # Test that longitude coordinates are adjusted by the 360 modulus when - # calculating the discontiguities in contiguous bounds. - coord = AuxCoord( - [[175, -175], [175, -175]], - standard_name="longitude", - bounds=np.array( - [ - [[170, 180, 180, 170], [10, 20, 20, 10]], - [[170, 180, 180, 170], [10, 20, 20, 10]], - ] - ), - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertFalse(contiguous) - self.assertArrayEqual(diffs_along_x, np.array([[True], [True]])) - self.assertTrue(not diffs_along_y.any()) - - def test_2d_contiguous_mod_360_not_longitude(self): - # Test that non-longitude coordinates are not adjusted by the 360 - # modulus when calculating the discontiguities in contiguous bounds. - coord = AuxCoord( - [[-150, 350], [-150, 350]], - standard_name="height", - bounds=np.array( - [ - [[-400, 100, 100, -400], [100, 600, 600, 100]], - [[-400, 100, 100, -400], [100, 600, 600, 100]], - ] - ), - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertTrue(contiguous) - self.assertTrue(not diffs_along_x.any()) - self.assertTrue(not diffs_along_y.any()) - - def test_2d_discontiguous_mod_360_not_longitude(self): - # Test that non-longitude coordinates are not adjusted by the 360 - # modulus when calculating the discontiguities in discontiguous bounds. - coord = AuxCoord( - [[-150, 350], [-150, 350]], - standard_name="height", - bounds=np.array( - [ - [[-400, 100, 100, -400], [200, 600, 600, 200]], - [[-400, 100, 100, -400], [200, 600, 600, 200]], - ] - ), - ) - contiguous, diffs = coord._discontiguity_in_bounds() - diffs_along_x, diffs_along_y = diffs - self.assertFalse(contiguous) - self.assertArrayEqual(diffs_along_x, np.array([[True], [True]])) - self.assertTrue(not diffs_along_y.any()) - - -class Test__sanity_check_bounds(tests.IrisTest): - def test_coord_1d_2_bounds(self): - # Check that a 1d coord with 2 bounds does not raise an error. - coord = iris.coords.DimCoord( - [0, 1], standard_name="latitude", bounds=[[0, 1], [1, 2]] - ) - coord._sanity_check_bounds() - - def test_coord_1d_no_bounds(self): - coord = iris.coords.DimCoord([0, 1], standard_name="latitude") - emsg = ( - "Contiguous bounds are only defined for 1D coordinates with " - "2 bounds." - ) - with self.assertRaisesRegex(ValueError, emsg): - coord._sanity_check_bounds() - - def test_coord_1d_1_bounds(self): - coord = iris.coords.DimCoord( - [0, 1], standard_name="latitude", bounds=np.array([[0], [1]]) - ) - emsg = ( - "Contiguous bounds are only defined for 1D coordinates with " - "2 bounds." - ) - with self.assertRaisesRegex(ValueError, emsg): - coord._sanity_check_bounds() - - def test_coord_2d_4_bounds(self): - coord = iris.coords.AuxCoord( - [[0, 0], [1, 1]], - standard_name="latitude", - bounds=np.array( - [[[0, 0, 1, 1], [0, 0, 1, 1]], [[1, 1, 2, 2], [1, 1, 2, 2]]] - ), - ) - coord._sanity_check_bounds() - - def test_coord_2d_no_bounds(self): - coord = iris.coords.AuxCoord( - [[0, 0], [1, 1]], standard_name="latitude" - ) - emsg = ( - "Contiguous bounds are only defined for 2D coordinates with " - "4 bounds." - ) - with self.assertRaisesRegex(ValueError, emsg): - coord._sanity_check_bounds() - - def test_coord_2d_2_bounds(self): - coord = iris.coords.AuxCoord( - [[0, 0], [1, 1]], - standard_name="latitude", - bounds=np.array([[[0, 1], [0, 1]], [[1, 2], [1, 2]]]), - ) - emsg = ( - "Contiguous bounds are only defined for 2D coordinates with " - "4 bounds." - ) - with self.assertRaisesRegex(ValueError, emsg): - coord._sanity_check_bounds() - - def test_coord_3d(self): - coord = iris.coords.AuxCoord( - np.zeros((2, 2, 2)), standard_name="height" - ) - emsg = ( - "Contiguous bounds are not defined for coordinates with more " - "than 2 dimensions." - ) - with self.assertRaisesRegex(ValueError, emsg): - coord._sanity_check_bounds() - - -class Test_convert_units(tests.IrisTest): - def test_convert_unknown_units(self): - coord = iris.coords.AuxCoord(1, units="unknown") - emsg = ( - "Cannot convert from unknown units. " - 'The "units" attribute may be set directly.' - ) - with self.assertRaisesRegex(UnitConversionError, emsg): - coord.convert_units("degrees") - - -class Test___str__(tests.IrisTest): - def test_short_time_interval(self): - coord = DimCoord( - [5], standard_name="time", units="days since 1970-01-01" - ) - expected = "\n".join( - [ - "DimCoord : time / (days since 1970-01-01, standard calendar)", - " points: [1970-01-06 00:00:00]", - " shape: (1,)", - " dtype: int64", - " standard_name: 'time'", - ] - ) - result = coord.__str__() - self.assertEqual(expected, result) - - def test_short_time_interval__bounded(self): - coord = DimCoord( - [5, 6], standard_name="time", units="days since 1970-01-01" - ) - coord.guess_bounds() - expected = "\n".join( - [ - "DimCoord : time / (days since 1970-01-01, standard calendar)", - " points: [1970-01-06 00:00:00, 1970-01-07 00:00:00]", - " bounds: [", - " [1970-01-05 12:00:00, 1970-01-06 12:00:00],", - " [1970-01-06 12:00:00, 1970-01-07 12:00:00]]", - " shape: (2,) bounds(2, 2)", - " dtype: int64", - " standard_name: 'time'", - ] - ) - result = coord.__str__() - self.assertEqual(expected, result) - - def test_long_time_interval(self): - coord = DimCoord( - [5], standard_name="time", units="years since 1970-01-01" - ) - expected = "\n".join( - [ - "DimCoord : time / (years since 1970-01-01, standard calendar)", - " points: [5]", - " shape: (1,)", - " dtype: int64", - " standard_name: 'time'", - ] - ) - result = coord.__str__() - self.assertEqual(expected, result) - - def test_long_time_interval__bounded(self): - coord = DimCoord( - [5, 6], standard_name="time", units="years since 1970-01-01" - ) - coord.guess_bounds() - expected = "\n".join( - [ - "DimCoord : time / (years since 1970-01-01, standard calendar)", - " points: [5, 6]", - " bounds: [", - " [4.5, 5.5],", - " [5.5, 6.5]]", - " shape: (2,) bounds(2, 2)", - " dtype: int64", - " standard_name: 'time'", - ] - ) - result = coord.__str__() - self.assertEqual(expected, result) - - def test_non_time_unit(self): - coord = DimCoord([1.0]) - expected = "\n".join( - [ - "DimCoord : unknown / (unknown)", - " points: [1.]", - " shape: (1,)", - " dtype: float64", - ] - ) - result = coord.__str__() - self.assertEqual(expected, result) - - -class TestClimatology(tests.IrisTest): - # Variety of tests for the climatological property of a coord. - # Only using AuxCoord since there is no different behaviour between Aux - # and DimCoords for this property. - - def test_create(self): - coord = AuxCoord( - points=[0, 1], - bounds=[[0, 1], [1, 2]], - units="days since 1970-01-01", - climatological=True, - ) - self.assertTrue(coord.climatological) - - def test_create_no_bounds_no_set(self): - with self.assertRaisesRegex(ValueError, "Cannot set.*no bounds exist"): - AuxCoord( - points=[0, 1], - units="days since 1970-01-01", - climatological=True, - ) - - def test_create_no_time_no_set(self): - emsg = "Cannot set climatological .* valid time reference units.*" - with self.assertRaisesRegex(TypeError, emsg): - AuxCoord( - points=[0, 1], bounds=[[0, 1], [1, 2]], climatological=True - ) - - def test_absent(self): - coord = AuxCoord(points=[0, 1], bounds=[[0, 1], [1, 2]]) - self.assertFalse(coord.climatological) - - def test_absent_no_bounds_no_set(self): - coord = AuxCoord(points=[0, 1], units="days since 1970-01-01") - with self.assertRaisesRegex(ValueError, "Cannot set.*no bounds exist"): - coord.climatological = True - - def test_absent_no_time_no_set(self): - coord = AuxCoord(points=[0, 1], bounds=[[0, 1], [1, 2]]) - emsg = "Cannot set climatological .* valid time reference units.*" - with self.assertRaisesRegex(TypeError, emsg): - coord.climatological = True - - def test_absent_no_bounds_unset(self): - coord = AuxCoord(points=[0, 1]) - coord.climatological = False - self.assertFalse(coord.climatological) - - def test_bounds_set(self): - coord = AuxCoord( - points=[0, 1], - bounds=[[0, 1], [1, 2]], - units="days since 1970-01-01", - ) - coord.climatological = True - self.assertTrue(coord.climatological) - - def test_bounds_unset(self): - coord = AuxCoord( - points=[0, 1], - bounds=[[0, 1], [1, 2]], - units="days since 1970-01-01", - climatological=True, - ) - coord.climatological = False - self.assertFalse(coord.climatological) - - def test_remove_bounds(self): - coord = AuxCoord( - points=[0, 1], - bounds=[[0, 1], [1, 2]], - units="days since 1970-01-01", - climatological=True, - ) - coord.bounds = None - self.assertFalse(coord.climatological) - - def test_change_units(self): - coord = AuxCoord( - points=[0, 1], - bounds=[[0, 1], [1, 2]], - units="days since 1970-01-01", - climatological=True, - ) - self.assertTrue(coord.climatological) - coord.units = "K" - self.assertFalse(coord.climatological) - - -class Test___init____abstractmethod(tests.IrisTest): - def test(self): - emsg = ( - "Can't instantiate abstract class Coord with abstract" - " method.* __init__" - ) - with self.assertRaisesRegex(TypeError, emsg): - _ = Coord(points=[0, 1]) - - -class Test_cube_dims(tests.IrisTest): - def test(self): - # Check that "coord.cube_dims(cube)" calls "cube.coord_dims(coord)". - mock_dims_result = mock.sentinel.COORD_DIMS - mock_dims_call = mock.Mock(return_value=mock_dims_result) - mock_cube = mock.Mock(Cube, coord_dims=mock_dims_call) - test_coord = AuxCoord([1], long_name="test_name") - - result = test_coord.cube_dims(mock_cube) - self.assertEqual(result, mock_dims_result) - self.assertEqual( - mock_dims_call.call_args_list, [mock.call(test_coord)] - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/test_DimCoord.py b/lib/iris/tests/unit/coords/test_DimCoord.py deleted file mode 100644 index dd0ba48f3d..0000000000 --- a/lib/iris/tests/unit/coords/test_DimCoord.py +++ /dev/null @@ -1,623 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.coords.DimCoord` class. - -Note: a lot of these methods are actually defined by the :class:`Coord` class, -but can only be tested on concrete instances (DimCoord or AuxCoord). - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris.coords import DimCoord -from iris.tests.unit.coords import ( - CoordTestMixin, - coords_all_dtypes_and_lazynesses, - lazyness_string, -) - - -class DimCoordTestMixin(CoordTestMixin): - # Define a 1-D default array shape. - def setupTestArrays(self, shape=(3,), masked=False): - super().setupTestArrays(shape, masked=masked) - - -class Test__init__(tests.IrisTest, DimCoordTestMixin): - # Test for DimCoord creation, with various combinations of points and - # bounds = real / lazy / None. - def setUp(self): - self.setupTestArrays(masked=True) - - def test_lazyness_and_dtype_combinations(self): - for ( - coord, - points_type_name, - bounds_type_name, - ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - pts = coord.core_points() - bds = coord.core_bounds() - # Check properties of points. - # Points array should not be identical to the reference one. - self.assertArraysDoNotShareData( - pts, - self.pts_real, - "Points are the same data as the provided array.", - ) - # the original points array was cast to a test dtype. - check_pts = self.pts_real.astype(coord.dtype) - self.assertEqualRealArraysAndDtypes(pts, check_pts) - - # Check properties of bounds. - if bounds_type_name != "no": - # Bounds array should not be the reference data. - self.assertArraysDoNotShareData( - bds, - self.bds_real, - "Bounds are the same data as the provided array.", - ) - # the original bounds array was cast to a test dtype. - check_bds = self.bds_real.astype(coord.bounds_dtype) - self.assertEqualRealArraysAndDtypes(bds, check_bds) - - def test_fail_bounds_shape_mismatch(self): - bds_shape = list(self.bds_real.shape) - bds_shape[0] += 1 - bds_wrong = np.zeros(bds_shape) - msg = "The shape of the 'unknown' DimCoord bounds array should be" - with self.assertRaisesRegex(ValueError, msg): - DimCoord(self.pts_real, bounds=bds_wrong) - - def test_fail_nonmonotonic(self): - msg = "must be strictly monotonic" - with self.assertRaisesRegex(ValueError, msg): - DimCoord([1, 2, 0, 3]) - - def test_no_masked_pts_real(self): - data = self.no_masked_pts_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertEqual(ma.count_masked(data), 0) - coord = DimCoord(data) - self.assertFalse(coord.has_lazy_points()) - self.assertFalse(ma.isMaskedArray(coord.points)) - self.assertEqual(ma.count_masked(coord.points), 0) - - def test_no_masked_pts_lazy(self): - data = self.no_masked_pts_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertEqual(ma.count_masked(computed), 0) - coord = DimCoord(data) - # DimCoord always realises its points. - self.assertFalse(coord.has_lazy_points()) - self.assertFalse(ma.isMaskedArray(coord.points)) - - def test_masked_pts_real(self): - data = self.masked_pts_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertTrue(ma.count_masked(data)) - emsg = "points array must not be masked" - with self.assertRaisesRegex(TypeError, emsg): - DimCoord(data) - - def test_masked_pts_lazy(self): - data = self.masked_pts_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertTrue(ma.count_masked(computed)) - emsg = "points array must not be masked" - with self.assertRaisesRegex(TypeError, emsg): - DimCoord(data) - - def test_no_masked_bds_real(self): - data = self.no_masked_bds_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertEqual(ma.count_masked(data), 0) - coord = DimCoord(self.pts_real, bounds=data) - self.assertFalse(coord.has_lazy_bounds()) - self.assertFalse(ma.isMaskedArray(coord.bounds)) - self.assertEqual(ma.count_masked(coord.bounds), 0) - - def test_no_masked_bds_lazy(self): - data = self.no_masked_bds_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertEqual(ma.count_masked(computed), 0) - coord = DimCoord(self.pts_real, bounds=data) - # DimCoord always realises its bounds. - self.assertFalse(coord.has_lazy_bounds()) - self.assertFalse(ma.isMaskedArray(coord.bounds)) - - def test_masked_bds_real(self): - data = self.masked_bds_real - self.assertTrue(ma.isMaskedArray(data)) - self.assertTrue(ma.count_masked(data)) - emsg = "bounds array must not be masked" - with self.assertRaisesRegex(TypeError, emsg): - DimCoord(self.pts_real, bounds=data) - - def test_masked_bds_lazy(self): - data = self.masked_bds_lazy - computed = data.compute() - self.assertTrue(ma.isMaskedArray(computed)) - self.assertTrue(ma.count_masked(computed)) - emsg = "bounds array must not be masked" - with self.assertRaisesRegex(TypeError, emsg): - DimCoord(self.pts_real, bounds=data) - - -class Test_core_points(tests.IrisTest, DimCoordTestMixin): - # Test for DimCoord.core_points() with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_real_points(self): - data = self.pts_real - coord = DimCoord(data) - result = coord.core_points() - self.assertArraysDoNotShareData( - result, - self.pts_real, - "core_points() are the same data as the internal array.", - ) - - def test_lazy_points(self): - lazy_data = self.pts_lazy - coord = DimCoord(lazy_data) - result = coord.core_points() - self.assertEqualRealArraysAndDtypes(result, self.pts_real) - - -class Test_core_bounds(tests.IrisTest, DimCoordTestMixin): - # Test for DimCoord.core_bounds() with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_no_bounds(self): - coord = DimCoord(self.pts_real) - result = coord.core_bounds() - self.assertIsNone(result) - - def test_real_bounds(self): - coord = DimCoord(self.pts_real, bounds=self.bds_real) - result = coord.core_bounds() - self.assertArraysDoNotShareData( - result, - self.bds_real, - "core_bounds() are the same data as the internal array.", - ) - - def test_lazy_bounds(self): - coord = DimCoord(self.pts_real, bounds=self.bds_lazy) - result = coord.core_bounds() - self.assertEqualRealArraysAndDtypes(result, self.bds_real) - - -class Test_lazy_points(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - coord = DimCoord(self.pts_real) - result = coord.lazy_points() - self.assertEqualLazyArraysAndDtypes(result, self.pts_lazy) - - def test_lazy_core(self): - coord = DimCoord(self.pts_lazy) - result = coord.lazy_points() - self.assertEqualLazyArraysAndDtypes(result, self.pts_lazy) - # NOTE: identity, as in "result is self.pts_lazy" does *NOT* work. - - -class Test_lazy_bounds(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_no_bounds(self): - coord = DimCoord(self.pts_real) - result = coord.lazy_bounds() - self.assertIsNone(result) - - def test_real_core(self): - coord = DimCoord(self.pts_real, bounds=self.bds_real) - result = coord.lazy_bounds() - self.assertEqualLazyArraysAndDtypes(result, self.bds_lazy) - - def test_lazy_core(self): - coord = DimCoord(self.pts_real, bounds=self.bds_lazy) - result = coord.lazy_bounds() - self.assertEqualLazyArraysAndDtypes(result, self.bds_lazy) - # NOTE: identity, as in "result is self.bds_lazy" does *NOT* work. - - -class Test_has_lazy_points(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - coord = DimCoord(self.pts_real) - result = coord.has_lazy_points() - self.assertFalse(result) - - def test_lazy_core(self): - coord = DimCoord(self.pts_lazy) - result = coord.has_lazy_points() - self.assertFalse(result) - - -class Test_has_lazy_bounds(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_core(self): - coord = DimCoord(self.pts_real, bounds=self.bds_real) - result = coord.has_lazy_bounds() - self.assertFalse(result) - - def test_lazy_core(self): - coord = DimCoord(self.pts_real, bounds=self.bds_lazy) - result = coord.has_lazy_bounds() - self.assertFalse(result) - - -class Test_bounds_dtype(tests.IrisTest): - def test_i16(self): - test_dtype = np.int16 - coord = DimCoord([1], bounds=np.array([[0, 4]], dtype=test_dtype)) - result = coord.bounds_dtype - self.assertEqual(result, test_dtype) - - def test_u16(self): - test_dtype = np.uint16 - coord = DimCoord([1], bounds=np.array([[0, 4]], dtype=test_dtype)) - result = coord.bounds_dtype - self.assertEqual(result, test_dtype) - - def test_f16(self): - test_dtype = np.float16 - coord = DimCoord([1], bounds=np.array([[0, 4]], dtype=test_dtype)) - result = coord.bounds_dtype - self.assertEqual(result, test_dtype) - - -class Test__getitem__(tests.IrisTest, DimCoordTestMixin): - # Test for DimCoord indexing with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_dtypes(self): - # Index coords with all combinations of real+lazy points+bounds, and - # either an int or floating dtype. - # Check that dtypes remain the same in all cases, taking the dtypes - # directly from the core points and bounds (as we have no masking). - for ( - main_coord, - points_type_name, - bounds_type_name, - ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - sub_coord = main_coord[:2] - - coord_dtype = main_coord.dtype - msg = ( - "Indexing main_coord of dtype {} " - "with {} points and {} bounds " - "changed dtype of {} to {}." - ) - - sub_points = sub_coord.core_points() - self.assertEqual( - sub_points.dtype, - coord_dtype, - msg.format( - coord_dtype, - points_type_name, - bounds_type_name, - "points", - sub_points.dtype, - ), - ) - - if bounds_type_name != "no": - sub_bounds = sub_coord.core_bounds() - main_bounds_dtype = main_coord.bounds_dtype - self.assertEqual( - sub_bounds.dtype, - main_bounds_dtype, - msg.format( - main_bounds_dtype, - points_type_name, - bounds_type_name, - "bounds", - sub_bounds.dtype, - ), - ) - - def test_lazyness(self): - # Index coords with all combinations of real+lazy points+bounds, and - # either an int or floating dtype. - # Check that lazy data stays lazy and real stays real, in all cases. - for ( - main_coord, - points_type_name, - bounds_type_name, - ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - # N.B. 'points_type_name' and 'bounds_type_name' in the iteration - # are the original types (lazy/real/none) of the points+bounds, - # but the DimCoord itself only ever has real data. - if points_type_name == "lazy": - points_type_name = "real" - if bounds_type_name == "lazy": - bounds_type_name = "real" - - sub_coord = main_coord[:2] - - msg = ( - "Indexing coord of dtype {} " - "with {} points and {} bounds " - 'changed "lazyness" of {} from {!r} to {!r}.' - ) - coord_dtype = main_coord.dtype - sub_points_lazyness = lazyness_string(sub_coord.core_points()) - self.assertEqual( - sub_points_lazyness, - points_type_name, - msg.format( - coord_dtype, - points_type_name, - bounds_type_name, - "points", - points_type_name, - sub_points_lazyness, - ), - ) - - if bounds_type_name != "no": - sub_bounds_lazy = lazyness_string(sub_coord.core_bounds()) - self.assertEqual( - sub_bounds_lazy, - bounds_type_name, - msg.format( - coord_dtype, - points_type_name, - bounds_type_name, - "bounds", - bounds_type_name, - sub_bounds_lazy, - ), - ) - - def test_real_data_copies(self): - # Index coords with all combinations of real+lazy points+bounds. - # In all cases, check that any real arrays are copied by the indexing. - for ( - main_coord, - points_lazyness, - bounds_lazyness, - ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - sub_coord = main_coord[:2] - - msg = ( - "Indexed coord with {} points and {} bounds " - "does not have its own separate {} array." - ) - if points_lazyness == "real": - main_points = main_coord.core_points() - sub_points = sub_coord.core_points() - sub_main_points = main_points[:2] - self.assertEqualRealArraysAndDtypes( - sub_points, sub_main_points - ) - self.assertArraysDoNotShareData( - sub_points, - sub_main_points, - msg.format(points_lazyness, bounds_lazyness, "points"), - ) - - if bounds_lazyness == "real": - main_bounds = main_coord.core_bounds() - sub_bounds = sub_coord.core_bounds() - sub_main_bounds = main_bounds[:2] - self.assertEqualRealArraysAndDtypes( - sub_bounds, sub_main_bounds - ) - self.assertArraysDoNotShareData( - sub_bounds, - sub_main_bounds, - msg.format(points_lazyness, bounds_lazyness, "bounds"), - ) - - -class Test_copy(tests.IrisTest, DimCoordTestMixin): - # Test for DimCoord.copy() with various types of points and bounds. - def setUp(self): - self.setupTestArrays() - - def test_writable_points(self): - coord1 = DimCoord( - np.arange(5), bounds=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 5]] - ) - coord2 = coord1.copy() - msg = "destination is read-only" - - with self.assertRaisesRegex(ValueError, msg): - coord1.points[:] = 0 - - with self.assertRaisesRegex(ValueError, msg): - coord2.points[:] = 0 - - with self.assertRaisesRegex(ValueError, msg): - coord1.bounds[:] = 0 - - with self.assertRaisesRegex(ValueError, msg): - coord2.bounds[:] = 0 - - def test_realdata_readonly(self): - # Copy coords with all combinations of real+lazy points+bounds. - # In all cases, check that data arrays are read-only. - for ( - main_coord, - points_type_name, - bounds_type_name, - ) in coords_all_dtypes_and_lazynesses(self, DimCoord): - copied_coord = main_coord.copy() - - copied_points = copied_coord.core_points() - expected_error_msg = "output array is read-only" - with self.assertRaisesRegex(ValueError, expected_error_msg): - copied_points[:1] += 33 - - if bounds_type_name != "no": - copied_bounds = copied_coord.core_bounds() - with self.assertRaisesRegex(ValueError, expected_error_msg): - copied_bounds[:1] += 33 - - -class Test_points__getter(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_points(self): - # Getting real points returns a copy - coord = DimCoord(self.pts_real) - result = coord.core_points() - self.assertArraysDoNotShareData( - result, - self.pts_real, - "Points are the same array as the provided data.", - ) - - -class Test_points__setter(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_set_real(self): - # Setting points copies the data - coord = DimCoord(self.pts_real) - new_pts = self.pts_real + 102.3 - coord.points = new_pts - result = coord.core_points() - self.assertArraysDoNotShareData( - result, new_pts, "Points are the same data as the assigned array." - ) - - def test_fail_bad_shape(self): - # Setting real points requires matching shape. - points = [1.0, 2.0] - coord = DimCoord(points) - msg = r"Require data with shape \(2,\), got \(3,\)" - with self.assertRaisesRegex(ValueError, msg): - coord.points = np.array([1.0, 2.0, 3.0]) - self.assertArrayEqual(coord.points, points) - - def test_fail_not_monotonic(self): - # Setting real points requires that they are monotonic. - coord = DimCoord(self.pts_real, bounds=self.bds_real) - msg = "strictly monotonic" - with self.assertRaisesRegex(ValueError, msg): - coord.points = np.array([3.0, 1.0, 2.0]) - self.assertArrayEqual(coord.points, self.pts_real) - - def test_set_lazy(self): - # Setting new lazy points realises them. - coord = DimCoord(self.pts_real) - new_pts = self.pts_lazy + 102.3 - coord.points = new_pts - result = coord.core_points() - self.assertEqualRealArraysAndDtypes(result, new_pts.compute()) - - def test_copy_array(self): - # Assigning points creates a copy - pts = np.array([1, 2, 3]) - coord = DimCoord(pts) - pts[1] = 5 - self.assertEqual(coord.points[1], 2) - - -class Test_bounds__getter(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_real_bounds(self): - # Getting real bounds does not change or copy them. - coord = DimCoord(self.pts_real, bounds=self.bds_real) - result = coord.bounds - self.assertArraysDoNotShareData( - result, - self.bds_real, - "Bounds are the same array as the provided data.", - ) - - -class Test_bounds__setter(tests.IrisTest, DimCoordTestMixin): - def setUp(self): - self.setupTestArrays() - - def test_set_real(self): - # Setting bounds does not copy, but makes a readonly view. - coord = DimCoord(self.pts_real, bounds=self.bds_real) - new_bounds = self.bds_real + 102.3 - coord.bounds = new_bounds - result = coord.core_bounds() - self.assertArraysDoNotShareData( - result, - new_bounds, - "Bounds are the same data as the assigned array.", - ) - - def test_fail_bad_shape(self): - # Setting real points requires matching shape. - coord = DimCoord(self.pts_real, bounds=self.bds_real) - msg = "The shape of the 'unknown' DimCoord bounds array should be" - with self.assertRaisesRegex(ValueError, msg): - coord.bounds = np.array([1.0, 2.0, 3.0]) - self.assertArrayEqual(coord.bounds, self.bds_real) - - def test_fail_not_monotonic(self): - # Setting real bounds requires that they are monotonic. - coord = DimCoord(self.pts_real, bounds=self.bds_real) - msg = "strictly monotonic" - with self.assertRaisesRegex(ValueError, msg): - coord.bounds = np.array([[3.0, 2.0], [1.0, 0.0], [2.0, 1.0]]) - self.assertArrayEqual(coord.bounds, self.bds_real) - - def test_set_lazy(self): - # Setting new lazy bounds realises them. - coord = DimCoord(self.pts_real, bounds=self.bds_lazy) - new_bounds = self.bds_lazy + 102.3 - coord.bounds = new_bounds - result = coord.core_bounds() - self.assertEqualRealArraysAndDtypes(result, new_bounds.compute()) - - def test_copy_array(self): - # Assigning bounds creates a copy - pts = np.array([2, 4, 6]) - bnds = np.array([[1, 3], [3, 5], [5, 7]]) - coord = DimCoord(pts, bounds=bnds) - bnds[1, 1] = 10 - self.assertEqual(coord.bounds[1, 1], 5) - - def test_flip_contiguous(self): - pts = np.arange(4) - bnds = np.transpose([np.arange(1, 5), np.arange(4)]) - coord = DimCoord(pts, bounds=bnds) - self.assertArrayEqual(coord.bounds, bnds[:, ::-1]) - - def test_flip_contiguous_decreasing(self): - pts = np.arange(4, 0, -1) - bnds = np.transpose([np.arange(4, 0, -1), np.arange(5, 1, -1)]) - coord = DimCoord(pts, bounds=bnds) - self.assertArrayEqual(coord.bounds, bnds[:, ::-1]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py b/lib/iris/tests/unit/coords/test__DimensionalMetadata.py deleted file mode 100644 index 83fcbc4512..0000000000 --- a/lib/iris/tests/unit/coords/test__DimensionalMetadata.py +++ /dev/null @@ -1,1078 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.coords._DimensionalMetadata` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - - -from cf_units import Unit -import numpy as np - -import iris._lazy_data as lazy -from iris.coord_systems import GeogCS -from iris.coords import ( - AncillaryVariable, - AuxCoord, - CellMeasure, - DimCoord, - _DimensionalMetadata, -) -from iris.experimental.ugrid.mesh import Connectivity -from iris.tests.stock import climatology_3d as cube_with_climatology -from iris.tests.stock.mesh import sample_meshcoord - - -class Test___init____abstractmethod(tests.IrisTest): - def test(self): - emsg = ( - "Can't instantiate abstract class _DimensionalMetadata with " - "abstract methods __init__" - ) - with self.assertRaisesRegex(TypeError, emsg): - _ = _DimensionalMetadata(0) - - -class Mixin__string_representations: - """ - Common testcode for generic `__str__`, `__repr__` and `summary` methods. - - Effectively, __str__ and __repr__ are thin wrappers around `summary`. - These are used by all the subclasses : notably Coord/DimCoord/AuxCoord, - but also AncillaryVariable, CellMeasure and MeshCoord. - - There are a lot of different aspects to consider: - - * different object classes with different class-specific properties - * changing with array sizes + dimensionalities - * masked data - * data types : int, float, string and (special) dates - * for Coords, handling of bounds - * "summary" controls (also can be affected by numpy printoptions). - - NOTE: since the details of formatting are important to us here, the basic - test method is to check printout results against an exact 'snapshot' - embedded (visibly) in the test itself. - - """ - - def repr_str_strings(self, dm, linewidth=55): - """ - Return a simple combination of repr and str printouts. - - N.B. we control linewidth to make the outputs easier to compare. - """ - with np.printoptions(linewidth=linewidth): - result = repr(dm) + "\n" + str(dm) - return result - - def sample_data(self, datatype=float, units="m", shape=(5,), masked=False): - """Make a sample data array for a test _DimensionalMetadata object.""" - # Get an actual Unit - units = Unit(units) - if units.calendar: - # fix string datatypes for date-based units - datatype = float - - # Get a dtype - dtype = np.dtype(datatype) - - # Make suitable test values for type/shape/masked - length = int(np.prod(shape)) - if dtype.kind == "U": - # String content. - digit_strs = [str(i) * (i + 1) for i in range(0, 10)] - if length < 10: - # ['0', '11', '222, '3333', ..] - values = np.array(digit_strs[:length]) - else: - # [... '9999999999', '0', '11' ....] - indices = [(i % 10) for i in range(length)] - values = np.array(digit_strs)[indices] - else: - # numeric content : a simple [0, 1, 2 ...] - values = np.arange(length).astype(dtype) - - if masked: - if np.prod(shape) >= 3: - # Mask 1 in 3 points : [x -- x x -- x ...] - i_firstmasked = 1 - else: - # Few points, mask 1 in 3 starting at 0 [-- x x -- x x -- ...] - i_firstmasked = 0 - masked_points = [(i % 3) == i_firstmasked for i in range(length)] - values = np.ma.masked_array(values, mask=masked_points) - - values = values.reshape(shape) - return values - - # Make a sample Coord, as _DimensionalMetadata is abstract and this is the - # obvious concrete subclass to use for testing - def sample_coord( - self, - datatype=float, - dates=False, - units="m", - long_name="x", - shape=(5,), - masked=False, - bounded=False, - dimcoord=False, - lazy_points=False, - lazy_bounds=False, - *coord_args, - **coord_kwargs, - ): - if masked: - dimcoord = False - if dates: - # Use a pre-programmed date unit. - units = Unit("days since 1970-03-5") - if not isinstance(units, Unit): - # This operation is *not* a no-op, it will wipe calendars ! - units = Unit(units) - values = self.sample_data( - datatype=datatype, units=units, shape=shape, masked=masked - ) - cls = DimCoord if dimcoord else AuxCoord - coord = cls( - points=values, - units=units, - long_name=long_name, - *coord_args, - **coord_kwargs, - ) - if bounded or lazy_bounds: - if shape == (1,): - # Guess-bounds doesn't work ! - val = coord.points[0] - bounds = [val - 10, val + 10] - # NB preserve masked/unmasked : avoid converting masks to NaNs - if np.ma.isMaskedArray(coord.points): - array = np.ma.array - else: - array = np.array - coord.bounds = array(bounds) - else: - coord.guess_bounds() - if lazy_points: - coord.points = lazy.as_lazy_data(coord.points) - if lazy_bounds: - coord.bounds = lazy.as_lazy_data(coord.bounds) - return coord - - def coord_representations(self, *args, **kwargs): - """ - Create a test coord and return its string representations. - - Pass args+kwargs to 'sample_coord' and return the 'repr_str_strings'. - - """ - coord = self.sample_coord(*args, **kwargs) - return self.repr_str_strings(coord) - - def assertLines(self, list_of_expected_lines, string_result): - """ - Assert equality between a result and expected output lines. - - For convenience, the 'expected lines' are joined with a '\\n', - because a list of strings is nicer to construct in code. - They should then match the actual result, which is a simple string. - - """ - self.assertEqual(list_of_expected_lines, string_result.split("\n")) - - -class Test__print_common(Mixin__string_representations, tests.IrisTest): - """ - Test aspects of __str__ and __repr__ output common to all - _DimensionalMetadata instances. - I.E. those from CFVariableMixin, plus values array (data-manager). - - Aspects : - * standard_name: - * long_name: - * var_name: - * attributes - * units - * shape - * dtype - - """ - - def test_simple(self): - result = self.coord_representations() - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_minimal(self): - result = self.coord_representations( - long_name=None, units=None, shape=(1,) - ) - expected = [ - "", - "AuxCoord : unknown / (unknown)", - " points: [0.]", - " shape: (1,)", - " dtype: float64", - ] - self.assertLines(expected, result) - - def test_names(self): - result = self.coord_representations( - standard_name="height", long_name="this", var_name="x_var" - ) - expected = [ - "", - "AuxCoord : height / (m)", - " points: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " standard_name: 'height'", - " long_name: 'this'", - " var_name: 'x_var'", - ] - self.assertLines(expected, result) - - def test_bounded(self): - result = self.coord_representations(shape=(3,), bounded=True) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0., 1., 2.]", - " bounds: [", - " [-0.5, 0.5],", - " [ 0.5, 1.5],", - " [ 1.5, 2.5]]", - " shape: (3,) bounds(3, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_masked(self): - result = self.coord_representations(masked=True) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0.0, -- , 2.0, 3.0, -- ]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_dtype_int(self): - result = self.coord_representations(units="1", datatype=np.int16) - expected = [ - "", - "AuxCoord : x / (1)", - " points: [0, 1, 2, 3, 4]", - " shape: (5,)", - " dtype: int16", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_dtype_date(self): - # Note: test with a date 'longer' than the built-in one in - # 'sample_coord(dates=True)', because it includes a time-of-day - full_date_unit = Unit( - "days since 1892-05-17 03:00:25", calendar="360_day" - ) - result = self.coord_representations(units=full_date_unit) - expected = [ - ( - "" - ), - ( - "AuxCoord : x / (days since 1892-05-17 03:00:25, " - "360_day calendar)" - ), - " points: [", - " 1892-05-17 03:00:25, 1892-05-18 03:00:25,", - " 1892-05-19 03:00:25, 1892-05-20 03:00:25,", - " 1892-05-21 03:00:25]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_attributes(self): - # NOTE: scheduled for future change, to put each attribute on a line - coord = self.sample_coord( - attributes={ - "array": np.arange(7.0), - "list": [1, 2, 3], - "empty": [], - "None": None, - "string": "this", - "long_long_long_long_long_name": 3, - "other": ( - "long_long_long_long_long_long_long_long_" - "long_long_long_long_long_long_long_long_value" - ), - "float": 4.3, - } - ) - result = self.repr_str_strings(coord) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - " attributes:", - " array [0. 1. 2. 3. 4. 5. 6.]", - " list [1, 2, 3]", - " empty []", - " None None", - " string 'this'", - " long_long_long_long_long_name 3", - ( - " other " - "'long_long_long_long_long_long_long_long_" - "long_long_long_long_long_long..." - ), - " float 4.3", - ] - self.assertLines(expected, result) - - def test_lazy_points(self): - result = self.coord_representations(lazy_points=True) - expected = [ - " shape(5,)>", - "AuxCoord : x / (m)", - " points: ", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_lazy_bounds(self): - result = self.coord_representations(lazy_bounds=True) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0., 1., 2., 3., 4.]", - " bounds: ", - " shape: (5,) bounds(5, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_lazy_points_and_bounds(self): - result = self.coord_representations(lazy_points=True, lazy_bounds=True) - expected = [ - "+bounds shape(5,)>", - "AuxCoord : x / (m)", - " points: ", - " bounds: ", - " shape: (5,) bounds(5, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_scalar(self): - result = self.coord_representations(shape=(1,), bounded=True) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0.]", - " bounds: [[-10., 10.]]", - " shape: (1,) bounds(1, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_scalar_masked(self): - result = self.coord_representations( - shape=(1,), bounded=True, masked=True - ) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [--]", - " bounds: [[--, --]]", - " shape: (1,) bounds(1, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_length_short(self): - result = self.coord_representations(shape=(2,), bounded=True) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0., 1.]", - " bounds: [", - " [-0.5, 0.5],", - " [ 0.5, 1.5]]", - " shape: (2,) bounds(2, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_length_medium(self): - # Where bounds are truncated, but points not. - result = self.coord_representations(shape=(14,), bounded=True) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [", - " 0., 1., 2., 3., 4., 5., 6., 7., 8.,", - " 9., 10., 11., 12., 13.]", - " bounds: [", - " [-0.5, 0.5],", - " [ 0.5, 1.5],", - " ...,", - " [11.5, 12.5],", - " [12.5, 13.5]]", - " shape: (14,) bounds(14, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_length_long(self): - # Completely truncated representations - result = self.coord_representations(shape=(150,), bounded=True) - expected = [ - ( - "" - ), - "AuxCoord : x / (m)", - " points: [ 0., 1., ..., 148., 149.]", - " bounds: [", - " [ -0.5, 0.5],", - " [ 0.5, 1.5],", - " ...,", - " [147.5, 148.5],", - " [148.5, 149.5]]", - " shape: (150,) bounds(150, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_strings(self): - result = self.coord_representations(datatype=str) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0 , 11 , 222 , 3333 , 44444]", - " shape: (5,)", - " dtype: ", - "AuxCoord : x / (m)", - " points: [", - " 0 , 11 , 222 ,", - " 3333 , 44444 , 555555 ,", - " 6666666 , 77777777 , 888888888 ,", - " 9999999999, 0 , 11 ,", - " 222 , 3333 , 44444 ]", - " shape: (15,)", - " dtype: ", - "AuxCoord : x / (days since 1970-03-5, standard calendar)", - " points: [1970-03-05 00:00:00, 1970-03-06 00:00:00]", - " shape: (2,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_dates_scalar(self): - # Printouts for a scalar date coord. - # Demonstrate that a "typical" datetime coord can print with the date - # value visible in the repr. - long_time_unit = Unit("hours since 2025-03-23 01:00:00") - coord = self.sample_coord( - standard_name="time", - long_name=None, - shape=(1,), - units=long_time_unit, - ) - # Do this one with a default linewidth, not our default reduced one, so - # that we can get the date value in the repr output. - result = self.repr_str_strings(coord, linewidth=None) - expected = [ - ( - "" - ), - ( - "AuxCoord : time / (hours since 2025-03-23 01:00:00, " - "standard calendar)" - ), - " points: [2025-03-23 01:00:00]", - " shape: (1,)", - " dtype: float64", - " standard_name: 'time'", - ] - self.assertLines(expected, result) - - def test_dates_bounds(self): - result = self.coord_representations(dates=True, bounded=True) - expected = [ - "", - "AuxCoord : x / (days since 1970-03-5, standard calendar)", - " points: [", - " 1970-03-05 00:00:00, 1970-03-06 00:00:00,", - " 1970-03-07 00:00:00, 1970-03-08 00:00:00,", - " 1970-03-09 00:00:00]", - " bounds: [", - " [1970-03-04 12:00:00, 1970-03-05 12:00:00],", - " [1970-03-05 12:00:00, 1970-03-06 12:00:00],", - " [1970-03-06 12:00:00, 1970-03-07 12:00:00],", - " [1970-03-07 12:00:00, 1970-03-08 12:00:00],", - " [1970-03-08 12:00:00, 1970-03-09 12:00:00]]", - " shape: (5,) bounds(5, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_dates_masked(self): - result = self.coord_representations(dates=True, masked=True) - expected = [ - "", - "AuxCoord : x / (days since 1970-03-5, standard calendar)", - " points: [", - " 1970-03-05 00:00:00, -- ,", - " 1970-03-07 00:00:00, 1970-03-08 00:00:00,", - " -- ]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_untypical_bounds(self): - # Check printing when n-bounds > 2 - coord = self.sample_coord() - bounds = coord.points.reshape((5, 1)) + np.array([[-3.0, -2, 2, 3]]) - coord.bounds = bounds - result = self.repr_str_strings(coord) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0., 1., 2., 3., 4.]", - " bounds: [", - " [-3., -2., 2., 3.],", - " [-2., -1., 3., 4.],", - " ...,", - " [ 0., 1., 5., 6.],", - " [ 1., 2., 6., 7.]]", - " shape: (5,) bounds(5, 4)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_multidimensional(self): - # Demonstrate formatting of multdimensional arrays - result = self.coord_representations(shape=(7, 5, 3)) - # This one is a bit unavoidably long .. - expected = [ - "", - "AuxCoord : x / (m)", - " points: [", - " [[ 0., 1., 2.],", - " [ 3., 4., 5.],", - " ...,", - " [ 9., 10., 11.],", - " [ 12., 13., 14.]],", - " ", - " [[ 15., 16., 17.],", - " [ 18., 19., 20.],", - " ...,", - " [ 24., 25., 26.],", - " [ 27., 28., 29.]],", - " ", - " ...,", - " ", - " [[ 75., 76., 77.],", - " [ 78., 79., 80.],", - " ...,", - " [ 84., 85., 86.],", - " [ 87., 88., 89.]],", - " ", - " [[ 90., 91., 92.],", - " [ 93., 94., 95.],", - " ...,", - " [ 99., 100., 101.],", - " [102., 103., 104.]]]", - " shape: (7, 5, 3)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_multidimensional_small(self): - # Demonstrate that a small-enough multidim will print in the repr. - result = self.coord_representations(shape=(2, 2), datatype=int) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [", - " [0, 1],", - " [2, 3]]", - " shape: (2, 2)", - " dtype: int64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_integers_short(self): - result = self.coord_representations(datatype=np.int16) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0, 1, 2, 3, 4]", - " shape: (5,)", - " dtype: int16", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_integers_masked(self): - result = self.coord_representations(datatype=int, masked=True) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0 , --, 2 , 3 , --]", - " shape: (5,)", - " dtype: int64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_integers_masked_long(self): - result = self.coord_representations( - shape=(20,), datatype=int, masked=True - ) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0 , --, ..., 18, --]", - " shape: (20,)", - " dtype: int64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - -class Test__print_Coord(Mixin__string_representations, tests.IrisTest): - """ - Test Coord-specific aspects of __str__ and __repr__ output. - - Aspects : - * DimCoord / AuxCoord - * coord_system - * climatological - * circular - - """ - - def test_dimcoord(self): - result = self.coord_representations(dimcoord=True) - expected = [ - "", - "DimCoord : x / (m)", - " points: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_coord_system(self): - result = self.coord_representations(coord_system=GeogCS(1000.0)) - expected = [ - "", - "AuxCoord : x / (m)", - " points: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - " coord_system: GeogCS(1000.0)", - ] - self.assertLines(expected, result) - - def test_climatological(self): - cube = cube_with_climatology() - coord = cube.coord("time") - coord = coord[:1] # Just to make it a bit shorter - result = self.repr_str_strings(coord) - expected = [ - ( - "" - ), - ( - "DimCoord : time / (days since 1970-01-01 00:00:00-00, " - "standard calendar)" - ), - " points: [2001-01-10 00:00:00]", - " bounds: [[2001-01-10 00:00:00, 2011-01-10 00:00:00]]", - " shape: (1,) bounds(1, 2)", - " dtype: float64", - " standard_name: 'time'", - " climatological: True", - ] - self.assertLines(expected, result) - - def test_circular(self): - coord = self.sample_coord(shape=(2,), dimcoord=True) - coord.circular = True - result = self.repr_str_strings(coord) - expected = [ - "", - "DimCoord : x / (m)", - " points: [0., 1.]", - " shape: (2,)", - " dtype: float64", - " long_name: 'x'", - " circular: True", - ] - self.assertLines(expected, result) - - -class Test__print_noncoord(Mixin__string_representations, tests.IrisTest): - """ - Limited testing of other _DimensionalMetadata subclasses. - - * AncillaryVariable - * CellMeasure - * Connectivity - * MeshCoord - - """ - - def test_ancillary(self): - # Check we can print an AncillaryVariable - # Practically, ~identical to an AuxCoord, but without bounds, and the - # array is called 'data'. - data = self.sample_data() - ancil = AncillaryVariable(data, long_name="v_aux", units="m s-1") - result = self.repr_str_strings(ancil) - expected = [ - "", - "AncillaryVariable : v_aux / (m s-1)", - " data: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'v_aux'", - ] - self.assertLines(expected, result) - - def test_cellmeasure(self): - # Check we can print an AncillaryVariable - # N.B. practically, identical to an AuxCoord (without bounds) - # Check we can print an AncillaryVariable - # Practically, ~identical to an AuxCoord, but without bounds, and the - # array is called 'data'. - data = self.sample_data() - cell_measure = CellMeasure( - data, measure="area", long_name="cell_area", units="m^2" - ) - result = self.repr_str_strings(cell_measure) - expected = [ - "", - "CellMeasure : cell_area / (m^2)", - " data: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'cell_area'", - " measure: 'area'", - ] - self.assertLines(expected, result) - - def test_connectivity(self): - # Check we can print a Connectivity - # Like a Coord, but always print : cf_role, location_axis, start_index - data = self.sample_data(shape=(3, 2), datatype=int) - conn = Connectivity( - data, cf_role="edge_node_connectivity", long_name="enc", units="1" - ) - result = self.repr_str_strings(conn) - expected = [ - "", - "Connectivity : enc / (1)", - " data: [", - " [0, 1],", - " [2, 3],", - " [4, 5]]", - " shape: (3, 2)", - " dtype: int64", - " long_name: 'enc'", - " cf_role: 'edge_node_connectivity'", - " start_index: 0", - " location_axis: 0", - ] - self.assertLines(expected, result) - - def test_connectivity__start_index(self): - # Check we can print a Connectivity - # Like a Coord, but always print : cf_role, location_axis, start_index - data = self.sample_data(shape=(3, 2), datatype=int) - conn = Connectivity( - data + 1, - start_index=1, - cf_role="edge_node_connectivity", - long_name="enc", - units="1", - ) - result = self.repr_str_strings(conn) - expected = [ - "", - "Connectivity : enc / (1)", - " data: [", - " [1, 2],", - " [3, 4],", - " [5, 6]]", - " shape: (3, 2)", - " dtype: int64", - " long_name: 'enc'", - " cf_role: 'edge_node_connectivity'", - " start_index: 1", - " location_axis: 0", - ] - self.assertLines(expected, result) - - def test_connectivity__location_axis(self): - # Check we can print a Connectivity - # Like a Coord, but always print : cf_role, location_axis, start_index - data = self.sample_data(shape=(3, 2), datatype=int) - conn = Connectivity( - data.transpose(), - location_axis=1, - cf_role="edge_node_connectivity", - long_name="enc", - units="1", - ) - result = self.repr_str_strings(conn) - expected = [ - "", - "Connectivity : enc / (1)", - " data: [", - " [0, 2, 4],", - " [1, 3, 5]]", - " shape: (2, 3)", - " dtype: int64", - " long_name: 'enc'", - " cf_role: 'edge_node_connectivity'", - " start_index: 0", - " location_axis: 1", - ] - self.assertLines(expected, result) - - def test_meshcoord(self): - meshco = sample_meshcoord() - meshco.mesh.long_name = "test_mesh" # For stable printout of the Mesh - result = self.repr_str_strings(meshco) - expected = [ - ( - "" - ), - "MeshCoord : longitude / (unknown)", - " mesh: ", - " location: 'face'", - " points: [3100, 3101, 3102]", - " bounds: [", - " [1100, 1101, 1102, 1103],", - " [1104, 1105, 1106, 1107],", - " [1108, 1109, 1110, 1111]]", - " shape: (3,) bounds(3, 4)", - " dtype: int64", - " standard_name: 'longitude'", - " axis: 'x'", - ] - self.assertLines(expected, result) - - -class Test_summary(Mixin__string_representations, tests.IrisTest): - """ - Test the controls of the 'summary' method. - """ - - def test_shorten(self): - coord = self.sample_coord() - expected = self.repr_str_strings(coord) - result = coord.summary(shorten=True) + "\n" + coord.summary() - self.assertEqual(expected, result) - - def test_max_values__default(self): - coord = self.sample_coord() - result = coord.summary() - expected = [ - "AuxCoord : x / (m)", - " points: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_max_values__2(self): - coord = self.sample_coord() - result = coord.summary(max_values=2) - expected = [ - "AuxCoord : x / (m)", - " points: [0., 1., ..., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_max_values__bounded__2(self): - coord = self.sample_coord(bounded=True) - result = coord.summary(max_values=2) - expected = [ - "AuxCoord : x / (m)", - " points: [0., 1., ..., 3., 4.]", - " bounds: [", - " [-0.5, 0.5],", - " [ 0.5, 1.5],", - " ...,", - " [ 2.5, 3.5],", - " [ 3.5, 4.5]]", - " shape: (5,) bounds(5, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_max_values__0(self): - coord = self.sample_coord(bounded=True) - result = coord.summary(max_values=0) - expected = [ - "AuxCoord : x / (m)", - " points: [...]", - " bounds: [...]", - " shape: (5,) bounds(5, 2)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_linewidth__default(self): - coord = self.sample_coord() - coord.points = coord.points + 1000.003 # Make the output numbers wider - result = coord.summary() - expected = [ - "AuxCoord : x / (m)", - " points: [1000.003, 1001.003, 1002.003, 1003.003, 1004.003]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - # Show that, when unset, it follows the numpy setting - with np.printoptions(linewidth=35): - result = coord.summary() - expected = [ - "AuxCoord : x / (m)", - " points: [", - " 1000.003, 1001.003,", - " 1002.003, 1003.003,", - " 1004.003]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - def test_linewidth__set(self): - coord = self.sample_coord() - coord.points = coord.points + 1000.003 # Make the output numbers wider - expected = [ - "AuxCoord : x / (m)", - " points: [", - " 1000.003, 1001.003,", - " 1002.003, 1003.003,", - " 1004.003]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - result = coord.summary(linewidth=35) - self.assertLines(expected, result) - - with np.printoptions(linewidth=999): - # Show that, when set, it ignores the numpy setting - result = coord.summary(linewidth=35) - self.assertLines(expected, result) - - def test_convert_dates(self): - coord = self.sample_coord(dates=True) - result = coord.summary() - expected = [ - "AuxCoord : x / (days since 1970-03-5, standard calendar)", - " points: [", - ( - " 1970-03-05 00:00:00, 1970-03-06 00:00:00, " - "1970-03-07 00:00:00," - ), - " 1970-03-08 00:00:00, 1970-03-09 00:00:00]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - result = coord.summary(convert_dates=False) - expected = [ - "AuxCoord : x / (days since 1970-03-5, standard calendar)", - " points: [0., 1., 2., 3., 4.]", - " shape: (5,)", - " dtype: float64", - " long_name: 'x'", - ] - self.assertLines(expected, result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/cube/__init__.py b/lib/iris/tests/unit/cube/__init__.py deleted file mode 100644 index 7852593e21..0000000000 --- a/lib/iris/tests/unit/cube/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.cube` module.""" diff --git a/lib/iris/tests/unit/cube/test_Cube.py b/lib/iris/tests/unit/cube/test_Cube.py deleted file mode 100644 index 8e9e00dce8..0000000000 --- a/lib/iris/tests/unit/cube/test_Cube.py +++ /dev/null @@ -1,3217 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.cube.Cube` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from collections import namedtuple -from itertools import permutations -from unittest import mock - -from cf_units import Unit -import numpy as np -import numpy.ma as ma -import pytest - -from iris._lazy_data import as_lazy_data -import iris.analysis -from iris.analysis import MEAN, Aggregator, WeightedAggregator -import iris.aux_factory -from iris.aux_factory import HybridHeightFactory -from iris.common.metadata import BaseMetadata -import iris.coords -from iris.coords import ( - AncillaryVariable, - AuxCoord, - CellMeasure, - CellMethod, - DimCoord, -) -from iris.cube import Cube -import iris.exceptions -from iris.exceptions import ( - AncillaryVariableNotFoundError, - CellMeasureNotFoundError, - CoordinateNotFoundError, - UnitConversionError, -) -import iris.tests.stock as stock -from iris.tests.stock.mesh import ( - sample_mesh, - sample_mesh_cube, - sample_meshcoord, -) - - -class Test___init___data(tests.IrisTest): - def test_ndarray(self): - # np.ndarray should be allowed through - data = np.arange(12).reshape(3, 4) - cube = Cube(data) - self.assertEqual(type(cube.data), np.ndarray) - self.assertArrayEqual(cube.data, data) - - def test_masked(self): - # ma.MaskedArray should be allowed through - data = ma.masked_greater(np.arange(12).reshape(3, 4), 1) - cube = Cube(data) - self.assertEqual(type(cube.data), ma.MaskedArray) - self.assertMaskedArrayEqual(cube.data, data) - - def test_masked_no_mask(self): - # ma.MaskedArray should be allowed through even if it has no mask - data = ma.masked_array(np.arange(12).reshape(3, 4), False) - cube = Cube(data) - self.assertEqual(type(cube.data), ma.MaskedArray) - self.assertMaskedArrayEqual(cube.data, data) - - def test_matrix(self): - # Subclasses of np.ndarray should be coerced back to np.ndarray. - # (Except for np.ma.MaskedArray.) - data = np.matrix([[1, 2, 3], [4, 5, 6]]) - cube = Cube(data) - self.assertEqual(type(cube.data), np.ndarray) - self.assertArrayEqual(cube.data, data) - - -class Test_data_dtype_fillvalue(tests.IrisTest): - def _sample_data( - self, dtype=("f4"), masked=False, fill_value=None, lazy=False - ): - data = np.arange(6).reshape((2, 3)) - dtype = np.dtype(dtype) - data = data.astype(dtype) - if masked: - data = ma.masked_array( - data, mask=[[0, 1, 0], [0, 0, 0]], fill_value=fill_value - ) - if lazy: - data = as_lazy_data(data) - return data - - def _sample_cube( - self, dtype=("f4"), masked=False, fill_value=None, lazy=False - ): - data = self._sample_data( - dtype=dtype, masked=masked, fill_value=fill_value, lazy=lazy - ) - cube = Cube(data) - return cube - - def test_realdata_change(self): - # Check re-assigning real data. - cube = self._sample_cube() - self.assertEqual(cube.dtype, np.float32) - new_dtype = np.dtype("i4") - new_data = self._sample_data(dtype=new_dtype) - cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) - - def test_realmaskdata_change(self): - # Check re-assigning real masked data. - cube = self._sample_cube(masked=True, fill_value=1234) - self.assertEqual(cube.dtype, np.float32) - new_dtype = np.dtype("i4") - new_fill_value = 4321 - new_data = self._sample_data( - masked=True, fill_value=new_fill_value, dtype=new_dtype - ) - cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) - self.assertEqual(cube.data.fill_value, new_fill_value) - - def test_lazydata_change(self): - # Check re-assigning lazy data. - cube = self._sample_cube(lazy=True) - self.assertEqual(cube.core_data().dtype, np.float32) - new_dtype = np.dtype("f8") - new_data = self._sample_data(new_dtype, lazy=True) - cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) - - def test_lazymaskdata_change(self): - # Check re-assigning lazy masked data. - cube = self._sample_cube(masked=True, fill_value=1234, lazy=True) - self.assertEqual(cube.core_data().dtype, np.float32) - new_dtype = np.dtype("f8") - new_fill_value = 4321 - new_data = self._sample_data( - dtype=new_dtype, masked=True, fill_value=new_fill_value, lazy=True - ) - cube.data = new_data - self.assertIs(cube.core_data(), new_data) - self.assertEqual(cube.dtype, new_dtype) - self.assertEqual(cube.data.fill_value, new_fill_value) - - def test_lazydata_realise(self): - # Check touching lazy data. - cube = self._sample_cube(lazy=True) - data = cube.data - self.assertIs(cube.core_data(), data) - self.assertEqual(cube.dtype, np.float32) - - def test_lazymaskdata_realise(self): - # Check touching masked lazy data. - fill_value = 27.3 - cube = self._sample_cube(masked=True, fill_value=fill_value, lazy=True) - data = cube.data - self.assertIs(cube.core_data(), data) - self.assertEqual(cube.dtype, np.float32) - self.assertEqual(data.fill_value, np.float32(fill_value)) - - def test_realmaskedconstantint_realise(self): - masked_data = ma.masked_array([666], mask=True) - masked_constant = masked_data[0] - cube = Cube(masked_constant) - data = cube.data - self.assertTrue(ma.isMaskedArray(data)) - self.assertNotIsInstance(data, ma.core.MaskedConstant) - - def test_lazymaskedconstantint_realise(self): - dtype = np.dtype("i2") - masked_data = ma.masked_array([666], mask=True, dtype=dtype) - masked_constant = masked_data[0] - masked_constant_lazy = as_lazy_data(masked_constant) - cube = Cube(masked_constant_lazy) - data = cube.data - self.assertTrue(ma.isMaskedArray(data)) - self.assertNotIsInstance(data, ma.core.MaskedConstant) - - def test_lazydata___getitem__dtype(self): - fill_value = 1234 - dtype = np.dtype("int16") - masked_array = ma.masked_array( - np.arange(5), - mask=[0, 0, 1, 0, 0], - fill_value=fill_value, - dtype=dtype, - ) - lazy_masked_array = as_lazy_data(masked_array) - cube = Cube(lazy_masked_array) - subcube = cube[3:] - self.assertEqual(subcube.dtype, dtype) - self.assertEqual(subcube.data.fill_value, fill_value) - - -class Test_extract(tests.IrisTest): - def test_scalar_cube_exists(self): - # Ensure that extract is able to extract a scalar cube. - constraint = iris.Constraint(name="a1") - cube = Cube(1, long_name="a1") - res = cube.extract(constraint) - self.assertIs(res, cube) - - def test_scalar_cube_noexists(self): - # Ensure that extract does not return a non-matching scalar cube. - constraint = iris.Constraint(name="a2") - cube = Cube(1, long_name="a1") - res = cube.extract(constraint) - self.assertIs(res, None) - - def test_scalar_cube_coord_match(self): - # Ensure that extract is able to extract a scalar cube according to - # constrained scalar coordinate. - constraint = iris.Constraint(scalar_coord=0) - cube = Cube(1, long_name="a1") - coord = iris.coords.AuxCoord(0, long_name="scalar_coord") - cube.add_aux_coord(coord, None) - res = cube.extract(constraint) - self.assertIs(res, cube) - - def test_scalar_cube_coord_nomatch(self): - # Ensure that extract is not extracting a scalar cube with scalar - # coordinate that does not match the constraint. - constraint = iris.Constraint(scalar_coord=1) - cube = Cube(1, long_name="a1") - coord = iris.coords.AuxCoord(0, long_name="scalar_coord") - cube.add_aux_coord(coord, None) - res = cube.extract(constraint) - self.assertIs(res, None) - - def test_1d_cube_exists(self): - # Ensure that extract is able to extract from a 1d cube. - constraint = iris.Constraint(name="a1") - cube = Cube([1], long_name="a1") - res = cube.extract(constraint) - self.assertIs(res, cube) - - def test_1d_cube_noexists(self): - # Ensure that extract does not return a non-matching 1d cube. - constraint = iris.Constraint(name="a2") - cube = Cube([1], long_name="a1") - res = cube.extract(constraint) - self.assertIs(res, None) - - -class Test_xml(tests.IrisTest): - def test_checksum_ignores_masked_values(self): - # Mask out an single element. - data = ma.arange(12).reshape(3, 4) - data[1, 2] = ma.masked - cube = Cube(data) - self.assertCML(cube) - - # If we change the underlying value before masking it, the - # checksum should be unaffected. - data = ma.arange(12).reshape(3, 4) - data[1, 2] = 42 - data[1, 2] = ma.masked - cube = Cube(data) - self.assertCML(cube) - - def test_byteorder_default(self): - cube = Cube(np.arange(3)) - self.assertIn("byteorder", cube.xml()) - - def test_byteorder_false(self): - cube = Cube(np.arange(3)) - self.assertNotIn("byteorder", cube.xml(byteorder=False)) - - def test_byteorder_true(self): - cube = Cube(np.arange(3)) - self.assertIn("byteorder", cube.xml(byteorder=True)) - - def test_cell_measures(self): - cube = stock.simple_3d_w_multidim_coords() - cm_a = iris.coords.CellMeasure( - np.zeros(cube.shape[-2:]), measure="area", units="1" - ) - cube.add_cell_measure(cm_a, (1, 2)) - cm_v = iris.coords.CellMeasure( - np.zeros(cube.shape), - measure="volume", - long_name="madeup", - units="m3", - ) - cube.add_cell_measure(cm_v, (0, 1, 2)) - self.assertCML(cube) - - def test_ancils(self): - cube = stock.simple_2d_w_multidim_coords() - av = iris.coords.AncillaryVariable( - np.zeros(cube.shape), long_name="xy", var_name="vxy", units="1" - ) - cube.add_ancillary_variable(av, (0, 1)) - self.assertCML(cube) - - -class Test_collapsed__lazy(tests.IrisTest): - def setUp(self): - self.data = np.arange(6.0).reshape((2, 3)) - self.lazydata = as_lazy_data(self.data) - cube = Cube(self.lazydata) - for i_dim, name in enumerate(("y", "x")): - npts = cube.shape[i_dim] - coord = DimCoord(np.arange(npts), long_name=name) - cube.add_dim_coord(coord, i_dim) - self.cube = cube - - def test_dim0_lazy(self): - cube_collapsed = self.cube.collapsed("y", MEAN) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, [1.5, 2.5, 3.5]) - self.assertFalse(cube_collapsed.has_lazy_data()) - - def test_dim1_lazy(self): - cube_collapsed = self.cube.collapsed("x", MEAN) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual(cube_collapsed.data, [1.0, 4.0]) - self.assertFalse(cube_collapsed.has_lazy_data()) - - def test_multidims(self): - # Check that MEAN works with multiple dims. - cube_collapsed = self.cube.collapsed(("x", "y"), MEAN) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAllClose(cube_collapsed.data, 2.5) - - def test_non_lazy_aggregator(self): - # An aggregator which doesn't have a lazy function should still work. - dummy_agg = Aggregator( - "custom_op", lambda x, axis=None: np.mean(x, axis=axis) - ) - result = self.cube.collapsed("x", dummy_agg) - self.assertFalse(result.has_lazy_data()) - self.assertArrayEqual(result.data, np.mean(self.data, axis=1)) - - -class Test_collapsed__multidim_weighted(tests.IrisTest): - def setUp(self): - self.data = np.arange(6.0).reshape((2, 3)) - self.lazydata = as_lazy_data(self.data) - # Test cubes wth (same-valued) real and lazy data - cube_real = Cube(self.data) - for i_dim, name in enumerate(("y", "x")): - npts = cube_real.shape[i_dim] - coord = DimCoord(np.arange(npts), long_name=name) - cube_real.add_dim_coord(coord, i_dim) - self.cube_real = cube_real - self.cube_lazy = cube_real.copy(data=self.lazydata) - # Test weights and expected result for a y-collapse - self.y_weights = np.array([0.3, 0.5]) - self.full_weights_y = np.broadcast_to( - self.y_weights.reshape((2, 1)), cube_real.shape - ) - self.expected_result_y = np.array([1.875, 2.875, 3.875]) - # Test weights and expected result for an x-collapse - self.x_weights = np.array([0.7, 0.4, 0.6]) - self.full_weights_x = np.broadcast_to( - self.x_weights.reshape((1, 3)), cube_real.shape - ) - self.expected_result_x = np.array([0.941176, 3.941176]) - - def test_weighted_fullweights_real_y(self): - # Supplying full-shape weights for collapsing over a single dimension. - cube_collapsed = self.cube_real.collapsed( - "y", MEAN, weights=self.full_weights_y - ) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_y - ) - - def test_weighted_fullweights_lazy_y(self): - # Full-shape weights, lazy data : Check lazy result, same values as real calc. - cube_collapsed = self.cube_lazy.collapsed( - "y", MEAN, weights=self.full_weights_y - ) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_y - ) - - def test_weighted_1dweights_real_y(self): - # 1-D weights, real data : Check same results as full-shape. - cube_collapsed = self.cube_real.collapsed( - "y", MEAN, weights=self.y_weights - ) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_y - ) - - def test_weighted_1dweights_lazy_y(self): - # 1-D weights, lazy data : Check lazy result, same values as real calc. - cube_collapsed = self.cube_lazy.collapsed( - "y", MEAN, weights=self.y_weights - ) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_y - ) - - def test_weighted_fullweights_real_x(self): - # Full weights, real data, ** collapse X ** : as for 'y' case above - cube_collapsed = self.cube_real.collapsed( - "x", MEAN, weights=self.full_weights_x - ) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_x - ) - - def test_weighted_fullweights_lazy_x(self): - # Full weights, lazy data, ** collapse X ** : as for 'y' case above - cube_collapsed = self.cube_lazy.collapsed( - "x", MEAN, weights=self.full_weights_x - ) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_x - ) - - def test_weighted_1dweights_real_x(self): - # 1-D weights, real data, ** collapse X ** : as for 'y' case above - cube_collapsed = self.cube_real.collapsed( - "x", MEAN, weights=self.x_weights - ) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_x - ) - - def test_weighted_1dweights_lazy_x(self): - # 1-D weights, lazy data, ** collapse X ** : as for 'y' case above - cube_collapsed = self.cube_lazy.collapsed( - "x", MEAN, weights=self.x_weights - ) - self.assertTrue(cube_collapsed.has_lazy_data()) - self.assertArrayAlmostEqual( - cube_collapsed.data, self.expected_result_x - ) - - -class Test_collapsed__cellmeasure_ancils(tests.IrisTest): - def setUp(self): - cube = Cube(np.arange(6.0).reshape((2, 3))) - for i_dim, name in enumerate(("y", "x")): - npts = cube.shape[i_dim] - coord = DimCoord(np.arange(npts), long_name=name) - cube.add_dim_coord(coord, i_dim) - self.ancillary_variable = AncillaryVariable([0, 1], long_name="foo") - cube.add_ancillary_variable(self.ancillary_variable, 0) - self.cell_measure = CellMeasure([0, 1], long_name="bar") - cube.add_cell_measure(self.cell_measure, 0) - self.cube = cube - - def test_ancillary_variables_and_cell_measures_kept(self): - cube_collapsed = self.cube.collapsed("x", MEAN) - self.assertEqual( - cube_collapsed.ancillary_variables(), [self.ancillary_variable] - ) - self.assertEqual(cube_collapsed.cell_measures(), [self.cell_measure]) - - def test_ancillary_variables_and_cell_measures_removed(self): - cube_collapsed = self.cube.collapsed("y", MEAN) - self.assertEqual(cube_collapsed.ancillary_variables(), []) - self.assertEqual(cube_collapsed.cell_measures(), []) - - -class Test_collapsed__warning(tests.IrisTest): - def setUp(self): - self.cube = Cube([[1, 2], [1, 2]]) - lat = DimCoord([1, 2], standard_name="latitude") - lon = DimCoord([1, 2], standard_name="longitude") - grid_lat = AuxCoord([1, 2], standard_name="grid_latitude") - grid_lon = AuxCoord([1, 2], standard_name="grid_longitude") - wibble = AuxCoord([1, 2], long_name="wibble") - - self.cube.add_dim_coord(lat, 0) - self.cube.add_dim_coord(lon, 1) - self.cube.add_aux_coord(grid_lat, 0) - self.cube.add_aux_coord(grid_lon, 1) - self.cube.add_aux_coord(wibble, 1) - - def _aggregator(self, uses_weighting): - # Returns a mock aggregator with a mocked method (uses_weighting) - # which returns the given True/False condition. - aggregator = mock.Mock(spec=WeightedAggregator, lazy_func=None) - aggregator.cell_method = None - aggregator.uses_weighting = mock.Mock(return_value=uses_weighting) - - return aggregator - - def _assert_warn_collapse_without_weight(self, coords, warn): - # Ensure that warning is raised. - msg = "Collapsing spatial coordinate {!r} without weighting" - for coord in coords: - self.assertIn(mock.call(msg.format(coord)), warn.call_args_list) - - def _assert_nowarn_collapse_without_weight(self, coords, warn): - # Ensure that warning is not rised. - msg = "Collapsing spatial coordinate {!r} without weighting" - for coord in coords: - self.assertNotIn(mock.call(msg.format(coord)), warn.call_args_list) - - def test_lat_lon_noweighted_aggregator(self): - # Collapse latitude coordinate with unweighted aggregator. - aggregator = mock.Mock(spec=Aggregator, lazy_func=None) - aggregator.cell_method = None - coords = ["latitude", "longitude"] - - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator, somekeyword="bla") - - self._assert_nowarn_collapse_without_weight(coords, warn) - - def test_lat_lon_weighted_aggregator(self): - # Collapse latitude coordinate with weighted aggregator without - # providing weights. - aggregator = self._aggregator(False) - coords = ["latitude", "longitude"] - - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator) - - coords = [coord for coord in coords if "latitude" in coord] - self._assert_warn_collapse_without_weight(coords, warn) - - def test_lat_lon_weighted_aggregator_with_weights(self): - # Collapse latitude coordinate with a weighted aggregators and - # providing suitable weights. - weights = np.array([[0.1, 0.5], [0.3, 0.2]]) - aggregator = self._aggregator(True) - coords = ["latitude", "longitude"] - - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator, weights=weights) - - self._assert_nowarn_collapse_without_weight(coords, warn) - - def test_lat_lon_weighted_aggregator_alt(self): - # Collapse grid_latitude coordinate with weighted aggregator without - # providing weights. Tests coordinate matching logic. - aggregator = self._aggregator(False) - coords = ["grid_latitude", "grid_longitude"] - - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator) - - coords = [coord for coord in coords if "latitude" in coord] - self._assert_warn_collapse_without_weight(coords, warn) - - def test_no_lat_weighted_aggregator_mixed(self): - # Collapse grid_latitude and an unmatched coordinate (not lat/lon) - # with weighted aggregator without providing weights. - # Tests coordinate matching logic. - aggregator = self._aggregator(False) - coords = ["wibble"] - - with mock.patch("warnings.warn") as warn: - self.cube.collapsed(coords, aggregator) - - self._assert_nowarn_collapse_without_weight(coords, warn) - - -class Test_collapsed_coord_with_3_bounds(tests.IrisTest): - def setUp(self): - self.cube = Cube([1, 2]) - - bounds = [[0.0, 1.0, 2.0], [2.0, 3.0, 4.0]] - lat = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="latitude") - lon = AuxCoord([1.0, 2.0], bounds=bounds, standard_name="longitude") - - self.cube.add_aux_coord(lat, 0) - self.cube.add_aux_coord(lon, 0) - - def _assert_warn_cannot_check_contiguity(self, warn): - # Ensure that warning is raised. - for coord in ["latitude", "longitude"]: - msg = ( - f"Cannot check if coordinate is contiguous: Invalid " - f"operation for '{coord}', with 3 bound(s). Contiguous " - f"bounds are only defined for 1D coordinates with 2 " - f"bounds. Metadata may not be fully descriptive for " - f"'{coord}'. Ignoring bounds." - ) - self.assertIn(mock.call(msg), warn.call_args_list) - - def _assert_cube_as_expected(self, cube): - """Ensure that cube data and coordiantes are as expected.""" - self.assertArrayEqual(cube.data, np.array(3)) - - lat = cube.coord("latitude") - self.assertArrayAlmostEqual(lat.points, np.array([1.5])) - self.assertArrayAlmostEqual(lat.bounds, np.array([[1.0, 2.0]])) - - lon = cube.coord("longitude") - self.assertArrayAlmostEqual(lon.points, np.array([1.5])) - self.assertArrayAlmostEqual(lon.bounds, np.array([[1.0, 2.0]])) - - def test_collapsed_lat_with_3_bounds(self): - """Collapse latitude with 3 bounds.""" - with mock.patch("warnings.warn") as warn: - collapsed_cube = self.cube.collapsed("latitude", iris.analysis.SUM) - self._assert_warn_cannot_check_contiguity(warn) - self._assert_cube_as_expected(collapsed_cube) - - def test_collapsed_lon_with_3_bounds(self): - """Collapse longitude with 3 bounds.""" - with mock.patch("warnings.warn") as warn: - collapsed_cube = self.cube.collapsed( - "longitude", iris.analysis.SUM - ) - self._assert_warn_cannot_check_contiguity(warn) - self._assert_cube_as_expected(collapsed_cube) - - def test_collapsed_lat_lon_with_3_bounds(self): - """Collapse latitude and longitude with 3 bounds.""" - with mock.patch("warnings.warn") as warn: - collapsed_cube = self.cube.collapsed( - ["latitude", "longitude"], iris.analysis.SUM - ) - self._assert_warn_cannot_check_contiguity(warn) - self._assert_cube_as_expected(collapsed_cube) - - -class Test_summary(tests.IrisTest): - def setUp(self): - self.cube = Cube(0) - - def test_cell_datetime_objects(self): - self.cube.add_aux_coord(AuxCoord(42, units="hours since epoch")) - summary = self.cube.summary() - self.assertIn("1970-01-02 18:00:00", summary) - - def test_scalar_str_coord(self): - str_value = "foo" - self.cube.add_aux_coord(AuxCoord(str_value)) - summary = self.cube.summary() - self.assertIn(str_value, summary) - - def test_ancillary_variable(self): - cube = Cube(np.arange(6).reshape(2, 3)) - av = AncillaryVariable([1, 2], "status_flag") - cube.add_ancillary_variable(av, 0) - expected_summary = ( - "unknown / (unknown) (-- : 2; -- : 3)\n" - " Ancillary variables:\n" - " status_flag x -" - ) - self.assertEqual(expected_summary, cube.summary()) - - def test_similar_coords(self): - coord1 = AuxCoord( - 42, long_name="foo", attributes=dict(bar=np.array([2, 5])) - ) - coord2 = coord1.copy() - coord2.attributes = dict(bar="baz") - for coord in [coord1, coord2]: - self.cube.add_aux_coord(coord) - self.assertIn("baz", self.cube.summary()) - - def test_long_components(self): - # Check that components with long names 'stretch' the printout correctly. - cube = Cube(np.zeros((20, 20, 20)), units=1) - dimco = DimCoord(np.arange(20), long_name="dimco") - auxco = AuxCoord(np.zeros(20), long_name="auxco") - ancil = AncillaryVariable(np.zeros(20), long_name="ancil") - cellm = CellMeasure(np.zeros(20), long_name="cellm") - cube.add_dim_coord(dimco, 0) - cube.add_aux_coord(auxco, 0) - cube.add_cell_measure(cellm, 1) - cube.add_ancillary_variable(ancil, 2) - - original_summary = cube.summary() - long_name = "long_name______________________________________" - for component in (dimco, auxco, ancil, cellm): - # For each (type of) component, set a long name so the header columns get shifted. - old_name = component.name() - component.rename(long_name) - new_summary = cube.summary() - component.rename( - old_name - ) # Put each back the way it was afterwards - - # Check that the resulting 'stretched' output has dimension columns aligned correctly. - lines = new_summary.split("\n") - header = lines[0] - colon_inds = [ - i_char for i_char, char in enumerate(header) if char == ":" - ] - for line in lines[1:]: - # Replace all '-' with 'x' to make checking easier, and add a final buffer space. - line = line.replace("-", "x") + " " - if " x " in line: - # For lines with any columns : check that columns are where expected - for col_ind in colon_inds: - # Chop out chars before+after each expected column. - self.assertEqual( - line[col_ind - 1 : col_ind + 2], " x " - ) - - # Finally also: compare old with new, but replacing new name and ignoring spacing differences - def collapse_space(string): - # Replace all multiple spaces with a single space. - while " " in string: - string = string.replace(" ", " ") - return string - - self.assertEqual( - collapse_space(new_summary).replace(long_name, old_name), - collapse_space(original_summary), - ) - - -class Test_is_compatible(tests.IrisTest): - def setUp(self): - self.test_cube = Cube([1.0]) - self.other_cube = self.test_cube.copy() - - def test_noncommon_array_attrs_compatible(self): - # Non-common array attributes should be ok. - self.test_cube.attributes["array_test"] = np.array([1.0, 2, 3]) - self.assertTrue(self.test_cube.is_compatible(self.other_cube)) - - def test_matching_array_attrs_compatible(self): - # Matching array attributes should be ok. - self.test_cube.attributes["array_test"] = np.array([1.0, 2, 3]) - self.other_cube.attributes["array_test"] = np.array([1.0, 2, 3]) - self.assertTrue(self.test_cube.is_compatible(self.other_cube)) - - def test_different_array_attrs_incompatible(self): - # Differing array attributes should make the cubes incompatible. - self.test_cube.attributes["array_test"] = np.array([1.0, 2, 3]) - self.other_cube.attributes["array_test"] = np.array([1.0, 2, 777.7]) - self.assertFalse(self.test_cube.is_compatible(self.other_cube)) - - -class Test_rolling_window(tests.IrisTest): - def setUp(self): - self.cube = Cube(np.arange(6)) - self.multi_dim_cube = Cube(np.arange(36).reshape(6, 6)) - val_coord = DimCoord([0, 1, 2, 3, 4, 5], long_name="val") - month_coord = AuxCoord( - ["jan", "feb", "mar", "apr", "may", "jun"], long_name="month" - ) - extra_coord = AuxCoord([0, 1, 2, 3, 4, 5], long_name="extra") - self.cube.add_dim_coord(val_coord, 0) - self.cube.add_aux_coord(month_coord, 0) - self.multi_dim_cube.add_dim_coord(val_coord, 0) - self.multi_dim_cube.add_aux_coord(extra_coord, 1) - self.ancillary_variable = AncillaryVariable( - [0, 1, 2, 0, 1, 2], long_name="foo" - ) - self.multi_dim_cube.add_ancillary_variable(self.ancillary_variable, 1) - self.cell_measure = CellMeasure([0, 1, 2, 0, 1, 2], long_name="bar") - self.multi_dim_cube.add_cell_measure(self.cell_measure, 1) - - self.mock_agg = mock.Mock(spec=Aggregator) - self.mock_agg.aggregate = mock.Mock(return_value=np.empty([4])) - self.mock_agg.post_process = mock.Mock(side_effect=lambda x, y, z: x) - - def test_string_coord(self): - # Rolling window on a cube that contains a string coordinate. - res_cube = self.cube.rolling_window("val", self.mock_agg, 3) - val_coord = DimCoord( - np.array([1, 2, 3, 4]), - bounds=np.array([[0, 2], [1, 3], [2, 4], [3, 5]]), - long_name="val", - ) - month_coord = AuxCoord( - np.array( - ["jan|feb|mar", "feb|mar|apr", "mar|apr|may", "apr|may|jun"] - ), - bounds=np.array( - [ - ["jan", "mar"], - ["feb", "apr"], - ["mar", "may"], - ["apr", "jun"], - ] - ), - long_name="month", - ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("month"), month_coord) - - def test_kwargs(self): - # Rolling window with missing data not tolerated - window = 2 - self.cube.data = ma.array( - self.cube.data, mask=([True, False, False, False, True, False]) - ) - res_cube = self.cube.rolling_window( - "val", iris.analysis.MEAN, window, mdtol=0 - ) - expected_result = ma.array( - [-99.0, 1.5, 2.5, -99.0, -99.0], - mask=[True, False, False, True, True], - dtype=np.float64, - ) - self.assertMaskedArrayEqual(expected_result, res_cube.data) - - def test_ancillary_variables_and_cell_measures_kept(self): - res_cube = self.multi_dim_cube.rolling_window("val", self.mock_agg, 3) - self.assertEqual( - res_cube.ancillary_variables(), [self.ancillary_variable] - ) - self.assertEqual(res_cube.cell_measures(), [self.cell_measure]) - - def test_ancillary_variables_and_cell_measures_removed(self): - res_cube = self.multi_dim_cube.rolling_window( - "extra", self.mock_agg, 3 - ) - self.assertEqual(res_cube.ancillary_variables(), []) - self.assertEqual(res_cube.cell_measures(), []) - - -class Test_slices_dim_order(tests.IrisTest): - """ - This class tests the capability of iris.cube.Cube.slices(), including its - ability to correctly re-order the dimensions. - """ - - def setUp(self): - """ - setup a 4D iris cube, each dimension is length 1. - The dimensions are; - dim1: time - dim2: height - dim3: latitude - dim4: longitude - """ - self.cube = iris.cube.Cube(np.array([[[[8.0]]]])) - self.cube.add_dim_coord(iris.coords.DimCoord([0], "time"), [0]) - self.cube.add_dim_coord(iris.coords.DimCoord([0], "height"), [1]) - self.cube.add_dim_coord(iris.coords.DimCoord([0], "latitude"), [2]) - self.cube.add_dim_coord(iris.coords.DimCoord([0], "longitude"), [3]) - - @staticmethod - def expected_cube_setup(dim1name, dim2name, dim3name): - """ - input: - ------ - dim1name: str - name of the first dimension coordinate - dim2name: str - name of the second dimension coordinate - dim3name: str - name of the third dimension coordinate - output: - ------ - cube: iris cube - iris cube with the specified axis holding the data 8 - """ - cube = iris.cube.Cube(np.array([[[8.0]]])) - cube.add_dim_coord(iris.coords.DimCoord([0], dim1name), [0]) - cube.add_dim_coord(iris.coords.DimCoord([0], dim2name), [1]) - cube.add_dim_coord(iris.coords.DimCoord([0], dim3name), [2]) - return cube - - def check_order(self, dim1, dim2, dim3, dim_to_remove): - """ - does two things: - (1) slices the 4D cube in dim1, dim2, dim3 (and removes the scalar - coordinate) and - (2) sets up a 3D cube with dim1, dim2, dim3. - input: - ----- - dim1: str - name of first dimension - dim2: str - name of second dimension - dim3: str - name of third dimension - dim_to_remove: str - name of the dimension that transforms into a scalar coordinate - when slicing the cube. - output: - ------ - sliced_cube: 3D cube - the cube that results if slicing the original cube - expected_cube: 3D cube - a cube set up with the axis corresponding to the dims - """ - sliced_cube = next(self.cube.slices([dim1, dim2, dim3])) - sliced_cube.remove_coord(dim_to_remove) - expected_cube = self.expected_cube_setup(dim1, dim2, dim3) - self.assertEqual(sliced_cube, expected_cube) - - def test_all_permutations(self): - for perm in permutations(["time", "height", "latitude", "longitude"]): - self.check_order(*perm) - - -@tests.skip_data -class Test_slices_over(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_4d() - # Define expected iterators for 1D and 2D test cases. - self.exp_iter_1d = range( - len(self.cube.coord("model_level_number").points) - ) - self.exp_iter_2d = np.ndindex(6, 70, 1, 1) - # Define maximum number of interations for particularly long - # (and so time-consuming) iterators. - self.long_iterator_max = 5 - - def test_1d_slice_coord_given(self): - res = self.cube.slices_over(self.cube.coord("model_level_number")) - for i, res_cube in zip(self.exp_iter_1d, res): - expected = self.cube[:, i] - self.assertEqual(res_cube, expected) - - def test_1d_slice_nonexistent_coord_given(self): - with self.assertRaises(CoordinateNotFoundError): - _ = self.cube.slices_over(self.cube.coord("wibble")) - - def test_1d_slice_coord_name_given(self): - res = self.cube.slices_over("model_level_number") - for i, res_cube in zip(self.exp_iter_1d, res): - expected = self.cube[:, i] - self.assertEqual(res_cube, expected) - - def test_1d_slice_nonexistent_coord_name_given(self): - with self.assertRaises(CoordinateNotFoundError): - _ = self.cube.slices_over("wibble") - - def test_1d_slice_dimension_given(self): - res = self.cube.slices_over(1) - for i, res_cube in zip(self.exp_iter_1d, res): - expected = self.cube[:, i] - self.assertEqual(res_cube, expected) - - def test_1d_slice_nonexistent_dimension_given(self): - with self.assertRaisesRegex(ValueError, "iterator over a dimension"): - _ = self.cube.slices_over(self.cube.ndim + 1) - - def test_2d_slice_coord_given(self): - # Slicing over these two dimensions returns 420 2D cubes, so only check - # cubes up to `self.long_iterator_max` to keep test runtime sensible. - res = self.cube.slices_over( - [self.cube.coord("time"), self.cube.coord("model_level_number")] - ) - for ct in range(self.long_iterator_max): - indices = list(next(self.exp_iter_2d)) - # Replace the dimensions not iterated over with spanning slices. - indices[2] = indices[3] = slice(None) - expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) - - def test_2d_slice_nonexistent_coord_given(self): - with self.assertRaises(CoordinateNotFoundError): - _ = self.cube.slices_over( - [self.cube.coord("time"), self.cube.coord("wibble")] - ) - - def test_2d_slice_coord_name_given(self): - # Slicing over these two dimensions returns 420 2D cubes, so only check - # cubes up to `self.long_iterator_max` to keep test runtime sensible. - res = self.cube.slices_over(["time", "model_level_number"]) - for ct in range(self.long_iterator_max): - indices = list(next(self.exp_iter_2d)) - # Replace the dimensions not iterated over with spanning slices. - indices[2] = indices[3] = slice(None) - expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) - - def test_2d_slice_nonexistent_coord_name_given(self): - with self.assertRaises(CoordinateNotFoundError): - _ = self.cube.slices_over(["time", "wibble"]) - - def test_2d_slice_dimension_given(self): - # Slicing over these two dimensions returns 420 2D cubes, so only check - # cubes up to `self.long_iterator_max` to keep test runtime sensible. - res = self.cube.slices_over([0, 1]) - for ct in range(self.long_iterator_max): - indices = list(next(self.exp_iter_2d)) - # Replace the dimensions not iterated over with spanning slices. - indices[2] = indices[3] = slice(None) - expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) - - def test_2d_slice_reversed_dimension_given(self): - # Confirm that reversing the order of the dimensions returns the same - # results as the above test. - res = self.cube.slices_over([1, 0]) - for ct in range(self.long_iterator_max): - indices = list(next(self.exp_iter_2d)) - # Replace the dimensions not iterated over with spanning slices. - indices[2] = indices[3] = slice(None) - expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) - - def test_2d_slice_nonexistent_dimension_given(self): - with self.assertRaisesRegex(ValueError, "iterator over a dimension"): - _ = self.cube.slices_over([0, self.cube.ndim + 1]) - - def test_multidim_slice_coord_given(self): - # Slicing over surface altitude returns 100x100 2D cubes, so only check - # cubes up to `self.long_iterator_max` to keep test runtime sensible. - res = self.cube.slices_over("surface_altitude") - # Define special ndindex iterator for the different dims sliced over. - nditer = np.ndindex(1, 1, 100, 100) - for ct in range(self.long_iterator_max): - indices = list(next(nditer)) - # Replace the dimensions not iterated over with spanning slices. - indices[0] = indices[1] = slice(None) - expected = self.cube[tuple(indices)] - self.assertEqual(next(res), expected) - - def test_duplicate_coordinate_given(self): - res = self.cube.slices_over([1, 1]) - for i, res_cube in zip(self.exp_iter_1d, res): - expected = self.cube[:, i] - self.assertEqual(res_cube, expected) - - def test_non_orthogonal_coordinates_given(self): - res = self.cube.slices_over(["model_level_number", "sigma"]) - for i, res_cube in zip(self.exp_iter_1d, res): - expected = self.cube[:, i] - self.assertEqual(res_cube, expected) - - def test_nodimension(self): - # Slicing over no dimension should return the whole cube. - res = self.cube.slices_over([]) - self.assertEqual(next(res), self.cube) - - -def create_cube(lon_min, lon_max, bounds=False): - n_lons = max(lon_min, lon_max) - min(lon_max, lon_min) - data = np.arange(4 * 3 * n_lons, dtype="f4").reshape(4, 3, -1) - data = as_lazy_data(data) - cube = Cube(data, standard_name="x_wind", units="ms-1") - cube.add_dim_coord( - iris.coords.DimCoord( - [0, 20, 40, 80], long_name="level_height", units="m" - ), - 0, - ) - cube.add_aux_coord( - iris.coords.AuxCoord( - [1.0, 0.9, 0.8, 0.6], long_name="sigma", units="1" - ), - 0, - ) - cube.add_dim_coord( - iris.coords.DimCoord([-45, 0, 45], "latitude", units="degrees"), 1 - ) - step = 1 if lon_max > lon_min else -1 - circular = abs(lon_max - lon_min) == 360 - cube.add_dim_coord( - iris.coords.DimCoord( - np.arange(lon_min, lon_max, step), - "longitude", - units="degrees", - circular=circular, - ), - 2, - ) - if bounds: - cube.coord("longitude").guess_bounds() - cube.add_aux_coord( - iris.coords.AuxCoord( - np.arange(3 * n_lons).reshape(3, -1) * 10, - "surface_altitude", - units="m", - ), - [1, 2], - ) - cube.add_aux_factory( - iris.aux_factory.HybridHeightFactory( - cube.coord("level_height"), - cube.coord("sigma"), - cube.coord("surface_altitude"), - ) - ) - return cube - - -# Ensure all the other coordinates and factories are correctly preserved. -class Test_intersection__Metadata(tests.IrisTest): - def test_metadata(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(170, 190)) - self.assertCMLApproxData(result) - - def test_metadata_wrapped(self): - cube = create_cube(-180, 180) - result = cube.intersection(longitude=(170, 190)) - self.assertCMLApproxData(result) - - -# Explicitly check the handling of `circular` on the result. -class Test_intersection__Circular(tests.IrisTest): - def test_regional(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.coord("longitude").circular) - - def test_regional_wrapped(self): - cube = create_cube(-180, 180) - result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.coord("longitude").circular) - - def test_global(self): - cube = create_cube(-180, 180) - result = cube.intersection(longitude=(-180, 180)) - self.assertTrue(result.coord("longitude").circular) - - def test_global_wrapped(self): - cube = create_cube(-180, 180) - result = cube.intersection(longitude=(10, 370)) - self.assertTrue(result.coord("longitude").circular) - - -# Check the various error conditions. -class Test_intersection__Invalid(tests.IrisTest): - def test_reversed_min_max(self): - cube = create_cube(0, 360) - with self.assertRaises(ValueError): - cube.intersection(longitude=(30, 10)) - - def test_dest_too_large(self): - cube = create_cube(0, 360) - with self.assertRaises(ValueError): - cube.intersection(longitude=(30, 500)) - - def test_src_too_large(self): - cube = create_cube(0, 400) - with self.assertRaises(ValueError): - cube.intersection(longitude=(10, 30)) - - def test_missing_coord(self): - cube = create_cube(0, 360) - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): - cube.intersection(parrots=(10, 30)) - - def test_multi_dim_coord(self): - cube = create_cube(0, 360) - with self.assertRaises(iris.exceptions.CoordinateMultiDimError): - cube.intersection(surface_altitude=(10, 30)) - - def test_null_region(self): - # 10 <= v < 10 - cube = create_cube(0, 360) - with self.assertRaises(IndexError): - cube.intersection(longitude=(10, 10, False, False)) - - -class Test_intersection__Lazy(tests.IrisTest): - def test_real_data(self): - cube = create_cube(0, 360) - cube.data - result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.has_lazy_data()) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(170, 191) - ) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) - - def test_real_data_wrapped(self): - cube = create_cube(-180, 180) - cube.data - result = cube.intersection(longitude=(170, 190)) - self.assertFalse(result.has_lazy_data()) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(170, 191) - ) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_lazy_data(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(170, 190)) - self.assertTrue(result.has_lazy_data()) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(170, 191) - ) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) - - def test_lazy_data_wrapped(self): - cube = create_cube(-180, 180) - result = cube.intersection(longitude=(170, 190)) - self.assertTrue(result.has_lazy_data()) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(170, 191) - ) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - -class Test_intersection_Points(tests.IrisTest): - def test_ignore_bounds(self): - cube = create_cube(0, 30, bounds=True) - result = cube.intersection(longitude=(9.5, 12.5), ignore_bounds=True) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(10, 13) - ) - self.assertArrayEqual(result.coord("longitude").bounds[0], [9.5, 10.5]) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [11.5, 12.5] - ) - - -# Check what happens with a regional, points-only circular intersection -# coordinate. -class Test_intersection__RegionalSrcModulus(tests.IrisTest): - def test_request_subset(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(45, 50)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(45, 51) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(5, 11)) - - def test_request_left(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(35, 45)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(40, 46) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 6)) - - def test_request_right(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(55, 65)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(55, 60) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(15, 20)) - - def test_request_superset(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(35, 65)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(40, 60) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 20)) - - def test_request_subset_modulus(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(45 + 360, 50 + 360)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(45 + 360, 51 + 360) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(5, 11)) - - def test_request_left_modulus(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(35 + 360, 45 + 360)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(40 + 360, 46 + 360) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 6)) - - def test_request_right_modulus(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(55 + 360, 65 + 360)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(55 + 360, 60 + 360) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(15, 20)) - - def test_request_superset_modulus(self): - cube = create_cube(40, 60) - result = cube.intersection(longitude=(35 + 360, 65 + 360)) - self.assertArrayEqual( - result.coord("longitude").points, np.arange(40 + 360, 60 + 360) - ) - self.assertArrayEqual(result.data[0, 0], np.arange(0, 20)) - - def test_tolerance_f4(self): - cube = create_cube(0, 5) - cube.coord("longitude").points = np.array( - [0.0, 3.74999905, 7.49999809, 11.24999714, 14.99999619], dtype="f4" - ) - result = cube.intersection(longitude=(0, 5)) - self.assertArrayAlmostEqual( - result.coord("longitude").points, np.array([0.0, 3.74999905]) - ) - self.assertArrayEqual(result.data[0, 0], np.array([0, 1])) - - def test_tolerance_f8(self): - cube = create_cube(0, 5) - cube.coord("longitude").points = np.array( - [0.0, 3.74999905, 7.49999809, 11.24999714, 14.99999619], dtype="f8" - ) - result = cube.intersection(longitude=(0, 5)) - self.assertArrayAlmostEqual( - result.coord("longitude").points, np.array([0.0, 3.74999905]) - ) - self.assertArrayEqual(result.data[0, 0], np.array([0, 1])) - - -# Check what happens with a global, points-only circular intersection -# coordinate. -class Test_intersection__GlobalSrcModulus(tests.IrisTest): - def test_global_wrapped_extreme_increasing_base_period(self): - # Ensure that we can correctly handle points defined at (base + period) - cube = create_cube(-180.0, 180.0) - lons = cube.coord("longitude") - # Redefine longitude so that points at (base + period) - lons.points = np.linspace(-180.0, 180, lons.points.size) - result = cube.intersection( - longitude=(lons.points.min(), lons.points.max()) - ) - self.assertArrayEqual(result.data, cube.data) - - def test_global_wrapped_extreme_decreasing_base_period(self): - # Ensure that we can correctly handle points defined at (base + period) - cube = create_cube(180.0, -180.0) - lons = cube.coord("longitude") - # Redefine longitude so that points at (base + period) - lons.points = np.linspace(180.0, -180.0, lons.points.size) - result = cube.intersection( - longitude=(lons.points.min(), lons.points.max()) - ) - self.assertArrayEqual(result.data, cube.data) - - def test_global(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(0, 360)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], 359) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 359) - - def test_global_wrapped(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(-180, 180)) - self.assertEqual(result.coord("longitude").points[0], -180) - self.assertEqual(result.coord("longitude").points[-1], 179) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) - - def test_aux_coord(self): - cube = create_cube(0, 360) - cube.replace_coord( - iris.coords.AuxCoord.from_coord(cube.coord("longitude")) - ) - result = cube.intersection(longitude=(0, 360)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], 359) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 359) - - def test_aux_coord_wrapped(self): - cube = create_cube(0, 360) - cube.replace_coord( - iris.coords.AuxCoord.from_coord(cube.coord("longitude")) - ) - result = cube.intersection(longitude=(-180, 180)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], -1) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 359) - - def test_aux_coord_non_contiguous_wrapped(self): - cube = create_cube(0, 360) - coord = iris.coords.AuxCoord.from_coord(cube.coord("longitude")) - coord.points = (coord.points * 1.5) % 360 - cube.replace_coord(coord) - result = cube.intersection(longitude=(-90, 90)) - self.assertEqual(result.coord("longitude").points[0], 0) - self.assertEqual(result.coord("longitude").points[-1], 90) - self.assertEqual(result.data[0, 0, 0], 0) - self.assertEqual(result.data[0, 0, -1], 300) - - def test_decrementing(self): - cube = create_cube(360, 0) - result = cube.intersection(longitude=(40, 60)) - self.assertEqual(result.coord("longitude").points[0], 60) - self.assertEqual(result.coord("longitude").points[-1], 40) - self.assertEqual(result.data[0, 0, 0], 300) - self.assertEqual(result.data[0, 0, -1], 320) - - def test_decrementing_wrapped(self): - cube = create_cube(360, 0) - result = cube.intersection(longitude=(-10, 10)) - self.assertEqual(result.coord("longitude").points[0], 10) - self.assertEqual(result.coord("longitude").points[-1], -10) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_no_wrap_after_modulus(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(170 + 360, 190 + 360)) - self.assertEqual(result.coord("longitude").points[0], 170 + 360) - self.assertEqual(result.coord("longitude").points[-1], 190 + 360) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) - - def test_wrap_after_modulus(self): - cube = create_cube(-180, 180) - result = cube.intersection(longitude=(170 + 360, 190 + 360)) - self.assertEqual(result.coord("longitude").points[0], 170 + 360) - self.assertEqual(result.coord("longitude").points[-1], 190 + 360) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_select_by_coord(self): - cube = create_cube(0, 360) - coord = iris.coords.DimCoord(0, "longitude", units="degrees") - result = cube.intersection(iris.coords.CoordExtent(coord, 10, 30)) - self.assertEqual(result.coord("longitude").points[0], 10) - self.assertEqual(result.coord("longitude").points[-1], 30) - self.assertEqual(result.data[0, 0, 0], 10) - self.assertEqual(result.data[0, 0, -1], 30) - - def test_inclusive_exclusive(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(170, 190, True, False)) - self.assertEqual(result.coord("longitude").points[0], 170) - self.assertEqual(result.coord("longitude").points[-1], 189) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 189) - - def test_exclusive_inclusive(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(170, 190, False)) - self.assertEqual(result.coord("longitude").points[0], 171) - self.assertEqual(result.coord("longitude").points[-1], 190) - self.assertEqual(result.data[0, 0, 0], 171) - self.assertEqual(result.data[0, 0, -1], 190) - - def test_exclusive_exclusive(self): - cube = create_cube(0, 360) - result = cube.intersection(longitude=(170, 190, False, False)) - self.assertEqual(result.coord("longitude").points[0], 171) - self.assertEqual(result.coord("longitude").points[-1], 189) - self.assertEqual(result.data[0, 0, 0], 171) - self.assertEqual(result.data[0, 0, -1], 189) - - def test_single_point(self): - # 10 <= v <= 10 - cube = create_cube(0, 360) - result = cube.intersection(longitude=(10, 10)) - self.assertEqual(result.coord("longitude").points[0], 10) - self.assertEqual(result.coord("longitude").points[-1], 10) - self.assertEqual(result.data[0, 0, 0], 10) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_two_points(self): - # -1.5 <= v <= 0.5 - cube = create_cube(0, 360) - result = cube.intersection(longitude=(-1.5, 0.5)) - self.assertEqual(result.coord("longitude").points[0], -1) - self.assertEqual(result.coord("longitude").points[-1], 0) - self.assertEqual(result.data[0, 0, 0], 359) - self.assertEqual(result.data[0, 0, -1], 0) - - def test_wrap_radians(self): - cube = create_cube(0, 360) - cube.coord("longitude").convert_units("radians") - result = cube.intersection(longitude=(-1, 0.5)) - self.assertArrayAllClose( - result.coord("longitude").points, np.arange(-57, 29) * np.pi / 180 - ) - self.assertEqual(result.data[0, 0, 0], 303) - self.assertEqual(result.data[0, 0, -1], 28) - - def test_tolerance_bug(self): - # Floating point changes introduced by wrapping mean - # the resulting coordinate values are not equal to their - # equivalents. This led to a bug that this test checks. - cube = create_cube(0, 400) - cube.coord("longitude").points = np.linspace(-179.55, 179.55, 400) - result = cube.intersection(longitude=(125, 145)) - self.assertArrayAlmostEqual( - result.coord("longitude").points, - cube.coord("longitude").points[339:361], - ) - - def test_tolerance_bug_wrapped(self): - cube = create_cube(0, 400) - cube.coord("longitude").points = np.linspace(-179.55, 179.55, 400) - result = cube.intersection(longitude=(-190, -170)) - # Expected result is the last 11 and first 11 points. - expected = np.append( - cube.coord("longitude").points[389:] - 360.0, - cube.coord("longitude").points[:11], - ) - self.assertArrayAlmostEqual(result.coord("longitude").points, expected) - - -# Check what happens with a global, points-and-bounds circular -# intersection coordinate. -class Test_intersection__ModulusBounds(tests.IrisTest): - def test_global_wrapped_extreme_increasing_base_period(self): - # Ensure that we can correctly handle bounds defined at (base + period) - cube = create_cube(-180.0, 180.0, bounds=True) - lons = cube.coord("longitude") - result = cube.intersection( - longitude=(lons.bounds.min(), lons.bounds.max()) - ) - self.assertArrayEqual(result.data, cube.data) - - def test_global_wrapped_extreme_decreasing_base_period(self): - # Ensure that we can correctly handle bounds defined at (base + period) - cube = create_cube(180.0, -180.0, bounds=True) - lons = cube.coord("longitude") - result = cube.intersection( - longitude=(lons.bounds.min(), lons.bounds.max()) - ) - self.assertArrayEqual(result.data, cube.data) - - def test_misaligned_points_inside(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(169.75, 190.25)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [169.5, 170.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [189.5, 190.5] - ) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) - - def test_misaligned_points_outside(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(170.25, 189.75)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [169.5, 170.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [189.5, 190.5] - ) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) - - def test_misaligned_bounds(self): - cube = create_cube(-180, 180, bounds=True) - result = cube.intersection(longitude=(0, 360)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [-0.5, 0.5]) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [358.5, 359.5] - ) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) - - def test_misaligned_bounds_decreasing(self): - cube = create_cube(180, -180, bounds=True) - result = cube.intersection(longitude=(0, 360)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [359.5, 358.5] - ) - self.assertArrayEqual(result.coord("longitude").points[-1], 0) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [0.5, -0.5] - ) - self.assertEqual(result.data[0, 0, 0], 181) - self.assertEqual(result.data[0, 0, -1], 180) - - def test_aligned_inclusive(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(170.5, 189.5)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [169.5, 170.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [189.5, 190.5] - ) - self.assertEqual(result.data[0, 0, 0], 170) - self.assertEqual(result.data[0, 0, -1], 190) - - def test_aligned_exclusive(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(170.5, 189.5, False, False)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [170.5, 171.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [188.5, 189.5] - ) - self.assertEqual(result.data[0, 0, 0], 171) - self.assertEqual(result.data[0, 0, -1], 189) - - def test_aligned_bounds_at_modulus(self): - cube = create_cube(-179.5, 180.5, bounds=True) - result = cube.intersection(longitude=(0, 360)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0, 1]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [359, 360]) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) - - def test_negative_aligned_bounds_at_modulus(self): - cube = create_cube(0.5, 360.5, bounds=True) - result = cube.intersection(longitude=(-180, 180)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [-180, -179] - ) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [179, 180]) - self.assertEqual(result.data[0, 0, 0], 180) - self.assertEqual(result.data[0, 0, -1], 179) - - def test_negative_misaligned_points_inside(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(-10.25, 10.25)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [-10.5, -9.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [9.5, 10.5] - ) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_negative_misaligned_points_outside(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(-9.75, 9.75)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [-10.5, -9.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [9.5, 10.5] - ) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_negative_aligned_inclusive(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(-10.5, 10.5)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [-11.5, -10.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [10.5, 11.5] - ) - self.assertEqual(result.data[0, 0, 0], 349) - self.assertEqual(result.data[0, 0, -1], 11) - - def test_negative_aligned_exclusive(self): - cube = create_cube(0, 360, bounds=True) - result = cube.intersection(longitude=(-10.5, 10.5, False, False)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [-10.5, -9.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [9.5, 10.5] - ) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_decrementing(self): - cube = create_cube(360, 0, bounds=True) - result = cube.intersection(longitude=(40, 60)) - self.assertArrayEqual( - result.coord("longitude").bounds[0], [60.5, 59.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [40.5, 39.5] - ) - self.assertEqual(result.data[0, 0, 0], 300) - self.assertEqual(result.data[0, 0, -1], 320) - - def test_decrementing_wrapped(self): - cube = create_cube(360, 0, bounds=True) - result = cube.intersection(longitude=(-10, 10)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [10.5, 9.5]) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [-9.5, -10.5] - ) - self.assertEqual(result.data[0, 0, 0], 350) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_numerical_tolerance(self): - # test the tolerance on the coordinate value is not causing a - # modulus wrapping - cube = create_cube(28.5, 68.5, bounds=True) - result = cube.intersection(longitude=(27.74, 68.61)) - result_lons = result.coord("longitude") - self.assertAlmostEqual(result_lons.points[0], 28.5) - self.assertAlmostEqual(result_lons.points[-1], 67.5) - dtype = result_lons.dtype - np.testing.assert_array_almost_equal( - result_lons.bounds[0], np.array([28.0, 29.0], dtype=dtype) - ) - np.testing.assert_array_almost_equal( - result_lons.bounds[-1], np.array([67.0, 68.0], dtype=dtype) - ) - - def test_numerical_tolerance_wrapped(self): - # test the tolerance on the coordinate value causes modulus wrapping - # where appropriate - cube = create_cube(0.5, 3600.5, bounds=True) - lons = cube.coord("longitude") - lons.points = lons.points / 10 - lons.bounds = lons.bounds / 10 - result = cube.intersection(longitude=(-60, 60)) - result_lons = result.coord("longitude") - self.assertAlmostEqual(result_lons.points[0], -60.05) - self.assertAlmostEqual(result_lons.points[-1], 60.05) - dtype = result_lons.dtype - np.testing.assert_array_almost_equal( - result_lons.bounds[0], np.array([-60.1, -60.0], dtype=dtype) - ) - np.testing.assert_array_almost_equal( - result_lons.bounds[-1], np.array([60.0, 60.1], dtype=dtype) - ) - - def test_ignore_bounds_wrapped(self): - # Test `ignore_bounds` fully ignores bounds when wrapping - cube = create_cube(0, 360, bounds=True) - result = cube.intersection( - longitude=(10.25, 370.25), ignore_bounds=True - ) - # Expect points 11..370 not bounds [9.5, 10.5] .. [368.5, 369.5] - self.assertArrayEqual( - result.coord("longitude").bounds[0], [10.5, 11.5] - ) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [369.5, 370.5] - ) - self.assertEqual(result.data[0, 0, 0], 11) - self.assertEqual(result.data[0, 0, -1], 10) - - def test_within_cell(self): - # Test cell is included when it entirely contains the requested range - cube = create_cube(0, 10, bounds=True) - result = cube.intersection(longitude=(0.7, 0.8)) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [0.5, 1.5]) - self.assertEqual(result.data[0, 0, 0], 1) - self.assertEqual(result.data[0, 0, -1], 1) - - def test_threshold_half(self): - cube = create_cube(0, 10, bounds=True) - result = cube.intersection(longitude=(1, 6.999), threshold=0.5) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [5.5, 6.5]) - self.assertEqual(result.data[0, 0, 0], 1) - self.assertEqual(result.data[0, 0, -1], 6) - - def test_threshold_full(self): - cube = create_cube(0, 10, bounds=True) - result = cube.intersection(longitude=(0.5, 7.499), threshold=1) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual(result.coord("longitude").bounds[-1], [5.5, 6.5]) - self.assertEqual(result.data[0, 0, 0], 1) - self.assertEqual(result.data[0, 0, -1], 6) - - def test_threshold_wrapped(self): - # Test that a cell is wrapped to `maximum` if required to exceed - # the threshold - cube = create_cube(-180, 180, bounds=True) - result = cube.intersection(longitude=(0.4, 360.4), threshold=0.2) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [359.5, 360.5] - ) - self.assertEqual(result.data[0, 0, 0], 181) - self.assertEqual(result.data[0, 0, -1], 180) - - def test_threshold_wrapped_gap(self): - # Test that a cell is wrapped to `maximum` if required to exceed - # the threshold (even with a gap in the range) - cube = create_cube(-180, 180, bounds=True) - result = cube.intersection(longitude=(0.4, 360.35), threshold=0.2) - self.assertArrayEqual(result.coord("longitude").bounds[0], [0.5, 1.5]) - self.assertArrayEqual( - result.coord("longitude").bounds[-1], [359.5, 360.5] - ) - self.assertEqual(result.data[0, 0, 0], 181) - self.assertEqual(result.data[0, 0, -1], 180) - - -def unrolled_cube(): - data = np.arange(5, dtype="f4") - cube = Cube(data) - cube.add_aux_coord( - iris.coords.AuxCoord( - [5.0, 10.0, 8.0, 5.0, 3.0], "longitude", units="degrees" - ), - 0, - ) - cube.add_aux_coord( - iris.coords.AuxCoord([1.0, 3.0, -2.0, -1.0, -4.0], "latitude"), 0 - ) - return cube - - -# Check what happens with a "unrolled" scatter-point data with a circular -# intersection coordinate. -class Test_intersection__ScatterModulus(tests.IrisTest): - def test_subset(self): - cube = unrolled_cube() - result = cube.intersection(longitude=(5, 8)) - self.assertArrayEqual(result.coord("longitude").points, [5, 8, 5]) - self.assertArrayEqual(result.data, [0, 2, 3]) - - def test_subset_wrapped(self): - cube = unrolled_cube() - result = cube.intersection(longitude=(5 + 360, 8 + 360)) - self.assertArrayEqual( - result.coord("longitude").points, [365, 368, 365] - ) - self.assertArrayEqual(result.data, [0, 2, 3]) - - def test_superset(self): - cube = unrolled_cube() - result = cube.intersection(longitude=(0, 15)) - self.assertArrayEqual( - result.coord("longitude").points, [5, 10, 8, 5, 3] - ) - self.assertArrayEqual(result.data, np.arange(5)) - - -# Test the API of the cube interpolation method. -class Test_interpolate(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_2d() - - self.scheme = mock.Mock(name="interpolation scheme") - self.interpolator = mock.Mock(name="interpolator") - self.interpolator.return_value = mock.sentinel.RESULT - self.scheme.interpolator.return_value = self.interpolator - self.collapse_coord = True - - def test_api(self): - sample_points = (("foo", 0.5), ("bar", 0.6)) - result = self.cube.interpolate( - sample_points, self.scheme, self.collapse_coord - ) - self.scheme.interpolator.assert_called_once_with( - self.cube, ("foo", "bar") - ) - self.interpolator.assert_called_once_with( - (0.5, 0.6), collapse_scalar=self.collapse_coord - ) - self.assertIs(result, mock.sentinel.RESULT) - - -class Test_regrid(tests.IrisTest): - def test(self): - # Test that Cube.regrid() just defers to the regridder of the - # given scheme. - - # Define a fake scheme and its associated regridder which just - # capture their arguments and return them in place of the - # regridded cube. - class FakeRegridder: - def __init__(self, *args): - self.args = args - - def __call__(self, cube): - return self.args + (cube,) - - class FakeScheme: - def regridder(self, src, target): - return FakeRegridder(self, src, target) - - cube = Cube(0) - scheme = FakeScheme() - result = cube.regrid(mock.sentinel.TARGET, scheme) - self.assertEqual(result, (scheme, cube, mock.sentinel.TARGET, cube)) - - -class Test_copy(tests.IrisTest): - def _check_copy(self, cube, cube_copy): - self.assertIsNot(cube_copy, cube) - self.assertEqual(cube_copy, cube) - self.assertIsNot(cube_copy.core_data(), cube.core_data()) - if ma.isMaskedArray(cube.data): - self.assertMaskedArrayEqual(cube_copy.data, cube.data) - if cube.data.mask is not ma.nomask: - # "No mask" is a constant : all other cases must be distinct. - self.assertIsNot( - cube_copy.core_data().mask, cube.core_data().mask - ) - else: - self.assertArrayEqual(cube_copy.data, cube.data) - - def test(self): - cube = stock.simple_3d() - self._check_copy(cube, cube.copy()) - - def test_copy_ancillary_variables(self): - cube = stock.simple_3d() - avr = AncillaryVariable([2, 3], long_name="foo") - cube.add_ancillary_variable(avr, 0) - self._check_copy(cube, cube.copy()) - - def test_copy_cell_measures(self): - cube = stock.simple_3d() - cms = CellMeasure([2, 3], long_name="foo") - cube.add_cell_measure(cms, 0) - self._check_copy(cube, cube.copy()) - - def test__masked_emptymask(self): - cube = Cube(ma.array([0, 1])) - self._check_copy(cube, cube.copy()) - - def test__masked_arraymask(self): - cube = Cube(ma.array([0, 1], mask=[True, False])) - self._check_copy(cube, cube.copy()) - - def test__scalar(self): - cube = Cube(0) - self._check_copy(cube, cube.copy()) - - def test__masked_scalar_emptymask(self): - cube = Cube(ma.array(0)) - self._check_copy(cube, cube.copy()) - - def test__masked_scalar_arraymask(self): - cube = Cube(ma.array(0, mask=False)) - self._check_copy(cube, cube.copy()) - - def test__lazy(self): - # 2022-11-02: Dask's current behaviour is that the computed array will - # be the same for cube and cube.copy(), even if the Dask arrays are - # different. - cube = Cube(as_lazy_data(np.array([1, 0]))) - self._check_copy(cube, cube.copy()) - - -def _add_test_meshcube(self, nomesh=False, n_z=2, **meshcoord_kwargs): - """ - Common setup action : Create a standard mesh test cube with a variety of coords, and save the cube and various of - its components as properties of the 'self' TestCase. - - """ - nomesh_faces = 5 if nomesh else None - cube, parts = sample_mesh_cube( - nomesh_faces=nomesh_faces, n_z=n_z, with_parts=True, **meshcoord_kwargs - ) - mesh, zco, mesh_dimco, auxco_x, meshx, meshy = parts - self.mesh = mesh - self.dimco_z = zco - self.dimco_mesh = mesh_dimco - if not nomesh: - self.meshco_x = meshx - self.meshco_y = meshy - self.auxco_x = auxco_x - self.allcoords = [meshx, meshy, zco, mesh_dimco, auxco_x] - self.cube = cube - - -class Test_coords__mesh_coords(tests.IrisTest): - """ - Checking *only* the new "mesh_coords" keyword of the coord/coords methods. - - This is *not* attached to the existing tests for this area, as they are - very old and patchy legacy tests. See: iris.tests.test_cdm.TestQueryCoord. - - """ - - def setUp(self): - # Create a standard test cube with a variety of types of coord. - _add_test_meshcube(self) - - def _assert_lists_equal(self, items_a, items_b): - """ - Check that two lists of coords, cubes etc contain the same things. - Lists must contain the same items, including any repeats, but can be in - a different order. - - """ - - # Compare (and thus sort) by their *common* metadata. - def sortkey(item): - return BaseMetadata.from_metadata(item.metadata) - - items_a = sorted(items_a, key=sortkey) - items_b = sorted(items_b, key=sortkey) - self.assertEqual(items_a, items_b) - - def test_coords__all__meshcoords_default(self): - # coords() includes mesh-coords along with the others. - result = self.cube.coords() - expected = self.allcoords - self._assert_lists_equal(expected, result) - - def test_coords__all__meshcoords_only(self): - # Coords(mesh_coords=True) returns only mesh-coords. - result = self.cube.coords(mesh_coords=True) - expected = [self.meshco_x, self.meshco_y] - self._assert_lists_equal(expected, result) - - def test_coords__all__meshcoords_omitted(self): - # Coords(mesh_coords=False) omits the mesh-coords. - result = self.cube.coords(mesh_coords=False) - expected = set(self.allcoords) - set([self.meshco_x, self.meshco_y]) - self._assert_lists_equal(expected, result) - - def test_coords__axis__meshcoords(self): - # Coord (singular) with axis + mesh_coords=True - result = self.cube.coord(axis="x", mesh_coords=True) - self.assertIs(result, self.meshco_x) - - def test_coords__dimcoords__meshcoords(self): - # dim_coords and mesh_coords should be mutually exclusive. - result = self.cube.coords(dim_coords=True, mesh_coords=True) - self.assertEqual(result, []) - - def test_coords__nodimcoords__meshcoords(self): - # When mesh_coords=True, dim_coords=False should have no effect. - result = self.cube.coords(dim_coords=False, mesh_coords=True) - expected = [self.meshco_x, self.meshco_y] - self._assert_lists_equal(expected, result) - - -class Test_mesh(tests.IrisTest): - def setUp(self): - # Create a standard test cube with a variety of types of coord. - _add_test_meshcube(self) - - def test_mesh(self): - result = self.cube.mesh - self.assertIs(result, self.mesh) - - def test_no_mesh(self): - # Replace standard setUp cube with a no-mesh version. - _add_test_meshcube(self, nomesh=True) - result = self.cube.mesh - self.assertIsNone(result) - - -class Test_location(tests.IrisTest): - def setUp(self): - # Create a standard test cube with a variety of types of coord. - _add_test_meshcube(self) - - def test_no_mesh(self): - # Replace standard setUp cube with a no-mesh version. - _add_test_meshcube(self, nomesh=True) - result = self.cube.location - self.assertIsNone(result) - - def test_mesh(self): - cube = self.cube - result = cube.location - self.assertEqual(result, self.meshco_x.location) - - def test_alternate_location(self): - # Replace standard setUp cube with an edge-based version. - _add_test_meshcube(self, location="edge") - cube = self.cube - result = cube.location - self.assertEqual(result, "edge") - - -class Test_mesh_dim(tests.IrisTest): - def setUp(self): - # Create a standard test cube with a variety of types of coord. - _add_test_meshcube(self) - - def test_no_mesh(self): - # Replace standard setUp cube with a no-mesh version. - _add_test_meshcube(self, nomesh=True) - result = self.cube.mesh_dim() - self.assertIsNone(result) - - def test_mesh(self): - cube = self.cube - result = cube.mesh_dim() - self.assertEqual(result, 1) - - def test_alternate(self): - # Replace standard setUp cube with an edge-based version. - _add_test_meshcube(self, location="edge") - cube = self.cube - # Transpose the cube : the mesh dim is then 0 - cube.transpose() - result = cube.mesh_dim() - self.assertEqual(result, 0) - - -class Test__init__mesh(tests.IrisTest): - """ - Test that creation with mesh-coords functions, and prevents a cube having - incompatible mesh-coords. - - """ - - def setUp(self): - # Create a standard test mesh and other useful components. - mesh = sample_mesh() - meshco = sample_meshcoord(mesh=mesh) - self.mesh = mesh - self.meshco = meshco - self.nz = 2 - self.n_faces = meshco.shape[0] - - def test_mesh(self): - # Create a new cube from some of the parts. - nz, n_faces = self.nz, self.n_faces - dimco_z = DimCoord(np.arange(nz), long_name="z") - dimco_mesh = DimCoord(np.arange(n_faces), long_name="x") - meshco = self.meshco - cube = Cube( - np.zeros((nz, n_faces)), - dim_coords_and_dims=[(dimco_z, 0), (dimco_mesh, 1)], - aux_coords_and_dims=[(meshco, 1)], - ) - self.assertEqual(cube.mesh, meshco.mesh) - - def test_fail_dim_meshcoord(self): - # As "test_mesh", but attempt to use the meshcoord as a dim-coord. - # This should not be allowed. - nz, n_faces = self.nz, self.n_faces - dimco_z = DimCoord(np.arange(nz), long_name="z") - meshco = self.meshco - with self.assertRaisesRegex(ValueError, "may not be an AuxCoord"): - Cube( - np.zeros((nz, n_faces)), - dim_coords_and_dims=[(dimco_z, 0), (meshco, 1)], - ) - - def test_multi_meshcoords(self): - meshco_x = sample_meshcoord(axis="x", mesh=self.mesh) - meshco_y = sample_meshcoord(axis="y", mesh=self.mesh) - n_faces = meshco_x.shape[0] - cube = Cube( - np.zeros(n_faces), - aux_coords_and_dims=[(meshco_x, 0), (meshco_y, 0)], - ) - self.assertEqual(cube.mesh, meshco_x.mesh) - - def test_multi_meshcoords_same_axis(self): - # *Not* an error, as long as the coords are distinguishable. - meshco_1 = sample_meshcoord(axis="x", mesh=self.mesh) - meshco_2 = sample_meshcoord(axis="x", mesh=self.mesh) - # Can't make these different at creation, owing to the limited - # constructor args, but we can adjust common metadata afterwards. - meshco_2.rename("junk_name") - - n_faces = meshco_1.shape[0] - cube = Cube( - np.zeros(n_faces), - aux_coords_and_dims=[(meshco_1, 0), (meshco_2, 0)], - ) - self.assertEqual(cube.mesh, meshco_1.mesh) - - def test_fail_meshcoords_different_locations(self): - # Same as successful 'multi_mesh', but different locations. - # N.B. must have a mesh with n-faces == n-edges to test this - mesh = sample_mesh(n_faces=7, n_edges=7) - meshco_1 = sample_meshcoord(axis="x", mesh=mesh, location="face") - meshco_2 = sample_meshcoord(axis="y", mesh=mesh, location="edge") - # They should still have the same *shape* (or would fail anyway) - self.assertEqual(meshco_1.shape, meshco_2.shape) - n_faces = meshco_1.shape[0] - msg = "does not match existing cube location" - with self.assertRaisesRegex(ValueError, msg): - Cube( - np.zeros(n_faces), - aux_coords_and_dims=[(meshco_1, 0), (meshco_2, 0)], - ) - - def test_meshcoords_equal_meshes(self): - meshco_x = sample_meshcoord(axis="x") - meshco_y = sample_meshcoord(axis="y") - n_faces = meshco_x.shape[0] - Cube( - np.zeros(n_faces), - aux_coords_and_dims=[(meshco_x, 0), (meshco_y, 0)], - ) - - def test_fail_meshcoords_different_meshes(self): - meshco_x = sample_meshcoord(axis="x") - meshco_y = sample_meshcoord(axis="y") # Own (different) mesh - meshco_y.mesh.long_name = "new_name" - n_faces = meshco_x.shape[0] - with self.assertRaisesRegex(ValueError, "Mesh.* does not match"): - Cube( - np.zeros(n_faces), - aux_coords_and_dims=[(meshco_x, 0), (meshco_y, 0)], - ) - - def test_fail_meshcoords_different_dims(self): - # Same as 'test_mesh', but meshcoords on different dimensions. - # Replace standard setup with one where n_z == n_faces. - n_z, n_faces = 4, 4 - mesh = sample_mesh(n_faces=n_faces) - meshco_x = sample_meshcoord(mesh=mesh, axis="x") - meshco_y = sample_meshcoord(mesh=mesh, axis="y") - msg = "does not match existing cube mesh dimension" - with self.assertRaisesRegex(ValueError, msg): - Cube( - np.zeros((n_z, n_faces)), - aux_coords_and_dims=[(meshco_x, 1), (meshco_y, 0)], - ) - - -class Test__add_aux_coord__mesh(tests.IrisTest): - """ - Test that "Cube.add_aux_coord" functions with a mesh-coord, and prevents a - cube having incompatible mesh-coords. - - """ - - def setUp(self): - _add_test_meshcube(self) - # Remove the existing "meshco_y", so we can add similar ones without - # needing to distinguish from the existing. - self.cube.remove_coord(self.meshco_y) - - def test_add_compatible(self): - cube = self.cube - meshco_y = self.meshco_y - # Add the y-meshco back into the cube. - cube.add_aux_coord(meshco_y, 1) - self.assertIn(meshco_y, cube.coords(mesh_coords=True)) - - def test_add_multiple(self): - # Show that we can add extra mesh coords. - cube = self.cube - meshco_y = self.meshco_y - # Add the y-meshco back into the cube. - cube.add_aux_coord(meshco_y, 1) - # Make a duplicate y-meshco, renamed so it can add into the cube. - new_meshco_y = meshco_y.copy() - new_meshco_y.rename("alternative") - cube.add_aux_coord(new_meshco_y, 1) - self.assertEqual(len(cube.coords(mesh_coords=True)), 3) - - def test_add_equal_mesh(self): - # Make a duplicate y-meshco, and rename so it can add into the cube. - cube = self.cube - # Create 'meshco_y' duplicate, but a new mesh - meshco_y = sample_meshcoord(axis="y") - cube.add_aux_coord(meshco_y, 1) - self.assertIn(meshco_y, cube.coords(mesh_coords=True)) - - def test_fail_different_mesh(self): - # Make a duplicate y-meshco, and rename so it can add into the cube. - cube = self.cube - # Create 'meshco_y' duplicate, but a new mesh - meshco_y = sample_meshcoord(axis="y") - meshco_y.mesh.long_name = "new_name" - msg = "does not match existing cube mesh" - with self.assertRaisesRegex(ValueError, msg): - cube.add_aux_coord(meshco_y, 1) - - def test_fail_different_location(self): - # Make a new mesh with equal n_faces and n_edges - mesh = sample_mesh(n_faces=4, n_edges=4) - # Re-make the test objects based on that. - _add_test_meshcube(self, mesh=mesh) - cube = self.cube - cube.remove_coord(self.meshco_y) # Remove y-coord, as in setUp() - # Create a new meshco_y, same mesh but based on edges. - meshco_y = sample_meshcoord(axis="y", mesh=self.mesh, location="edge") - msg = "does not match existing cube location" - with self.assertRaisesRegex(ValueError, msg): - cube.add_aux_coord(meshco_y, 1) - - def test_fail_different_dimension(self): - # Re-make the test objects with the non-mesh dim equal in length. - n_faces = self.cube.shape[1] - _add_test_meshcube(self, n_z=n_faces) - cube = self.cube - meshco_y = self.meshco_y - cube.remove_coord(meshco_y) # Remove y-coord, as in setUp() - - # Attempt to re-attach the 'y' meshcoord, to a different cube dimension. - msg = "does not match existing cube mesh dimension" - with self.assertRaisesRegex(ValueError, msg): - cube.add_aux_coord(meshco_y, 0) - - -class Test__add_dim_coord__mesh(tests.IrisTest): - """ - Test that "Cube.add_dim_coord" cannot work with a mesh-coord. - - """ - - def test(self): - # Create a mesh with only 2 faces, so coord *can't* be non-monotonic. - mesh = sample_mesh(n_faces=2) - meshco = sample_meshcoord(mesh=mesh) - cube = Cube([0, 1]) - with self.assertRaisesRegex(ValueError, "may not be an AuxCoord"): - cube.add_dim_coord(meshco, 0) - - -class Test__eq__mesh(tests.IrisTest): - """ - Check that cubes with meshes support == as expected. - - Note: there is no special code for this in iris.cube.Cube : it is - provided by the coord comparisons. - - """ - - def setUp(self): - # Create a 'standard' test cube. - _add_test_meshcube(self) - - def test_copied_cube_match(self): - cube = self.cube - cube2 = cube.copy() - self.assertEqual(cube, cube2) - - def test_equal_mesh_match(self): - cube1 = self.cube - # re-create an identical cube, using the same mesh. - _add_test_meshcube(self) - cube2 = self.cube - self.assertEqual(cube1, cube2) - - def test_new_mesh_different(self): - cube1 = self.cube - # re-create an identical cube, using a different mesh. - _add_test_meshcube(self) - self.cube.mesh.long_name = "new_name" - cube2 = self.cube - self.assertNotEqual(cube1, cube2) - - -class Test_dtype(tests.IrisTest): - def setUp(self): - self.dtypes = ( - np.dtype("int"), - np.dtype("uint"), - np.dtype("bool"), - np.dtype("float"), - ) - - def test_real_data(self): - for dtype in self.dtypes: - data = np.array([0, 1], dtype=dtype) - cube = Cube(data) - self.assertEqual(cube.dtype, dtype) - - def test_real_data_masked__mask_unset(self): - for dtype in self.dtypes: - data = ma.array([0, 1], dtype=dtype) - cube = Cube(data) - self.assertEqual(cube.dtype, dtype) - - def test_real_data_masked__mask_set(self): - for dtype in self.dtypes: - data = ma.array([0, 1], dtype=dtype) - data[0] = ma.masked - cube = Cube(data) - self.assertEqual(cube.dtype, dtype) - - def test_lazy_data(self): - for dtype in self.dtypes: - data = np.array([0, 1], dtype=dtype) - cube = Cube(as_lazy_data(data)) - self.assertEqual(cube.dtype, dtype) - # Check that accessing the dtype does not trigger loading - # of the data. - self.assertTrue(cube.has_lazy_data()) - - def test_lazy_data_masked__mask_unset(self): - for dtype in self.dtypes: - data = ma.array([0, 1], dtype=dtype) - cube = Cube(as_lazy_data(data)) - self.assertEqual(cube.dtype, dtype) - # Check that accessing the dtype does not trigger loading - # of the data. - self.assertTrue(cube.has_lazy_data()) - - def test_lazy_data_masked__mask_set(self): - for dtype in self.dtypes: - data = ma.array([0, 1], dtype=dtype) - data[0] = ma.masked - cube = Cube(as_lazy_data(data)) - self.assertEqual(cube.dtype, dtype) - # Check that accessing the dtype does not trigger loading - # of the data. - self.assertTrue(cube.has_lazy_data()) - - -class TestSubset(tests.IrisTest): - def test_scalar_coordinate(self): - cube = Cube(0, long_name="apricot", units="1") - cube.add_aux_coord(DimCoord([0], long_name="banana", units="1")) - result = cube.subset(cube.coord("banana")) - self.assertEqual(cube, result) - - def test_dimensional_coordinate(self): - cube = Cube(np.zeros((4)), long_name="tinned_peach", units="1") - cube.add_dim_coord( - DimCoord([0, 1, 2, 3], long_name="sixteen_ton_weight", units="1"), - 0, - ) - result = cube.subset(cube.coord("sixteen_ton_weight")) - self.assertEqual(cube, result) - - def test_missing_coordinate(self): - cube = Cube(0, long_name="raspberry", units="1") - cube.add_aux_coord(DimCoord([0], long_name="loganberry", units="1")) - bad_coord = DimCoord([0], long_name="tiger", units="1") - self.assertRaises(CoordinateNotFoundError, cube.subset, bad_coord) - - def test_different_coordinate(self): - cube = Cube(0, long_name="raspberry", units="1") - cube.add_aux_coord(DimCoord([0], long_name="loganberry", units="1")) - different_coord = DimCoord([2], long_name="loganberry", units="1") - result = cube.subset(different_coord) - self.assertEqual(result, None) - - def test_different_coordinate_vector(self): - cube = Cube([0, 1], long_name="raspberry", units="1") - cube.add_dim_coord( - DimCoord([0, 1], long_name="loganberry", units="1"), 0 - ) - different_coord = DimCoord([2], long_name="loganberry", units="1") - result = cube.subset(different_coord) - self.assertEqual(result, None) - - def test_not_coordinate(self): - cube = Cube(0, long_name="peach", units="1") - cube.add_aux_coord(DimCoord([0], long_name="crocodile", units="1")) - self.assertRaises(ValueError, cube.subset, "Pointed Stick") - - -class Test_add_metadata(tests.IrisTest): - def test_add_dim_coord(self): - cube = Cube(np.arange(3)) - x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") - cube.add_dim_coord(x_coord, 0) - self.assertEqual(cube.coord("x"), x_coord) - - def test_add_aux_coord(self): - cube = Cube(np.arange(6).reshape(2, 3)) - x_coord = AuxCoord(points=np.arange(6).reshape(2, 3), long_name="x") - cube.add_aux_coord(x_coord, [0, 1]) - self.assertEqual(cube.coord("x"), x_coord) - - def test_add_cell_measure(self): - cube = Cube(np.arange(6).reshape(2, 3)) - a_cell_measure = CellMeasure( - np.arange(6).reshape(2, 3), long_name="area" - ) - cube.add_cell_measure(a_cell_measure, [0, 1]) - self.assertEqual(cube.cell_measure("area"), a_cell_measure) - - def test_add_ancillary_variable(self): - cube = Cube(np.arange(6).reshape(2, 3)) - ancillary_variable = AncillaryVariable( - data=np.arange(6).reshape(2, 3), long_name="detection quality" - ) - cube.add_ancillary_variable(ancillary_variable, [0, 1]) - self.assertEqual( - cube.ancillary_variable("detection quality"), ancillary_variable - ) - - def test_add_valid_aux_factory(self): - cube = Cube(np.arange(8).reshape(2, 2, 2)) - delta = AuxCoord(points=[0, 1], long_name="delta", units="m") - sigma = AuxCoord(points=[0, 1], long_name="sigma") - orog = AuxCoord(np.arange(4).reshape(2, 2), units="m") - cube.add_aux_coord(delta, 0) - cube.add_aux_coord(sigma, 0) - cube.add_aux_coord(orog, (1, 2)) - factory = HybridHeightFactory(delta=delta, sigma=sigma, orography=orog) - self.assertIsNone(cube.add_aux_factory(factory)) - - def test_error_for_add_invalid_aux_factory(self): - cube = Cube(np.arange(8).reshape(2, 2, 2), long_name="bar") - delta = AuxCoord(points=[0, 1], long_name="delta", units="m") - sigma = AuxCoord(points=[0, 1], long_name="sigma") - orog = AuxCoord(np.arange(4).reshape(2, 2), units="m", long_name="foo") - cube.add_aux_coord(delta, 0) - cube.add_aux_coord(sigma, 0) - # Note orography is not added to the cube here - factory = HybridHeightFactory(delta=delta, sigma=sigma, orography=orog) - expected_error = ( - "foo coordinate for factory is not present on cube " "bar" - ) - with self.assertRaisesRegex(ValueError, expected_error): - cube.add_aux_factory(factory) - - -class Test_remove_metadata(tests.IrisTest): - def setUp(self): - cube = Cube(np.arange(6).reshape(2, 3)) - x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") - cube.add_dim_coord(x_coord, 1) - z_coord = AuxCoord(points=np.arange(6).reshape(2, 3), long_name="z") - cube.add_aux_coord(z_coord, [0, 1]) - a_cell_measure = CellMeasure( - np.arange(6).reshape(2, 3), long_name="area" - ) - self.b_cell_measure = CellMeasure( - np.arange(6).reshape(2, 3), long_name="other_area" - ) - cube.add_cell_measure(a_cell_measure, [0, 1]) - cube.add_cell_measure(self.b_cell_measure, [0, 1]) - ancillary_variable = AncillaryVariable( - data=np.arange(6).reshape(2, 3), long_name="Quality of Detection" - ) - cube.add_ancillary_variable(ancillary_variable, [0, 1]) - self.cube = cube - - def test_remove_dim_coord(self): - self.cube.remove_coord(self.cube.coord("x")) - self.assertEqual(self.cube.coords("x"), []) - - def test_remove_aux_coord(self): - self.cube.remove_coord(self.cube.coord("z")) - self.assertEqual(self.cube.coords("z"), []) - - def test_remove_cell_measure(self): - self.cube.remove_cell_measure(self.cube.cell_measure("area")) - self.assertEqual( - self.cube._cell_measures_and_dims, [(self.b_cell_measure, (0, 1))] - ) - - def test_remove_cell_measure_by_name(self): - self.cube.remove_cell_measure("area") - self.assertEqual( - self.cube._cell_measures_and_dims, [(self.b_cell_measure, (0, 1))] - ) - - def test_fail_remove_cell_measure_by_name(self): - with self.assertRaises(CellMeasureNotFoundError): - self.cube.remove_cell_measure("notarea") - - def test_remove_ancilliary_variable(self): - self.cube.remove_ancillary_variable( - self.cube.ancillary_variable("Quality of Detection") - ) - self.assertEqual(self.cube._ancillary_variables_and_dims, []) - - def test_remove_ancilliary_variable_by_name(self): - self.cube.remove_ancillary_variable("Quality of Detection") - self.assertEqual(self.cube._ancillary_variables_and_dims, []) - - def test_fail_remove_ancilliary_variable_by_name(self): - with self.assertRaises(AncillaryVariableNotFoundError): - self.cube.remove_ancillary_variable("notname") - - -class TestCoords(tests.IrisTest): - def setUp(self): - cube = Cube(np.arange(6).reshape(2, 3)) - x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") - cube.add_dim_coord(x_coord, 1) - self.x_coord = x_coord - self.cube = cube - - def test_bad_coord(self): - bad_coord = self.x_coord.copy() - bad_coord.attributes = {"bad": "attribute"} - re = ( - "Expected to find exactly 1 coordinate matching the given " - "'x' coordinate's metadata, but found none." - ) - with self.assertRaisesRegex(CoordinateNotFoundError, re): - _ = self.cube.coord(bad_coord) - - -class Test__getitem_CellMeasure(tests.IrisTest): - def setUp(self): - cube = Cube(np.arange(6).reshape(2, 3)) - x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") - cube.add_dim_coord(x_coord, 1) - y_coord = DimCoord(points=np.array([5, 6]), long_name="y") - cube.add_dim_coord(y_coord, 0) - z_coord = AuxCoord(points=np.arange(6).reshape(2, 3), long_name="z") - cube.add_aux_coord(z_coord, [0, 1]) - a_cell_measure = CellMeasure( - np.arange(6).reshape(2, 3), long_name="area" - ) - cube.add_cell_measure(a_cell_measure, [0, 1]) - self.cube = cube - - def test_cell_measure_2d(self): - result = self.cube[0:2, 0:2] - self.assertEqual(len(result.cell_measures()), 1) - self.assertEqual(result.shape, result.cell_measures()[0].data.shape) - - def test_cell_measure_1d(self): - result = self.cube[0, 0:2] - self.assertEqual(len(result.cell_measures()), 1) - self.assertEqual(result.shape, result.cell_measures()[0].data.shape) - - -class Test__getitem_AncillaryVariables(tests.IrisTest): - def setUp(self): - cube = Cube(np.arange(6).reshape(2, 3)) - x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") - cube.add_dim_coord(x_coord, 1) - y_coord = DimCoord(points=np.array([5, 6]), long_name="y") - cube.add_dim_coord(y_coord, 0) - z_coord = AuxCoord(points=np.arange(6).reshape(2, 3), long_name="z") - cube.add_aux_coord(z_coord, [0, 1]) - a_ancillary_variable = AncillaryVariable( - data=np.arange(6).reshape(2, 3), long_name="foo" - ) - cube.add_ancillary_variable(a_ancillary_variable, [0, 1]) - self.cube = cube - - def test_ancillary_variables_2d(self): - result = self.cube[0:2, 0:2] - self.assertEqual(len(result.ancillary_variables()), 1) - self.assertEqual( - result.shape, result.ancillary_variables()[0].data.shape - ) - - def test_ancillary_variables_1d(self): - result = self.cube[0, 0:2] - self.assertEqual(len(result.ancillary_variables()), 1) - self.assertEqual( - result.shape, result.ancillary_variables()[0].data.shape - ) - - -class TestAncillaryVariables(tests.IrisTest): - def setUp(self): - cube = Cube(10 * np.arange(6).reshape(2, 3)) - self.ancill_var = AncillaryVariable( - np.arange(6).reshape(2, 3), - standard_name="number_of_observations", - units="1", - ) - cube.add_ancillary_variable(self.ancill_var, [0, 1]) - self.cube = cube - - def test_get_ancillary_variable(self): - ancill_var = self.cube.ancillary_variable("number_of_observations") - self.assertEqual(ancill_var, self.ancill_var) - - def test_get_ancillary_variables(self): - ancill_vars = self.cube.ancillary_variables("number_of_observations") - self.assertEqual(len(ancill_vars), 1) - self.assertEqual(ancill_vars[0], self.ancill_var) - - def test_get_ancillary_variable_obj(self): - ancill_vars = self.cube.ancillary_variables(self.ancill_var) - self.assertEqual(len(ancill_vars), 1) - self.assertEqual(ancill_vars[0], self.ancill_var) - - def test_fail_get_ancillary_variables(self): - with self.assertRaises(AncillaryVariableNotFoundError): - self.cube.ancillary_variable("other_ancill_var") - - def test_fail_get_ancillary_variables_obj(self): - ancillary_variable = self.ancill_var.copy() - ancillary_variable.long_name = "Number of observations at site" - with self.assertRaises(AncillaryVariableNotFoundError): - self.cube.ancillary_variable(ancillary_variable) - - def test_ancillary_variable_dims(self): - ancill_var_dims = self.cube.ancillary_variable_dims(self.ancill_var) - self.assertEqual(ancill_var_dims, (0, 1)) - - def test_fail_ancill_variable_dims(self): - ancillary_variable = self.ancill_var.copy() - ancillary_variable.long_name = "Number of observations at site" - with self.assertRaises(AncillaryVariableNotFoundError): - self.cube.ancillary_variable_dims(ancillary_variable) - - def test_ancillary_variable_dims_by_name(self): - ancill_var_dims = self.cube.ancillary_variable_dims( - "number_of_observations" - ) - self.assertEqual(ancill_var_dims, (0, 1)) - - def test_fail_ancillary_variable_dims_by_name(self): - with self.assertRaises(AncillaryVariableNotFoundError): - self.cube.ancillary_variable_dims("notname") - - -class TestCellMeasures(tests.IrisTest): - def setUp(self): - cube = Cube(np.arange(6).reshape(2, 3)) - x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") - cube.add_dim_coord(x_coord, 1) - z_coord = AuxCoord(points=np.arange(6).reshape(2, 3), long_name="z") - cube.add_aux_coord(z_coord, [0, 1]) - self.a_cell_measure = CellMeasure( - np.arange(6).reshape(2, 3), long_name="area", units="m2" - ) - cube.add_cell_measure(self.a_cell_measure, [0, 1]) - self.cube = cube - - def test_get_cell_measure(self): - cm = self.cube.cell_measure("area") - self.assertEqual(cm, self.a_cell_measure) - - def test_get_cell_measures(self): - cms = self.cube.cell_measures() - self.assertEqual(len(cms), 1) - self.assertEqual(cms[0], self.a_cell_measure) - - def test_get_cell_measures_obj(self): - cms = self.cube.cell_measures(self.a_cell_measure) - self.assertEqual(len(cms), 1) - self.assertEqual(cms[0], self.a_cell_measure) - - def test_fail_get_cell_measure(self): - with self.assertRaises(CellMeasureNotFoundError): - _ = self.cube.cell_measure("notarea") - - def test_fail_get_cell_measures_obj(self): - a_cell_measure = self.a_cell_measure.copy() - a_cell_measure.units = "km2" - with self.assertRaises(CellMeasureNotFoundError): - _ = self.cube.cell_measure(a_cell_measure) - - def test_cell_measure_dims(self): - cm_dims = self.cube.cell_measure_dims(self.a_cell_measure) - self.assertEqual(cm_dims, (0, 1)) - - def test_fail_cell_measure_dims(self): - a_cell_measure = self.a_cell_measure.copy() - a_cell_measure.units = "km2" - with self.assertRaises(CellMeasureNotFoundError): - _ = self.cube.cell_measure_dims(a_cell_measure) - - def test_cell_measure_dims_by_name(self): - cm_dims = self.cube.cell_measure_dims("area") - self.assertEqual(cm_dims, (0, 1)) - - def test_fail_cell_measure_dims_by_name(self): - with self.assertRaises(CellMeasureNotFoundError): - self.cube.cell_measure_dims("notname") - - -class Test_transpose(tests.IrisTest): - def setUp(self): - self.data = np.arange(24).reshape(3, 2, 4) - self.cube = Cube(self.data) - self.lazy_cube = Cube(as_lazy_data(self.data)) - - def test_lazy_data(self): - cube = self.lazy_cube - cube.transpose() - self.assertTrue(cube.has_lazy_data()) - self.assertArrayEqual(self.data.T, cube.data) - - def test_real_data(self): - self.cube.transpose() - self.assertFalse(self.cube.has_lazy_data()) - self.assertIs(self.data.base, self.cube.data.base) - self.assertArrayEqual(self.data.T, self.cube.data) - - def test_real_data__new_order(self): - new_order = [2, 0, 1] - self.cube.transpose(new_order) - self.assertFalse(self.cube.has_lazy_data()) - self.assertIs(self.data.base, self.cube.data.base) - self.assertArrayEqual(self.data.transpose(new_order), self.cube.data) - - def test_lazy_data__new_order(self): - new_order = [2, 0, 1] - cube = self.lazy_cube - cube.transpose(new_order) - self.assertTrue(cube.has_lazy_data()) - self.assertArrayEqual(self.data.transpose(new_order), cube.data) - - def test_lazy_data__transpose_order_ndarray(self): - # Check that a transpose order supplied as an array does not trip up - # a dask transpose operation. - new_order = np.array([2, 0, 1]) - cube = self.lazy_cube - cube.transpose(new_order) - self.assertTrue(cube.has_lazy_data()) - self.assertArrayEqual(self.data.transpose(new_order), cube.data) - - def test_bad_transpose_order(self): - exp_emsg = "Incorrect number of dimensions" - with self.assertRaisesRegex(ValueError, exp_emsg): - self.cube.transpose([1]) - - def test_dim_coords(self): - x_coord = DimCoord(points=np.array([2, 3, 4]), long_name="x") - self.cube.add_dim_coord(x_coord, 0) - self.cube.transpose() - self.assertEqual(self.cube._dim_coords_and_dims, [(x_coord, 2)]) - - def test_aux_coords(self): - x_coord = AuxCoord( - points=np.array([[2, 3], [8, 4], [7, 9]]), long_name="x" - ) - self.cube.add_aux_coord(x_coord, (0, 1)) - self.cube.transpose() - self.assertEqual(self.cube._aux_coords_and_dims, [(x_coord, (2, 1))]) - - def test_cell_measures(self): - area_cm = CellMeasure( - np.arange(12).reshape(3, 4), long_name="area of cells" - ) - self.cube.add_cell_measure(area_cm, (0, 2)) - self.cube.transpose() - self.assertEqual( - self.cube._cell_measures_and_dims, [(area_cm, (2, 0))] - ) - - def test_ancillary_variables(self): - ancill_var = AncillaryVariable( - data=np.arange(8).reshape(2, 4), long_name="instrument error" - ) - self.cube.add_ancillary_variable(ancill_var, (1, 2)) - self.cube.transpose() - self.assertEqual( - self.cube._ancillary_variables_and_dims, [(ancill_var, (1, 0))] - ) - - -class Test_convert_units(tests.IrisTest): - def test_convert_unknown_units(self): - cube = iris.cube.Cube(1) - emsg = ( - "Cannot convert from unknown units. " - 'The "cube.units" attribute may be set directly.' - ) - with self.assertRaisesRegex(UnitConversionError, emsg): - cube.convert_units("mm day-1") - - def test_preserves_lazy(self): - real_data = np.arange(12.0).reshape((3, 4)) - lazy_data = as_lazy_data(real_data) - cube = iris.cube.Cube(lazy_data, units="m") - real_data_ft = Unit("m").convert(real_data, "ft") - cube.convert_units("ft") - self.assertTrue(cube.has_lazy_data()) - self.assertArrayAllClose(cube.data, real_data_ft) - - -class Test__eq__data(tests.IrisTest): - """Partial cube equality testing, for data type only.""" - - def test_data_float_eq(self): - cube1 = Cube([1.0]) - cube2 = Cube([1.0]) - self.assertTrue(cube1 == cube2) - - def test_data_float_eqtol(self): - val1 = np.array(1.0, dtype=np.float32) - # NOTE: Since v2.3, Iris uses "allclose". Prior to that we used - # "rtol=1e-8", and this example would *fail*. - val2 = np.array(1.0 + 1.0e-6, dtype=np.float32) - cube1 = Cube([val1]) - cube2 = Cube([val2]) - self.assertNotEqual(val1, val2) - self.assertTrue(cube1 == cube2) - - def test_data_float_not_eq(self): - val1 = 1.0 - val2 = 1.0 + 1.0e-4 - cube1 = Cube([1.0, val1]) - cube2 = Cube([1.0, val2]) - self.assertFalse(cube1 == cube2) - - def test_data_int_eq(self): - cube1 = Cube([1, 2, 3]) - cube2 = Cube([1, 2, 3]) - self.assertTrue(cube1 == cube2) - - def test_data_int_not_eq(self): - cube1 = Cube([1, 2, 3]) - cube2 = Cube([1, 2, 0]) - self.assertFalse(cube1 == cube2) - - # NOTE: since numpy v1.18, boolean array subtract is deprecated. - def test_data_bool_eq(self): - cube1 = Cube([True, False]) - cube2 = Cube([True, False]) - self.assertTrue(cube1 == cube2) - - def test_data_bool_not_eq(self): - cube1 = Cube([True, False]) - cube2 = Cube([True, True]) - self.assertFalse(cube1 == cube2) - - -class Test__eq__meta(tests.IrisTest): - def test_ancillary_fail(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - avr = AncillaryVariable([2, 3], long_name="foo") - cube2.add_ancillary_variable(avr, 0) - self.assertFalse(cube1 == cube2) - - def test_ancillary_reorder(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - avr1 = AncillaryVariable([2, 3], long_name="foo") - avr2 = AncillaryVariable([4, 5], long_name="bar") - # Add the same ancillary variables to cube1 and cube2 in - # opposite orders. - cube1.add_ancillary_variable(avr1, 0) - cube1.add_ancillary_variable(avr2, 0) - cube2.add_ancillary_variable(avr2, 0) - cube2.add_ancillary_variable(avr1, 0) - self.assertTrue(cube1 == cube2) - - def test_ancillary_diff_data(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - avr1 = AncillaryVariable([2, 3], long_name="foo") - avr2 = AncillaryVariable([4, 5], long_name="foo") - cube1.add_ancillary_variable(avr1, 0) - cube2.add_ancillary_variable(avr2, 0) - self.assertFalse(cube1 == cube2) - - def test_cell_measure_fail(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - cms = CellMeasure([2, 3], long_name="foo") - cube2.add_cell_measure(cms, 0) - self.assertFalse(cube1 == cube2) - - def test_cell_measure_reorder(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - cms1 = CellMeasure([2, 3], long_name="foo") - cms2 = CellMeasure([4, 5], long_name="bar") - # Add the same cell measure to cube1 and cube2 in - # opposite orders. - cube1.add_cell_measure(cms1, 0) - cube1.add_cell_measure(cms2, 0) - cube2.add_cell_measure(cms2, 0) - cube2.add_cell_measure(cms1, 0) - self.assertTrue(cube1 == cube2) - - def test_cell_measure_diff_data(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - cms1 = CellMeasure([2, 3], long_name="foo") - cms2 = CellMeasure([4, 5], long_name="foo") - cube1.add_cell_measure(cms1, 0) - cube2.add_cell_measure(cms2, 0) - self.assertFalse(cube1 == cube2) - - def test_cell_method_fail(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - cmth = CellMethod("mean", "time", "6hr") - cube2.add_cell_method(cmth) - self.assertFalse(cube1 == cube2) - - # Unlike cell measures, cell methods are order sensitive. - def test_cell_method_reorder_fail(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - cmth1 = CellMethod("mean", "time", "6hr") - cmth2 = CellMethod("mean", "time", "12hr") - # Add the same cell method to cube1 and cube2 in - # opposite orders. - cube1.add_cell_method(cmth1) - cube1.add_cell_method(cmth2) - cube2.add_cell_method(cmth2) - cube2.add_cell_method(cmth1) - self.assertFalse(cube1 == cube2) - - def test_cell_method_correct_order(self): - cube1 = Cube([0, 1]) - cube2 = Cube([0, 1]) - cmth1 = CellMethod("mean", "time", "6hr") - cmth2 = CellMethod("mean", "time", "12hr") - # Add the same cell method to cube1 and cube2 in - # the same order. - cube1.add_cell_method(cmth1) - cube1.add_cell_method(cmth2) - cube2.add_cell_method(cmth1) - cube2.add_cell_method(cmth2) - self.assertTrue(cube1 == cube2) - - -@pytest.fixture -def simplecube(): - return stock.simple_2d_w_cell_measure_ancil_var() - - -class Test__dimensional_metadata: - """ - Tests for the "Cube._dimensional_data" method. - - NOTE: test could all be static methods, but that adds a line to each definition. - """ - - def test_not_found(self, simplecube): - with pytest.raises(KeyError, match="was not found in"): - simplecube._dimensional_metadata("grid_latitude") - - def test_dim_coord_name_found(self, simplecube): - res = simplecube._dimensional_metadata("bar") - assert res == simplecube.coord("bar") - - def test_dim_coord_instance_found(self, simplecube): - res = simplecube._dimensional_metadata(simplecube.coord("bar")) - assert res == simplecube.coord("bar") - - def test_aux_coord_name_found(self, simplecube): - res = simplecube._dimensional_metadata("wibble") - assert res == simplecube.coord("wibble") - - def test_aux_coord_instance_found(self, simplecube): - res = simplecube._dimensional_metadata(simplecube.coord("wibble")) - assert res == simplecube.coord("wibble") - - def test_cell_measure_name_found(self, simplecube): - res = simplecube._dimensional_metadata("cell_area") - assert res == simplecube.cell_measure("cell_area") - - def test_cell_measure_instance_found(self, simplecube): - res = simplecube._dimensional_metadata( - simplecube.cell_measure("cell_area") - ) - assert res == simplecube.cell_measure("cell_area") - - def test_ancillary_var_name_found(self, simplecube): - res = simplecube._dimensional_metadata("quality_flag") - assert res == simplecube.ancillary_variable("quality_flag") - - def test_ancillary_var_instance_found(self, simplecube): - res = simplecube._dimensional_metadata( - simplecube.ancillary_variable("quality_flag") - ) - assert res == simplecube.ancillary_variable("quality_flag") - - def test_two_with_same_name(self, simplecube): - # If a cube has two _DimensionalMetadata objects with the same name, the - # current behaviour results in _dimensional_metadata returning the first - # one it finds. - simplecube.cell_measure("cell_area").rename("wibble") - res = simplecube._dimensional_metadata("wibble") - assert res == simplecube.coord("wibble") - - def test_two_with_same_name_specify_instance(self, simplecube): - # The cube has two _DimensionalMetadata objects with the same name so - # we specify the _DimensionalMetadata instance to ensure it returns the - # correct one. - simplecube.cell_measure("cell_area").rename("wibble") - res = simplecube._dimensional_metadata( - simplecube.cell_measure("wibble") - ) - assert res == simplecube.cell_measure("wibble") - - -class TestReprs: - """ - Confirm that str(cube), repr(cube) and cube.summary() work by creating a fresh - :class:`iris._representation.cube_printout.CubePrinter` object, and using it - in the expected ways. - - Notes - ----- - This only tests code connectivity. The functionality is tested elsewhere, in - `iris.tests.unit._representation.cube_printout.test_CubePrintout`. - """ - - # Note: logically this could be a staticmethod, but that seems to upset Pytest - @pytest.fixture - def patched_cubeprinter(self): - target = "iris._representation.cube_printout.CubePrinter" - instance_mock = mock.MagicMock( - to_string=mock.MagicMock( - return_value="" - ) # NB this must return a string - ) - with mock.patch(target, return_value=instance_mock) as class_mock: - yield class_mock, instance_mock - - @staticmethod - def _check_expected_effects( - simplecube, patched_cubeprinter, oneline, padding - ): - class_mock, instance_mock = patched_cubeprinter - assert class_mock.call_args_list == [ - # "CubePrinter()" was called exactly once, with the cube as arg - mock.call(simplecube) - ] - assert instance_mock.to_string.call_args_list == [ - # "CubePrinter(cube).to_string()" was called exactly once, with these args - mock.call(oneline=oneline, name_padding=padding) - ] - - def test_str_effects(self, simplecube, patched_cubeprinter): - str(simplecube) - self._check_expected_effects( - simplecube, patched_cubeprinter, oneline=False, padding=35 - ) - - def test_repr_effects(self, simplecube, patched_cubeprinter): - repr(simplecube) - self._check_expected_effects( - simplecube, patched_cubeprinter, oneline=True, padding=1 - ) - - def test_summary_effects(self, simplecube, patched_cubeprinter): - simplecube.summary( - shorten=mock.sentinel.oneliner, name_padding=mock.sentinel.padding - ) - self._check_expected_effects( - simplecube, - patched_cubeprinter, - oneline=mock.sentinel.oneliner, - padding=mock.sentinel.padding, - ) - - -class TestHtmlRepr: - """ - Confirm that Cube._repr_html_() creates a fresh - :class:`iris.experimental.representation.CubeRepresentation` object, and uses it - in the expected way. - - Notes - ----- - This only tests code connectivity. The functionality is tested elsewhere, in - `iris.tests.unit.experimental.representation.test_CubeRepresentation`. - """ - - # Note: logically this could be a staticmethod, but that seems to upset Pytest - @pytest.fixture - def patched_cubehtml(self): - target = "iris.experimental.representation.CubeRepresentation" - instance_mock = mock.MagicMock( - repr_html=mock.MagicMock( - return_value="" - ) # NB this must return a string - ) - with mock.patch(target, return_value=instance_mock) as class_mock: - yield class_mock, instance_mock - - @staticmethod - def test__repr_html__effects(simplecube, patched_cubehtml): - simplecube._repr_html_() - - class_mock, instance_mock = patched_cubehtml - assert class_mock.call_args_list == [ - # "CubeRepresentation()" was called exactly once, with the cube as arg - mock.call(simplecube) - ] - assert instance_mock.repr_html.call_args_list == [ - # "CubeRepresentation(cube).repr_html()" was called exactly once, with no args - mock.call() - ] - - -class Test__cell_methods: - @pytest.fixture(autouse=True) - def cell_measures_testdata(self): - self.cube = Cube([0]) - self.cm = CellMethod("mean", "time", "6hr") - self.cm2 = CellMethod("max", "latitude", "4hr") - - def test_none(self): - assert self.cube.cell_methods == () - - def test_one(self): - cube = Cube([0], cell_methods=[self.cm]) - expected = (self.cm,) - assert expected == cube.cell_methods - - def test_empty_assigns(self): - testargs = [(), [], {}, 0, 0.0, False, None] - results = [] - for arg in testargs: - cube = self.cube.copy() - cube.cell_methods = arg # assign test object - results.append(cube.cell_methods) # capture what is read back - expected_results = [()] * len(testargs) - assert expected_results == results - - def test_single_assigns(self): - cms = (self.cm, self.cm2) - # Any type of iterable ought to work - # But N.B. *not* testing sets, as order is not stable - testargs = [cms, list(cms), {cm: 1 for cm in cms}] - results = [] - for arg in testargs: - cube = self.cube.copy() - cube.cell_methods = arg # assign test object - results.append(cube.cell_methods) # capture what is read back - expected_results = [cms] * len(testargs) - assert expected_results == results - - def test_fail_assign_noniterable(self): - test_object = object() - with pytest.raises(TypeError, match="not iterable"): - self.cube.cell_methods = test_object - - def test_fail_create_noniterable(self): - test_object = object() - with pytest.raises(TypeError, match="not iterable"): - Cube([0], cell_methods=test_object) - - def test_fail_assign_noncellmethod(self): - test_object = object() - with pytest.raises(ValueError, match="not an iris.coords.CellMethod"): - self.cube.cell_methods = (test_object,) - - def test_fail_create_noncellmethod(self): - test_object = object() - with pytest.raises(ValueError, match="not an iris.coords.CellMethod"): - Cube([0], cell_methods=[test_object]) - - def test_assign_derivedcellmethod(self): - class DerivedCellMethod(CellMethod): - pass - - test_object = DerivedCellMethod("mean", "time", "6hr") - cms = (test_object,) - self.cube.cell_methods = (test_object,) - assert cms == self.cube.cell_methods - - def test_fail_assign_duckcellmethod(self): - # Can't currently assign a "duck-typed" CellMethod replacement, since - # implementation requires class membership (boo!) - DuckCellMethod = namedtuple("DuckCellMethod", CellMethod._names) - test_object = DuckCellMethod( - *CellMethod._names - ) # fill props with value==name - with pytest.raises(ValueError, match="not an iris.coords.CellMethod"): - self.cube.cell_methods = (test_object,) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/cube/test_CubeList.py b/lib/iris/tests/unit/cube/test_CubeList.py deleted file mode 100644 index 86457d3888..0000000000 --- a/lib/iris/tests/unit/cube/test_CubeList.py +++ /dev/null @@ -1,770 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.cube.CubeList` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import collections -import copy -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris import Constraint -import iris.coord_systems -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube, CubeList -import iris.exceptions -from iris.fileformats.pp import STASH -import iris.tests.stock - -NOT_CUBE_MSG = "cannot be put in a cubelist, as it is not a Cube." -NON_ITERABLE_MSG = "object is not iterable" - - -class Test_append(tests.IrisTest): - def setUp(self): - self.cubelist = iris.cube.CubeList() - self.cube1 = iris.cube.Cube(1, long_name="foo") - self.cube2 = iris.cube.Cube(1, long_name="bar") - - def test_pass(self): - self.cubelist.append(self.cube1) - self.assertEqual(self.cubelist[-1], self.cube1) - self.cubelist.append(self.cube2) - self.assertEqual(self.cubelist[-1], self.cube2) - - def test_fail(self): - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): - self.cubelist.append(None) - - -class Test_concatenate_cube(tests.IrisTest): - def setUp(self): - self.units = Unit( - "days since 1970-01-01 00:00:00", calendar="standard" - ) - self.cube1 = Cube([1, 2, 3], "air_temperature", units="K") - self.cube1.add_dim_coord( - DimCoord([0, 1, 2], "time", units=self.units), 0 - ) - - def test_pass(self): - self.cube2 = Cube([1, 2, 3], "air_temperature", units="K") - self.cube2.add_dim_coord( - DimCoord([3, 4, 5], "time", units=self.units), 0 - ) - result = CubeList([self.cube1, self.cube2]).concatenate_cube() - self.assertIsInstance(result, Cube) - - def test_fail(self): - units = Unit("days since 1970-01-02 00:00:00", calendar="standard") - cube2 = Cube([1, 2, 3], "air_temperature", units="K") - cube2.add_dim_coord(DimCoord([0, 1, 2], "time", units=units), 0) - with self.assertRaises(iris.exceptions.ConcatenateError): - CubeList([self.cube1, cube2]).concatenate_cube() - - def test_names_differ_fail(self): - self.cube2 = Cube([1, 2, 3], "air_temperature", units="K") - self.cube2.add_dim_coord( - DimCoord([3, 4, 5], "time", units=self.units), 0 - ) - self.cube3 = Cube([1, 2, 3], "air_pressure", units="Pa") - self.cube3.add_dim_coord( - DimCoord([3, 4, 5], "time", units=self.units), 0 - ) - exc_regexp = "Cube names differ: air_temperature != air_pressure" - with self.assertRaisesRegex( - iris.exceptions.ConcatenateError, exc_regexp - ): - CubeList([self.cube1, self.cube2, self.cube3]).concatenate_cube() - - def test_empty(self): - exc_regexp = "can't concatenate an empty CubeList" - with self.assertRaisesRegex(ValueError, exc_regexp): - CubeList([]).concatenate_cube() - - -class Test_extend(tests.IrisTest): - def setUp(self): - self.cube1 = iris.cube.Cube(1, long_name="foo") - self.cube2 = iris.cube.Cube(1, long_name="bar") - self.cubelist1 = iris.cube.CubeList([self.cube1]) - self.cubelist2 = iris.cube.CubeList([self.cube2]) - - def test_pass(self): - cubelist = copy.copy(self.cubelist1) - cubelist.extend(self.cubelist2) - self.assertEqual(cubelist, self.cubelist1 + self.cubelist2) - cubelist.extend([self.cube2]) - self.assertEqual(cubelist[-1], self.cube2) - - def test_fail(self): - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): - self.cubelist1.extend(self.cube1) - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): - self.cubelist1.extend(None) - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): - self.cubelist1.extend(range(3)) - - -class Test_extract_overlapping(tests.IrisTest): - def setUp(self): - shape = (6, 14, 19) - n_time, n_lat, n_lon = shape - n_data = n_time * n_lat * n_lon - cube = Cube(np.arange(n_data, dtype=np.int32).reshape(shape)) - coord = iris.coords.DimCoord( - points=np.arange(n_time), - standard_name="time", - units="hours since epoch", - ) - cube.add_dim_coord(coord, 0) - cs = iris.coord_systems.GeogCS(6371229) - coord = iris.coords.DimCoord( - points=np.linspace(-90, 90, n_lat), - standard_name="latitude", - units="degrees", - coord_system=cs, - ) - cube.add_dim_coord(coord, 1) - coord = iris.coords.DimCoord( - points=np.linspace(-180, 180, n_lon), - standard_name="longitude", - units="degrees", - coord_system=cs, - ) - cube.add_dim_coord(coord, 2) - self.cube = cube - - def test_extract_one_str_dim(self): - cubes = iris.cube.CubeList([self.cube[2:], self.cube[:4]]) - a, b = cubes.extract_overlapping("time") - self.assertEqual(a.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) - - def test_extract_one_list_dim(self): - cubes = iris.cube.CubeList([self.cube[2:], self.cube[:4]]) - a, b = cubes.extract_overlapping(["time"]) - self.assertEqual(a.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) - - def test_extract_two_dims(self): - cubes = iris.cube.CubeList([self.cube[2:, 5:], self.cube[:4, :10]]) - a, b = cubes.extract_overlapping(["time", "latitude"]) - self.assertEqual(a.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual( - a.coord("latitude"), self.cube.coord("latitude")[5:10] - ) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) - self.assertEqual( - b.coord("latitude"), self.cube.coord("latitude")[5:10] - ) - - def test_different_orders(self): - cubes = iris.cube.CubeList([self.cube[::-1][:4], self.cube[:4]]) - a, b = cubes.extract_overlapping("time") - self.assertEqual(a.coord("time"), self.cube[::-1].coord("time")[2:4]) - self.assertEqual(b.coord("time"), self.cube.coord("time")[2:4]) - - -class Test_iadd(tests.IrisTest): - def setUp(self): - self.cube1 = iris.cube.Cube(1, long_name="foo") - self.cube2 = iris.cube.Cube(1, long_name="bar") - self.cubelist1 = iris.cube.CubeList([self.cube1]) - self.cubelist2 = iris.cube.CubeList([self.cube2]) - - def test_pass(self): - cubelist = copy.copy(self.cubelist1) - cubelist += self.cubelist2 - self.assertEqual(cubelist, self.cubelist1 + self.cubelist2) - cubelist += [self.cube2] - self.assertEqual(cubelist[-1], self.cube2) - - def test_fail(self): - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): - self.cubelist1 += self.cube1 - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): - self.cubelist1 += 1.0 - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): - self.cubelist1 += range(3) - - -class Test_insert(tests.IrisTest): - def setUp(self): - self.cube1 = iris.cube.Cube(1, long_name="foo") - self.cube2 = iris.cube.Cube(1, long_name="bar") - self.cubelist = iris.cube.CubeList([self.cube1] * 3) - - def test_pass(self): - self.cubelist.insert(1, self.cube2) - self.assertEqual(self.cubelist[1], self.cube2) - - def test_fail(self): - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): - self.cubelist.insert(0, None) - - -class Test_merge_cube(tests.IrisTest): - def setUp(self): - self.cube1 = Cube([1, 2, 3], "air_temperature", units="K") - self.cube1.add_aux_coord(AuxCoord([0], "height", units="m")) - - def test_pass(self): - cube2 = self.cube1.copy() - cube2.coord("height").points = [1] - result = CubeList([self.cube1, cube2]).merge_cube() - self.assertIsInstance(result, Cube) - - def test_fail(self): - cube2 = self.cube1.copy() - cube2.rename("not air temperature") - with self.assertRaises(iris.exceptions.MergeError): - CubeList([self.cube1, cube2]).merge_cube() - - def test_empty(self): - with self.assertRaises(ValueError): - CubeList([]).merge_cube() - - def test_single_cube(self): - result = CubeList([self.cube1]).merge_cube() - self.assertEqual(result, self.cube1) - self.assertIsNot(result, self.cube1) - - def test_repeated_cube(self): - with self.assertRaises(iris.exceptions.MergeError): - CubeList([self.cube1, self.cube1]).merge_cube() - - -class Test_merge__time_triple(tests.IrisTest): - @staticmethod - def _make_cube(fp, rt, t, realization=None): - cube = Cube(np.arange(20).reshape(4, 5)) - cube.add_dim_coord(DimCoord(np.arange(5), long_name="x", units="1"), 1) - cube.add_dim_coord(DimCoord(np.arange(4), long_name="y", units="1"), 0) - cube.add_aux_coord( - DimCoord(fp, standard_name="forecast_period", units="1") - ) - cube.add_aux_coord( - DimCoord(rt, standard_name="forecast_reference_time", units="1") - ) - cube.add_aux_coord(DimCoord(t, standard_name="time", units="1")) - if realization is not None: - cube.add_aux_coord( - DimCoord(realization, standard_name="realization", units="1") - ) - return cube - - def test_orthogonal_with_realization(self): - # => fp: 2; rt: 2; t: 2; realization: 2 - triples = ( - (0, 10, 1), - (0, 10, 2), - (0, 11, 1), - (0, 11, 2), - (1, 10, 1), - (1, 10, 2), - (1, 11, 1), - (1, 11, 2), - ) - en1_cubes = [ - self._make_cube(*triple, realization=1) for triple in triples - ] - en2_cubes = [ - self._make_cube(*triple, realization=2) for triple in triples - ] - cubes = CubeList(en1_cubes) + CubeList(en2_cubes) - (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) - - def test_combination_with_realization(self): - # => fp, rt, t: 8; realization: 2 - triples = ( - (0, 10, 1), - (0, 10, 2), - (0, 11, 1), - (0, 11, 3), # This '3' breaks the pattern. - (1, 10, 1), - (1, 10, 2), - (1, 11, 1), - (1, 11, 2), - ) - en1_cubes = [ - self._make_cube(*triple, realization=1) for triple in triples - ] - en2_cubes = [ - self._make_cube(*triple, realization=2) for triple in triples - ] - cubes = CubeList(en1_cubes) + CubeList(en2_cubes) - (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) - - def test_combination_with_extra_realization(self): - # => fp, rt, t, realization: 17 - triples = ( - (0, 10, 1), - (0, 10, 2), - (0, 11, 1), - (0, 11, 2), - (1, 10, 1), - (1, 10, 2), - (1, 11, 1), - (1, 11, 2), - ) - en1_cubes = [ - self._make_cube(*triple, realization=1) for triple in triples - ] - en2_cubes = [ - self._make_cube(*triple, realization=2) for triple in triples - ] - # Add extra that is a duplicate of one of the time triples - # but with a different realisation. - en3_cubes = [self._make_cube(0, 10, 2, realization=3)] - cubes = CubeList(en1_cubes) + CubeList(en2_cubes) + CubeList(en3_cubes) - (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) - - def test_combination_with_extra_triple(self): - # => fp, rt, t, realization: 17 - triples = ( - (0, 10, 1), - (0, 10, 2), - (0, 11, 1), - (0, 11, 2), - (1, 10, 1), - (1, 10, 2), - (1, 11, 1), - (1, 11, 2), - ) - en1_cubes = [ - self._make_cube(*triple, realization=1) for triple in triples - ] - # Add extra time triple on the end. - en2_cubes = [ - self._make_cube(*triple, realization=2) - for triple in triples + ((1, 11, 3),) - ] - cubes = CubeList(en1_cubes) + CubeList(en2_cubes) - (cube,) = cubes.merge() - self.assertCML(cube, checksum=False) - - -class Test_setitem(tests.IrisTest): - def setUp(self): - self.cube1 = iris.cube.Cube(1, long_name="foo") - self.cube2 = iris.cube.Cube(1, long_name="bar") - self.cube3 = iris.cube.Cube(1, long_name="boo") - self.cubelist = iris.cube.CubeList([self.cube1] * 3) - - def test_pass(self): - self.cubelist[1] = self.cube2 - self.assertEqual(self.cubelist[1], self.cube2) - self.cubelist[:2] = (self.cube2, self.cube3) - self.assertEqual( - self.cubelist, - iris.cube.CubeList([self.cube2, self.cube3, self.cube1]), - ) - - def test_fail(self): - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): - self.cubelist[0] = None - with self.assertRaisesRegex(ValueError, NOT_CUBE_MSG): - self.cubelist[0:2] = [self.cube3, None] - - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): - self.cubelist[:1] = 2.5 - with self.assertRaisesRegex(TypeError, NON_ITERABLE_MSG): - self.cubelist[:1] = self.cube1 - - -class Test_xml(tests.IrisTest): - def setUp(self): - self.cubes = CubeList([Cube(np.arange(3)), Cube(np.arange(3))]) - - def test_byteorder_default(self): - self.assertIn("byteorder", self.cubes.xml()) - - def test_byteorder_false(self): - self.assertNotIn("byteorder", self.cubes.xml(byteorder=False)) - - def test_byteorder_true(self): - self.assertIn("byteorder", self.cubes.xml(byteorder=True)) - - -class Test_extract(tests.IrisTest): - def setUp(self): - self.scalar_cubes = CubeList() - for i in range(5): - for letter in "abcd": - self.scalar_cubes.append(Cube(i, long_name=letter)) - - def test_scalar_cube_name_constraint(self): - # Test the name based extraction of a CubeList containing scalar cubes. - res = self.scalar_cubes.extract("a") - expected = CubeList([Cube(i, long_name="a") for i in range(5)]) - self.assertEqual(res, expected) - - def test_scalar_cube_data_constraint(self): - # Test the extraction of a CubeList containing scalar cubes - # when using a cube_func. - val = 2 - constraint = iris.Constraint(cube_func=lambda c: c.data == val) - res = self.scalar_cubes.extract(constraint) - expected = CubeList([Cube(val, long_name=letter) for letter in "abcd"]) - self.assertEqual(res, expected) - - -class ExtractMixin: - # Choose "which" extract method to test. - # Effectively "abstract" -- inheritor must define this property : - # method_name = 'extract_cube' / 'extract_cubes' - - def setUp(self): - self.cube_x = Cube(0, long_name="x") - self.cube_y = Cube(0, long_name="y") - self.cons_x = Constraint("x") - self.cons_y = Constraint("y") - self.cons_any = Constraint(cube_func=lambda cube: True) - self.cons_none = Constraint(cube_func=lambda cube: False) - - def check_extract(self, cubes, constraints, expected): - # Check that extracting a cubelist with the given arguments has the - # expected result. - # 'expected' and the operation results can be: - # * None - # * a single cube - # * a list of cubes --> cubelist (with cubes matching) - # * string --> a ConstraintMatchException matching the string - cubelist = CubeList(cubes) - method = getattr(cubelist, self.method_name) - if isinstance(expected, str): - with self.assertRaisesRegex( - iris.exceptions.ConstraintMismatchError, expected - ): - method(constraints) - else: - result = method(constraints) - if expected is None: - self.assertIsNone(result) - elif isinstance(expected, Cube): - self.assertIsInstance(result, Cube) - self.assertEqual(result, expected) - elif isinstance(expected, list): - self.assertIsInstance(result, CubeList) - self.assertEqual(result, expected) - else: - msg = ( - 'Unhandled usage in "check_extract" call: ' - '"expected" arg has type {}, value {}.' - ) - raise ValueError(msg.format(type(expected), expected)) - - -class Test_extract_cube(ExtractMixin, tests.IrisTest): - method_name = "extract_cube" - - def test_empty(self): - self.check_extract([], self.cons_x, "Got 0 cubes .* expecting 1") - - def test_single_cube_ok(self): - self.check_extract([self.cube_x], self.cons_x, self.cube_x) - - def test_single_cube_fail__too_few(self): - self.check_extract( - [self.cube_x], self.cons_y, "Got 0 cubes .* expecting 1" - ) - - def test_single_cube_fail__too_many(self): - self.check_extract( - [self.cube_x, self.cube_y], - self.cons_any, - "Got 2 cubes .* expecting 1", - ) - - def test_string_as_constraint(self): - # Check that we can use a string, that converts to a constraint - # ( via "as_constraint" ). - self.check_extract([self.cube_x], "x", self.cube_x) - - def test_none_as_constraint(self): - # Check that we can use a None, that converts to a constraint - # ( via "as_constraint" ). - self.check_extract([self.cube_x], None, self.cube_x) - - def test_constraint_in_list__fail(self): - # Check that we *cannot* use [constraint] - msg = "cannot be cast to a constraint" - with self.assertRaisesRegex(TypeError, msg): - self.check_extract([], [self.cons_x], []) - - def test_multi_cube_ok(self): - self.check_extract( - [self.cube_x, self.cube_y], self.cons_x, self.cube_x - ) # NOTE: returns a cube - - def test_multi_cube_fail__too_few(self): - self.check_extract( - [self.cube_x, self.cube_y], - self.cons_none, - "Got 0 cubes .* expecting 1", - ) - - def test_multi_cube_fail__too_many(self): - self.check_extract( - [self.cube_x, self.cube_y], - self.cons_any, - "Got 2 cubes .* expecting 1", - ) - - -class ExtractCubesMixin(ExtractMixin): - method_name = "extract_cubes" - - -class Test_extract_cubes__noconstraint(ExtractCubesMixin, tests.IrisTest): - """Test with an empty list of constraints.""" - - def test_empty(self): - self.check_extract([], [], []) - - def test_single_cube(self): - self.check_extract([self.cube_x], [], []) - - def test_multi_cubes(self): - self.check_extract([self.cube_x, self.cube_y], [], []) - - -class ExtractCubesSingleConstraintMixin(ExtractCubesMixin): - """ - Common code for testing extract_cubes with a single constraint. - Generalised, so that we can do the same tests for a "bare" constraint, - and a list containing a single [constraint]. - - """ - - # Effectively "abstract" -- inheritor must define this property : - # wrap_test_constraint_as_list_of_one = True / False - - def check_extract(self, cubes, constraint, result): - # Overload standard test operation. - if self.wrap_test_constraint_as_list_of_one: - constraint = [constraint] - super().check_extract(cubes, constraint, result) - - def test_empty(self): - self.check_extract([], self.cons_x, "Got 0 cubes .* expecting 1") - - def test_single_cube_ok(self): - self.check_extract( - [self.cube_x], self.cons_x, [self.cube_x] - ) # NOTE: always returns list NOT cube - - def test_single_cube__fail_mismatch(self): - self.check_extract( - [self.cube_x], self.cons_y, "Got 0 cubes .* expecting 1" - ) - - def test_multi_cube_ok(self): - self.check_extract( - [self.cube_x, self.cube_y], self.cons_x, [self.cube_x] - ) # NOTE: always returns list NOT cube - - def test_multi_cube__fail_too_few(self): - self.check_extract( - [self.cube_x, self.cube_y], - self.cons_none, - "Got 0 cubes .* expecting 1", - ) - - def test_multi_cube__fail_too_many(self): - self.check_extract( - [self.cube_x, self.cube_y], - self.cons_any, - "Got 2 cubes .* expecting 1", - ) - - -class Test_extract_cubes__bare_single_constraint( - ExtractCubesSingleConstraintMixin, tests.IrisTest -): - """Testing with a single constraint as the argument.""" - - wrap_test_constraint_as_list_of_one = False - - -class Test_extract_cubes__list_single_constraint( - ExtractCubesSingleConstraintMixin, tests.IrisTest -): - """Testing with a list of one constraint as the argument.""" - - wrap_test_constraint_as_list_of_one = True - - -class Test_extract_cubes__multi_constraints(ExtractCubesMixin, tests.IrisTest): - """ - Testing when the 'constraints' arg is a list of multiple constraints. - """ - - def test_empty(self): - # Always fails. - self.check_extract( - [], [self.cons_x, self.cons_any], "Got 0 cubes .* expecting 1" - ) - - def test_single_cube_ok(self): - # Possible if the one cube matches all the constraints. - self.check_extract( - [self.cube_x], - [self.cons_x, self.cons_any], - [self.cube_x, self.cube_x], - ) - - def test_single_cube__fail_too_few(self): - self.check_extract( - [self.cube_x], - [self.cons_x, self.cons_y], - "Got 0 cubes .* expecting 1", - ) - - def test_multi_cube_ok(self): - self.check_extract( - [self.cube_x, self.cube_y], - [self.cons_y, self.cons_x], # N.B. reverse order ! - [self.cube_y, self.cube_x], - ) - - def test_multi_cube_castable_constraint_args(self): - # Check with args that *aren't* constraints, but can be converted - # ( via "as_constraint" ). - self.check_extract( - [self.cube_x, self.cube_y], - ["y", "x", self.cons_y], - [self.cube_y, self.cube_x, self.cube_y], - ) - - # NOTE: not bothering to check we can cast a 'None', as it will anyway - # fail with multiple input cubes. - - def test_multi_cube__fail_too_few(self): - self.check_extract( - [self.cube_x, self.cube_y], - [self.cons_x, self.cons_y, self.cons_none], - "Got 0 cubes .* expecting 1", - ) - - def test_multi_cube__fail_too_many(self): - self.check_extract( - [self.cube_x, self.cube_y], - [self.cons_x, self.cons_y, self.cons_any], - "Got 2 cubes .* expecting 1", - ) - - -class Test_iteration(tests.IrisTest): - def setUp(self): - self.scalar_cubes = CubeList() - for i in range(5): - for letter in "abcd": - self.scalar_cubes.append(Cube(i, long_name=letter)) - - def test_iterable(self): - self.assertIsInstance(self.scalar_cubes, collections.abc.Iterable) - - def test_iteration(self): - letters = "abcd" * 5 - for i, cube in enumerate(self.scalar_cubes): - self.assertEqual(cube.long_name, letters[i]) - - -class TestPrint(tests.IrisTest): - def setUp(self): - self.cubes = CubeList([iris.tests.stock.lat_lon_cube()]) - - def test_summary(self): - expected = ( - "0: unknown / (unknown) " - " (latitude: 3; longitude: 4)" - ) - self.assertEqual(str(self.cubes), expected) - - def test_summary_name_unit(self): - self.cubes[0].long_name = "aname" - self.cubes[0].units = "1" - expected = ( - "0: aname / (1) " - " (latitude: 3; longitude: 4)" - ) - self.assertEqual(str(self.cubes), expected) - - def test_summary_stash(self): - self.cubes[0].attributes["STASH"] = STASH.from_msi("m01s00i004") - expected = ( - "0: m01s00i004 / (unknown) " - " (latitude: 3; longitude: 4)" - ) - self.assertEqual(str(self.cubes), expected) - - -class TestRealiseData(tests.IrisTest): - def test_realise_data(self): - # Simply check that calling CubeList.realise_data is calling - # _lazy_data.co_realise_cubes. - mock_cubes_list = [mock.Mock(ident=count) for count in range(3)] - test_cubelist = CubeList(mock_cubes_list) - call_patch = self.patch("iris._lazy_data.co_realise_cubes") - test_cubelist.realise_data() - # Check it was called once, passing cubes as *args. - self.assertEqual( - call_patch.call_args_list, [mock.call(*mock_cubes_list)] - ) - - -class Test_CubeList_copy(tests.IrisTest): - def setUp(self): - self.cube_list = iris.cube.CubeList() - self.copied_cube_list = self.cube_list.copy() - - def test_copy(self): - self.assertIsInstance(self.copied_cube_list, iris.cube.CubeList) - - -class TestHtmlRepr: - """ - Confirm that Cubelist._repr_html_() creates a fresh - :class:`iris.experimental.representation.CubeListRepresentation` object, and uses - it in the expected way. - - Notes - ----- - This only tests code connectivity. The functionality is tested elsewhere, at - `iris.tests.unit.experimental.representation.test_CubeListRepresentation` - """ - - @staticmethod - def test__repr_html_(): - test_cubelist = CubeList([]) - - target = "iris.experimental.representation.CubeListRepresentation" - with mock.patch(target) as class_mock: - # Exercise the function-under-test. - test_cubelist._repr_html_() - - assert class_mock.call_args_list == [ - # "CubeListRepresentation()" was called exactly once, with the cubelist as arg - mock.call(test_cubelist) - ] - assert class_mock.return_value.repr_html.call_args_list == [ - # "CubeListRepresentation(cubelist).repr_html()" was called exactly once, with no args - mock.call() - ] - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py b/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py deleted file mode 100644 index 9e60631c33..0000000000 --- a/lib/iris/tests/unit/cube/test_Cube__aggregated_by.py +++ /dev/null @@ -1,893 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.cube.Cube` class aggregated_by method.""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import Unit -import numpy as np - -from iris._lazy_data import as_lazy_data -import iris.analysis -from iris.analysis import MEAN, SUM, Aggregator, WeightedAggregator -import iris.aux_factory -import iris.coords -from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord -from iris.cube import Cube -import iris.exceptions -from iris.tests.stock import realistic_4d - - -class Test_aggregated_by(tests.IrisTest): - def setUp(self): - self.cube = Cube(np.arange(44).reshape(4, 11)) - - val_coord = AuxCoord( - [0, 0, 0, 1, 1, 2, 0, 0, 2, 0, 1], long_name="val" - ) - label_coord = AuxCoord( - [ - "alpha", - "alpha", - "beta", - "beta", - "alpha", - "gamma", - "alpha", - "alpha", - "alpha", - "gamma", - "beta", - ], - long_name="label", - units="no_unit", - ) - simple_agg_coord = AuxCoord([1, 1, 2, 2], long_name="simple_agg") - spanning_coord = AuxCoord( - np.arange(44).reshape(4, 11), long_name="spanning" - ) - spanning_label_coord = AuxCoord( - np.arange(1, 441, 10).reshape(4, 11).astype(str), - long_name="span_label", - units="no_unit", - ) - - self.cube.add_aux_coord(simple_agg_coord, 0) - self.cube.add_aux_coord(val_coord, 1) - self.cube.add_aux_coord(label_coord, 1) - self.cube.add_aux_coord(spanning_coord, (0, 1)) - self.cube.add_aux_coord(spanning_label_coord, (0, 1)) - - self.mock_agg = mock.Mock(spec=Aggregator) - self.mock_agg.cell_method = [] - self.mock_agg.aggregate = mock.Mock( - return_value=mock.Mock(dtype="object") - ) - self.mock_agg.aggregate_shape = mock.Mock(return_value=()) - self.mock_agg.lazy_func = None - self.mock_agg.post_process = mock.Mock(side_effect=lambda x, y, z: x) - - self.mock_weighted_agg = mock.Mock(spec=WeightedAggregator) - self.mock_weighted_agg.cell_method = [] - - def mock_weighted_aggregate(*_, **kwargs): - if kwargs.get("returned", False): - return (mock.Mock(dtype="object"), mock.Mock(dtype="object")) - return mock.Mock(dtype="object") - - self.mock_weighted_agg.aggregate = mock.Mock( - side_effect=mock_weighted_aggregate - ) - self.mock_weighted_agg.aggregate_shape = mock.Mock(return_value=()) - self.mock_weighted_agg.lazy_func = None - self.mock_weighted_agg.post_process = mock.Mock( - side_effect=lambda x, y, z, **kwargs: y - ) - - self.ancillary_variable = AncillaryVariable( - [0, 1, 2, 3], long_name="foo" - ) - self.cube.add_ancillary_variable(self.ancillary_variable, 0) - self.cell_measure = CellMeasure([0, 1, 2, 3], long_name="bar") - self.cube.add_cell_measure(self.cell_measure, 0) - - self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) - self.val_weights = np.ones_like(self.cube.data, dtype=np.float32) - - def test_2d_coord_simple_agg(self): - # For 2d coords, slices of aggregated coord should be the same as - # aggregated slices. - res_cube = self.cube.aggregated_by("simple_agg", self.mock_agg) - for res_slice, cube_slice in zip( - res_cube.slices("simple_agg"), self.cube.slices("simple_agg") - ): - cube_slice_agg = cube_slice.aggregated_by( - "simple_agg", self.mock_agg - ) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) - self.assertEqual( - res_slice.coord("span_label"), - cube_slice_agg.coord("span_label"), - ) - - def test_agg_by_label(self): - # Aggregate a cube on a string coordinate label where label - # and val entries are not in step; the resulting cube has a val - # coord of bounded cells and a label coord of single string entries. - res_cube = self.cube.aggregated_by("label", self.mock_agg) - val_coord = AuxCoord( - np.array([1.0, 0.5, 1.0]), - bounds=np.array([[0, 2], [0, 1], [0, 2]]), - long_name="val", - ) - label_coord = AuxCoord( - np.array(["alpha", "beta", "gamma"]), - long_name="label", - units="no_unit", - ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - - def test_agg_by_label_bounded(self): - # Aggregate a cube on a string coordinate label where label - # and val entries are not in step; the resulting cube has a val - # coord of bounded cells and a label coord of single string entries. - val_points = self.cube.coord("val").points - self.cube.coord("val").bounds = np.array( - [val_points - 0.5, val_points + 0.5] - ).T - res_cube = self.cube.aggregated_by("label", self.mock_agg) - val_coord = AuxCoord( - np.array([1.0, 0.5, 1.0]), - bounds=np.array([[-0.5, 2.5], [-0.5, 1.5], [-0.5, 2.5]]), - long_name="val", - ) - label_coord = AuxCoord( - np.array(["alpha", "beta", "gamma"]), - long_name="label", - units="no_unit", - ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - - def test_2d_agg_by_label(self): - res_cube = self.cube.aggregated_by("label", self.mock_agg) - # For 2d coord, slices of aggregated coord should be the same as - # aggregated slices. - for res_slice, cube_slice in zip( - res_cube.slices("val"), self.cube.slices("val") - ): - cube_slice_agg = cube_slice.aggregated_by("label", self.mock_agg) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) - - def test_agg_by_val(self): - # Aggregate a cube on a numeric coordinate val where label - # and val entries are not in step; the resulting cube has a label - # coord with serialised labels from the aggregated cells. - res_cube = self.cube.aggregated_by("val", self.mock_agg) - val_coord = AuxCoord(np.array([0, 1, 2]), long_name="val") - exp0 = "alpha|alpha|beta|alpha|alpha|gamma" - exp1 = "beta|alpha|beta" - exp2 = "gamma|alpha" - label_coord = AuxCoord( - np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" - ) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - - def test_2d_agg_by_val(self): - res_cube = self.cube.aggregated_by("val", self.mock_agg) - # For 2d coord, slices of aggregated coord should be the same as - # aggregated slices. - for res_slice, cube_slice in zip( - res_cube.slices("val"), self.cube.slices("val") - ): - cube_slice_agg = cube_slice.aggregated_by("val", self.mock_agg) - self.assertEqual( - res_slice.coord("spanning"), cube_slice_agg.coord("spanning") - ) - - def test_single_string_aggregation(self): - aux_coords = [ - (AuxCoord(["a", "b", "a"], long_name="foo"), 0), - (AuxCoord(["a", "a", "a"], long_name="bar"), 0), - ] - cube = iris.cube.Cube( - np.arange(12).reshape(3, 4), aux_coords_and_dims=aux_coords - ) - result = cube.aggregated_by("foo", MEAN) - self.assertEqual(result.shape, (2, 4)) - self.assertEqual( - result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar") - ) - - def test_ancillary_variables_and_cell_measures_kept(self): - cube_agg = self.cube.aggregated_by("val", self.mock_agg) - self.assertEqual( - cube_agg.ancillary_variables(), [self.ancillary_variable] - ) - self.assertEqual(cube_agg.cell_measures(), [self.cell_measure]) - - def test_ancillary_variables_and_cell_measures_removed(self): - cube_agg = self.cube.aggregated_by("simple_agg", self.mock_agg) - self.assertEqual(cube_agg.ancillary_variables(), []) - self.assertEqual(cube_agg.cell_measures(), []) - - def test_1d_weights(self): - self.cube.aggregated_by( - "simple_agg", self.mock_weighted_agg, weights=self.simple_weights - ) - - self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 2) - - # A simple mock.assert_called_with does not work due to ValueError: The - # truth value of an array with more than one element is ambiguous. Use - # a.any() or a.all() - call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] - np.testing.assert_array_equal( - call_1.args[0], - np.array( - [ - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21], - ] - ), - ) - self.assertEqual(call_1.kwargs["axis"], 0) - np.testing.assert_array_almost_equal( - call_1.kwargs["weights"], - np.array( - [ - [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], - [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], - ] - ), - ) - - call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] - np.testing.assert_array_equal( - call_2.args[0], - np.array( - [ - [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32], - [33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43], - ] - ), - ) - self.assertEqual(call_2.kwargs["axis"], 0) - np.testing.assert_array_almost_equal( - call_2.kwargs["weights"], - np.array( - [ - [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], - [2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0], - ] - ), - ) - - def test_2d_weights(self): - self.cube.aggregated_by( - "val", self.mock_weighted_agg, weights=self.val_weights - ) - - self.assertEqual(self.mock_weighted_agg.aggregate.call_count, 3) - - # A simple mock.assert_called_with does not work due to ValueError: The - # truth value of an array with more than one element is ambiguous. Use - # a.any() or a.all() - call_1 = self.mock_weighted_agg.aggregate.mock_calls[0] - np.testing.assert_array_equal( - call_1.args[0], - np.array( - [ - [0, 1, 2, 6, 7, 9], - [11, 12, 13, 17, 18, 20], - [22, 23, 24, 28, 29, 31], - [33, 34, 35, 39, 40, 42], - ] - ), - ) - self.assertEqual(call_1.kwargs["axis"], 1) - np.testing.assert_array_almost_equal( - call_1.kwargs["weights"], np.ones((4, 6)) - ) - - call_2 = self.mock_weighted_agg.aggregate.mock_calls[1] - np.testing.assert_array_equal( - call_2.args[0], - np.array([[3, 4, 10], [14, 15, 21], [25, 26, 32], [36, 37, 43]]), - ) - self.assertEqual(call_2.kwargs["axis"], 1) - np.testing.assert_array_almost_equal( - call_2.kwargs["weights"], np.ones((4, 3)) - ) - - call_3 = self.mock_weighted_agg.aggregate.mock_calls[2] - np.testing.assert_array_equal( - call_3.args[0], np.array([[5, 8], [16, 19], [27, 30], [38, 41]]) - ) - self.assertEqual(call_3.kwargs["axis"], 1) - np.testing.assert_array_almost_equal( - call_3.kwargs["weights"], np.ones((4, 2)) - ) - - def test_returned(self): - output = self.cube.aggregated_by( - "simple_agg", self.mock_weighted_agg, returned=True - ) - - self.assertTrue(isinstance(output, tuple)) - self.assertEqual(len(output), 2) - self.assertEqual(output[0].shape, (2, 11)) - self.assertEqual(output[1].shape, (2, 11)) - - def test_fail_1d_weights_wrong_len(self): - wrong_weights = np.array([1.0, 2.0]) - msg = ( - r"1D weights must have the same length as the dimension that is " - r"aggregated, got 2, expected 11" - ) - with self.assertRaisesRegex(ValueError, msg): - self.cube.aggregated_by( - "val", self.mock_weighted_agg, weights=wrong_weights - ) - - def test_fail_weights_wrong_shape(self): - wrong_weights = np.ones((42, 1)) - msg = ( - r"Weights must either be 1D or have the same shape as the cube, " - r"got shape \(42, 1\) for weights, \(4, 11\) for cube" - ) - with self.assertRaisesRegex(ValueError, msg): - self.cube.aggregated_by( - "val", self.mock_weighted_agg, weights=wrong_weights - ) - - -class Test_aggregated_by__lazy(tests.IrisTest): - def setUp(self): - self.data = np.arange(44).reshape(4, 11) - self.lazydata = as_lazy_data(self.data) - self.cube = Cube(self.lazydata) - - val_coord = AuxCoord( - [0, 0, 0, 1, 1, 2, 0, 0, 2, 0, 1], long_name="val" - ) - label_coord = AuxCoord( - [ - "alpha", - "alpha", - "beta", - "beta", - "alpha", - "gamma", - "alpha", - "alpha", - "alpha", - "gamma", - "beta", - ], - long_name="label", - units="no_unit", - ) - simple_agg_coord = AuxCoord([1, 1, 2, 2], long_name="simple_agg") - - self.label_mean = np.array( - [ - [4.0 + 1.0 / 3.0, 5.0, 7.0], - [15.0 + 1.0 / 3.0, 16.0, 18.0], - [26.0 + 1.0 / 3.0, 27.0, 29.0], - [37.0 + 1.0 / 3.0, 38.0, 40.0], - ] - ) - self.val_mean = np.array( - [ - [4.0 + 1.0 / 6.0, 5.0 + 2.0 / 3.0, 6.5], - [15.0 + 1.0 / 6.0, 16.0 + 2.0 / 3.0, 17.5], - [26.0 + 1.0 / 6.0, 27.0 + 2.0 / 3.0, 28.5], - [37.0 + 1.0 / 6.0, 38.0 + 2.0 / 3.0, 39.5], - ] - ) - - self.cube.add_aux_coord(simple_agg_coord, 0) - self.cube.add_aux_coord(val_coord, 1) - self.cube.add_aux_coord(label_coord, 1) - - self.simple_weights = np.array([1.0, 0.0, 2.0, 2.0]) - self.val_weights = 2.0 * np.ones(self.cube.shape, dtype=np.float32) - - def test_agg_by_label__lazy(self): - # Aggregate a cube on a string coordinate label where label - # and val entries are not in step; the resulting cube has a val - # coord of bounded cells and a label coord of single string entries. - res_cube = self.cube.aggregated_by("label", MEAN) - val_coord = AuxCoord( - np.array([1.0, 0.5, 1.0]), - bounds=np.array([[0, 2], [0, 1], [0, 2]]), - long_name="val", - ) - label_coord = AuxCoord( - np.array(["alpha", "beta", "gamma"]), - long_name="label", - units="no_unit", - ) - self.assertTrue(res_cube.has_lazy_data()) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - self.assertArrayEqual(res_cube.data, self.label_mean) - self.assertFalse(res_cube.has_lazy_data()) - - def test_agg_by_val__lazy(self): - # Aggregate a cube on a numeric coordinate val where label - # and val entries are not in step; the resulting cube has a label - # coord with serialised labels from the aggregated cells. - res_cube = self.cube.aggregated_by("val", MEAN) - val_coord = AuxCoord(np.array([0, 1, 2]), long_name="val") - exp0 = "alpha|alpha|beta|alpha|alpha|gamma" - exp1 = "beta|alpha|beta" - exp2 = "gamma|alpha" - label_coord = AuxCoord( - np.array((exp0, exp1, exp2)), long_name="label", units="no_unit" - ) - self.assertTrue(res_cube.has_lazy_data()) - self.assertEqual(res_cube.coord("val"), val_coord) - self.assertEqual(res_cube.coord("label"), label_coord) - self.assertArrayEqual(res_cube.data, self.val_mean) - self.assertFalse(res_cube.has_lazy_data()) - - def test_single_string_aggregation__lazy(self): - aux_coords = [ - (AuxCoord(["a", "b", "a"], long_name="foo"), 0), - (AuxCoord(["a", "a", "a"], long_name="bar"), 0), - ] - cube = iris.cube.Cube( - as_lazy_data(np.arange(12).reshape(3, 4)), - aux_coords_and_dims=aux_coords, - ) - means = np.array([[4.0, 5.0, 6.0, 7.0], [4.0, 5.0, 6.0, 7.0]]) - result = cube.aggregated_by("foo", MEAN) - self.assertTrue(result.has_lazy_data()) - self.assertEqual(result.shape, (2, 4)) - self.assertEqual( - result.coord("bar"), AuxCoord(["a|a", "a"], long_name="bar") - ) - self.assertArrayEqual(result.data, means) - self.assertFalse(result.has_lazy_data()) - - def test_1d_weights__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) - - cube_agg = self.cube.aggregated_by( - "simple_agg", SUM, weights=self.simple_weights - ) - - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(cube_agg.has_lazy_data()) - self.assertEqual(cube_agg.shape, (2, 11)) - - row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] - row_1 = [ - 110.0, - 114.0, - 118.0, - 122.0, - 126.0, - 130.0, - 134.0, - 138.0, - 142.0, - 146.0, - 150.0, - ] - np.testing.assert_array_almost_equal( - cube_agg.data, np.array([row_0, row_1]) - ) - - def test_2d_weights__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) - - cube_agg = self.cube.aggregated_by( - "val", SUM, weights=self.val_weights - ) - - self.assertTrue(self.cube.has_lazy_data()) - self.assertTrue(cube_agg.has_lazy_data()) - - self.assertEqual(cube_agg.shape, (4, 3)) - np.testing.assert_array_almost_equal( - cube_agg.data, - np.array( - [ - [50.0, 34.0, 26.0], - [182.0, 100.0, 70.0], - [314.0, 166.0, 114.0], - [446.0, 232.0, 158.0], - ] - ), - ) - - def test_returned__lazy(self): - self.assertTrue(self.cube.has_lazy_data()) - - output = self.cube.aggregated_by( - "simple_agg", SUM, weights=self.simple_weights, returned=True - ) - - self.assertTrue(self.cube.has_lazy_data()) - - self.assertTrue(isinstance(output, tuple)) - self.assertEqual(len(output), 2) - - cube = output[0] - self.assertTrue(isinstance(cube, Cube)) - self.assertTrue(cube.has_lazy_data()) - self.assertEqual(cube.shape, (2, 11)) - row_0 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] - row_1 = [ - 110.0, - 114.0, - 118.0, - 122.0, - 126.0, - 130.0, - 134.0, - 138.0, - 142.0, - 146.0, - 150.0, - ] - np.testing.assert_array_almost_equal( - cube.data, np.array([row_0, row_1]) - ) - - weights = output[1] - self.assertEqual(weights.shape, (2, 11)) - np.testing.assert_array_almost_equal( - weights, - np.array( - [ - [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], - [4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0], - ] - ), - ) - - -class Test_aggregated_by__climatology(tests.IrisTest): - def setUp(self): - self.data = np.arange(100).reshape(20, 5) - self.aggregator = iris.analysis.MEAN - - def get_result( - self, - transpose: bool = False, - second_categorised: bool = False, - bounds: bool = False, - partially_aligned: bool = False, - partially_aligned_timelike: bool = False, - invalid_units: bool = False, - already_climatological: bool = False, - climatological_op: bool = True, - ) -> Cube: - cube_data = self.data - if transpose: - cube_data = cube_data.T - axes = [1, 0] - else: - axes = [0, 1] - if not invalid_units: - units = Unit("days since 1970-01-01") - else: - units = Unit("m") - if partially_aligned_timelike: - pa_units = Unit("days since 1970-01-01") - else: - pa_units = Unit("m") - - # DimCoords - aligned_coord = DimCoord( - np.arange(20), - long_name="aligned", - units=units, - ) - orthogonal_coord = DimCoord(np.arange(5), long_name="orth") - - if bounds: - aligned_coord.guess_bounds() - - aligned_coord.climatological = already_climatological - - dim_coords_and_dims = zip([aligned_coord, orthogonal_coord], axes) - - # AuxCoords - categorised_coord1 = AuxCoord( - np.tile([0, 1], 10), long_name="cat1", units=Unit("month") - ) - - if second_categorised: - categorised_coord2 = AuxCoord( - np.tile([0, 1, 2, 3, 4], 4), long_name="cat2" - ) - categorised_coords = [categorised_coord1, categorised_coord2] - else: - categorised_coords = categorised_coord1 - - aux_coords_and_dims = [ - (categorised_coord1, axes[0]), - ] - - if second_categorised: - aux_coords_and_dims.append((categorised_coord2, axes[0])) - - if partially_aligned: - partially_aligned_coord = AuxCoord( - cube_data + 1, - long_name="part_aligned", - units=pa_units, - ) - aux_coords_and_dims.append((partially_aligned_coord, (0, 1))) - - # Build cube - in_cube = iris.cube.Cube( - cube_data, - long_name="wibble", - dim_coords_and_dims=dim_coords_and_dims, - aux_coords_and_dims=aux_coords_and_dims, - ) - - out_cube = in_cube.aggregated_by( - categorised_coords, - self.aggregator, - climatological=climatological_op, - ) - - return out_cube - - def test_basic(self): - """ - Check the least complicated version works (set climatological, set - points correctly). - """ - result = self.get_result() - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[0, 18], [1, 19]]) - ) - self.assertTrue(aligned_coord.climatological) - self.assertIn(aligned_coord, result.dim_coords) - - categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) - - def test_2d_other_coord(self): - """ - Check that we can handle aggregation applying to a 2d AuxCoord that - covers the aggregation dimension and another one. - """ - result = self.get_result(partially_aligned=True) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[0, 18], [1, 19]]) - ) - self.assertTrue(aligned_coord.climatological) - - part_aligned_coord = result.coord("part_aligned") - self.assertArrayEqual( - part_aligned_coord.points, np.arange(46, 56).reshape(2, 5) - ) - self.assertArrayEqual( - part_aligned_coord.bounds, - np.array([np.arange(1, 11), np.arange(91, 101)]).T.reshape( - 2, 5, 2 - ), - ) - self.assertFalse(part_aligned_coord.climatological) - - def test_2d_timelike_other_coord(self): - """ - Check that we can handle aggregation applying to a 2d AuxCoord that - covers the aggregation dimension and another one. - """ - result = self.get_result( - partially_aligned=True, partially_aligned_timelike=True - ) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[0, 18], [1, 19]]) - ) - self.assertTrue(aligned_coord.climatological) - - part_aligned_coord = result.coord("part_aligned") - self.assertArrayEqual( - part_aligned_coord.points, np.arange(1, 11).reshape(2, 5) - ) - self.assertArrayEqual( - part_aligned_coord.bounds, - np.array([np.arange(1, 11), np.arange(91, 101)]).T.reshape( - 2, 5, 2 - ), - ) - self.assertTrue(part_aligned_coord.climatological) - - def test_transposed(self): - """ - Check that we can handle the axis of aggregation being a different one. - """ - result = self.get_result(transpose=True) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(2)) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[0, 18], [1, 19]]) - ) - self.assertTrue(aligned_coord.climatological) - - categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) - - def test_bounded(self): - """Check that we handle bounds correctly.""" - result = self.get_result(bounds=True) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, [-0.5, 0.5]) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) - ) - self.assertTrue(aligned_coord.climatological) - - def test_multiple_agg_coords(self): - """ - Check that we can aggregate on multiple coords on the same axis. - """ - result = self.get_result(second_categorised=True) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(10)) - self.assertArrayEqual( - aligned_coord.bounds, - np.array([np.arange(10), np.arange(10, 20)]).T, - ) - self.assertTrue(aligned_coord.climatological) - - categorised_coord1 = result.coord("cat1") - self.assertArrayEqual( - categorised_coord1.points, np.tile(np.arange(2), 5) - ) - self.assertIsNone(categorised_coord1.bounds) - self.assertFalse(categorised_coord1.climatological) - - categorised_coord2 = result.coord("cat2") - self.assertArrayEqual( - categorised_coord2.points, np.tile(np.arange(5), 2) - ) - self.assertIsNone(categorised_coord2.bounds) - self.assertFalse(categorised_coord2.climatological) - - def test_non_climatological_units(self): - """ - Check that the failure to set the climatological flag on an incompatible - unit is handled quietly. - """ - result = self.get_result(invalid_units=True) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(9, 11)) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[0, 18], [1, 19]]) - ) - self.assertFalse(aligned_coord.climatological) - - def test_clim_in_clim_op(self): - """ - Check the least complicated version works (set climatological, set - points correctly). For the input coordinate to be climatological, it - must have bounds - """ - result = self.get_result(bounds=True, already_climatological=True) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, [-0.5, 0.5]) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) - ) - self.assertTrue(aligned_coord.climatological) - - categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) - - def test_clim_in_no_clim_op(self): - """ - Check the least complicated version works (set climatological, set - points correctly). For the input coordinate to be climatological, it - must have bounds. - """ - result = self.get_result( - bounds=True, already_climatological=True, climatological_op=False - ) - - aligned_coord = result.coord("aligned") - self.assertArrayEqual(aligned_coord.points, np.arange(9, 11)) - self.assertArrayEqual( - aligned_coord.bounds, np.array([[-0.5, 18.5], [0.5, 19.5]]) - ) - self.assertTrue(aligned_coord.climatological) - - categorised_coord = result.coord("cat1") - self.assertArrayEqual(categorised_coord.points, np.arange(2)) - self.assertIsNone(categorised_coord.bounds) - self.assertFalse(categorised_coord.climatological) - - -class Test_aggregated_by__derived(tests.IrisTest): - def setUp(self): - self.cube = realistic_4d()[:, :10, :6, :8] - self.time_cat_coord = AuxCoord( - [0, 0, 1, 1, 2, 2], long_name="time_cat" - ) - self.cube.add_aux_coord(self.time_cat_coord, 0) - height_data = np.zeros(self.cube.shape[1]) - height_data[5:] = 1 - self.height_cat_coord = AuxCoord(height_data, long_name="height_cat") - self.cube.add_aux_coord(self.height_cat_coord, 1) - self.aggregator = iris.analysis.MEAN - - def test_grouped_dim(self): - """ - Check that derived coordinates are maintained when the coordinates they - derive from are aggregated. - """ - result = self.cube.aggregated_by( - self.height_cat_coord, - self.aggregator, - ) - assert len(result.aux_factories) == 1 - altitude = result.coord("altitude") - assert altitude.shape == (2, 6, 8) - - # Check the bounds are derived as expected. - orig_alt_bounds = self.cube.coord("altitude").bounds - bounds_0 = orig_alt_bounds[0::5, :, :, 0] - bounds_1 = orig_alt_bounds[4::5, :, :, 1] - expected_bounds = np.stack([bounds_0, bounds_1], axis=-1) - assert np.array_equal(expected_bounds, result.coord("altitude").bounds) - - def test_ungrouped_dim(self): - """ - Check that derived coordinates are preserved when aggregating along a - different axis. - """ - result = self.cube.aggregated_by( - self.time_cat_coord, - self.aggregator, - ) - assert len(result.aux_factories) == 1 - altitude = result.coord("altitude") - assert altitude == self.cube.coord("altitude") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/cube/test_Cube__operators.py b/lib/iris/tests/unit/cube/test_Cube__operators.py deleted file mode 100644 index e860c57636..0000000000 --- a/lib/iris/tests/unit/cube/test_Cube__operators.py +++ /dev/null @@ -1,252 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.cube.Cube` class operators.""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -import operator - -import dask.array as da -import numpy as np -import numpy.ma as ma - -import iris -from iris._lazy_data import as_lazy_data -from iris.coords import DimCoord - - -class Test_lazy_maths(tests.IrisTest): - def build_lazy_cube(self, points, dtype=np.float64, bounds=None, nx=10): - data = np.arange(len(points) * nx, dtype=dtype) + 1 # Just avoid 0. - data = data.reshape(len(points), nx) - data = as_lazy_data(data) - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - lat = DimCoord(points, "latitude", bounds=bounds) - lon = DimCoord(np.arange(nx), "longitude") - cube.add_dim_coord(lat, 0) - cube.add_dim_coord(lon, 1) - return cube - - def check_common(self, base_cube, result): - self.assertTrue(base_cube.has_lazy_data()) - self.assertTrue(result.has_lazy_data()) - self.assertIsInstance(result.lazy_data(), da.core.Array) - - def cube_cube_math_op(self, c1, math_op): - result = math_op(c1, c1) - self.check_common(c1, result) - expected = math_op(c1.data, c1.data) - self.assertArrayAlmostEqual(result.data, expected) - - def cube_scalar_math_op(self, c1, scalar, math_op, commutative=True): - result = math_op(c1, scalar) - if commutative: - self.assertEqual(math_op(c1, scalar), math_op(scalar, c1)) - self.check_common(c1, result) - expected = math_op(c1.data, scalar) - self.assertArrayAlmostEqual(result.data, expected) - - def test_add_cubes__float(self): - c1 = self.build_lazy_cube([1, 2]) - op = operator.add - self.cube_cube_math_op(c1, op) - - def test_add_scalar__float(self): - c1 = self.build_lazy_cube([1, 2]) - scalar = 5 - op = operator.add - self.cube_scalar_math_op(c1, scalar, op) - - def test_mul_cubes__float(self): - c1 = self.build_lazy_cube([1, 2]) - op = operator.mul - self.cube_cube_math_op(c1, op) - - def test_mul_scalar__float(self): - c1 = self.build_lazy_cube([1, 2]) - scalar = 5 - op = operator.mul - self.cube_scalar_math_op(c1, scalar, op) - - def test_sub_cubes__float(self): - c1 = self.build_lazy_cube([1, 2]) - op = operator.sub - self.cube_cube_math_op(c1, op) - - def test_sub_scalar__float(self): - c1 = self.build_lazy_cube([1, 2]) - scalar = 5 - op = operator.sub - self.cube_scalar_math_op(c1, scalar, op, commutative=False) - - def test_div_cubes__float(self): - c1 = self.build_lazy_cube([1, 2]) - op = operator.truediv - self.cube_cube_math_op(c1, op) - - def test_div_scalar__float(self): - c1 = self.build_lazy_cube([1, 2]) - scalar = 5 - op = operator.truediv - self.cube_scalar_math_op(c1, scalar, op, commutative=False) - - def test_add_cubes__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - op = operator.add - self.cube_cube_math_op(c1, op) - - def test_add_scalar__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - scalar = 5 - op = operator.add - self.cube_scalar_math_op(c1, scalar, op) - - def test_mul_cubes__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - op = operator.mul - self.cube_cube_math_op(c1, op) - - def test_mul_scalar__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - scalar = 5 - op = operator.mul - self.cube_scalar_math_op(c1, scalar, op) - - def test_sub_cubes__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - op = operator.sub - self.cube_cube_math_op(c1, op) - - def test_sub_scalar__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - scalar = 5 - op = operator.sub - self.cube_scalar_math_op(c1, scalar, op, commutative=False) - - def test_div_cubes__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - op = operator.truediv - self.cube_cube_math_op(c1, op) - - def test_div_scalar__int(self): - c1 = self.build_lazy_cube([1, 2], dtype=np.int64) - scalar = 5 - op = operator.truediv - self.cube_scalar_math_op(c1, scalar, op, commutative=False) - - -class Test_lazy_maths__scalar_cube(tests.IrisTest): - def build_lazy_cube(self, value, dtype=np.float64): - data = as_lazy_data(np.array(value, dtype=dtype)) - return iris.cube.Cube(data, standard_name="air_temperature", units="K") - - def setUp(self): - self.c1 = self.build_lazy_cube(3) - self.c2 = self.build_lazy_cube(4) - self.c3 = self.build_lazy_cube(3, dtype=np.int64) - self.c4 = self.build_lazy_cube(4, dtype=np.int64) - - def check_common(self, c1, c2, math_op): - cube = math_op(c1, c2) - data = cube.data - self.assertTrue(isinstance(data, np.ndarray)) - self.assertEqual(data.shape, ()) - - def test_add_scalar__int(self): - c3, c4, op = self.c3, 5, operator.add - self.check_common(c3, c4, op) - - def test_add_cubes__int(self): - c3, c4, op = self.c3, self.c4, operator.add - self.check_common(c3, c4, op) - - def test_mul_scalar__int(self): - c3, c4, op = self.c3, 5, operator.mul - self.check_common(c3, c4, op) - - def test_mul_cubes__int(self): - c3, c4, op = self.c3, self.c4, operator.mul - self.check_common(c3, c4, op) - - def test_sub_scalar__int(self): - c3, c4, op = self.c3, 5, operator.sub - self.check_common(c3, c4, op) - - def test_sub_cubes__int(self): - c3, c4, op = self.c3, self.c4, operator.sub - self.check_common(c3, c4, op) - - def test_div_scalar__int(self): - c3, c4, op = self.c3, 5, operator.truediv - self.check_common(c3, c4, op) - - def test_div_cubes__int(self): - c3, c4, op = self.c3, self.c4, operator.truediv - self.check_common(c3, c4, op) - - def test_add_scalar__float(self): - c1, c2, op = self.c1, 5, operator.add - self.check_common(c1, c2, op) - - def test_add_cubes__float(self): - c1, c2, op = self.c1, self.c2, operator.add - self.check_common(c1, c2, op) - - def test_mul_scalar__float(self): - c1, c2, op = self.c1, 5, operator.mul - self.check_common(c1, c2, op) - - def test_mul_cubes__float(self): - c1, c2, op = self.c1, self.c2, operator.mul - self.check_common(c1, c2, op) - - def test_sub_scalar__float(self): - c1, c2, op = self.c1, 5, operator.sub - self.check_common(c1, c2, op) - - def test_sub_cubes__float(self): - c1, c2, op = self.c1, self.c2, operator.sub - self.check_common(c1, c2, op) - - def test_div_scalar__float(self): - c1, c2, op = self.c1, 5, operator.truediv - self.check_common(c1, c2, op) - - def test_div_cubes__float(self): - c1, c2, op = self.c1, self.c2, operator.truediv - self.check_common(c1, c2, op) - - -class Test_lazy_maths__masked_data(tests.IrisTest): - def build_lazy_cube(self, dtype=np.float64): - data = ma.array( - [[1.0, 1.0], [1.0, 100000.0]], mask=[[0, 0], [0, 1]], dtype=dtype - ) - data = as_lazy_data(data) - cube = iris.cube.Cube(data, standard_name="air_temperature", units="K") - lat = DimCoord([-10, 10], "latitude") - lon = DimCoord([10, 20], "longitude") - cube.add_dim_coord(lat, 0) - cube.add_dim_coord(lon, 1) - return cube - - def test_subtract__float(self): - cube_a = self.build_lazy_cube() - cube_b = self.build_lazy_cube() - cube_c = cube_a - cube_b - self.assertTrue(ma.isMaskedArray(cube_c.data)) - - def test_subtract__int(self): - cube_a = self.build_lazy_cube(dtype=np.int64) - cube_b = self.build_lazy_cube(dtype=np.int64) - cube_c = cube_a - cube_b - self.assertTrue(ma.isMaskedArray(cube_c.data)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/data_manager/__init__.py b/lib/iris/tests/unit/data_manager/__init__.py deleted file mode 100644 index 41dcc0adf3..0000000000 --- a/lib/iris/tests/unit/data_manager/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._data_manager` module.""" diff --git a/lib/iris/tests/unit/data_manager/test_DataManager.py b/lib/iris/tests/unit/data_manager/test_DataManager.py deleted file mode 100644 index e73714730f..0000000000 --- a/lib/iris/tests/unit/data_manager/test_DataManager.py +++ /dev/null @@ -1,598 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris._data_manager.DataManager`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import copy -from unittest import mock - -import numpy as np -import numpy.ma as ma - -from iris._data_manager import DataManager -from iris._lazy_data import as_lazy_data - - -class Test___copy__(tests.IrisTest): - def test(self): - dm = DataManager(np.array(0)) - emsg = "Shallow-copy of {!r} is not permitted." - name = type(dm).__name__ - with self.assertRaisesRegex(copy.Error, emsg.format(name)): - copy.copy(dm) - - -class Test___deepcopy__(tests.IrisTest): - def test(self): - dm = DataManager(np.array(0)) - method = "iris._data_manager.DataManager._deepcopy" - return_value = mock.sentinel.return_value - with mock.patch(method) as mocker: - mocker.return_value = return_value - result = copy.deepcopy(dm) - self.assertEqual(mocker.call_count, 1) - [args], kwargs = mocker.call_args - self.assertEqual(kwargs, dict()) - self.assertEqual(len(args), 2) - expected = [return_value, [dm]] - for item in args.values(): - self.assertIn(item, expected) - self.assertIs(result, return_value) - - -class Test___eq__(tests.IrisTest): - def setUp(self): - self.shape = (2, 3, 4) - self.size = np.prod(self.shape) - self.real_array = np.arange(self.size, dtype=float).reshape(self.shape) - - def test_real_with_real(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(self.real_array.copy()) - self.assertEqual(dm1, dm2) - - def test_real_with_real_failure(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(np.ones(self.shape)) - self.assertFalse(dm1 == dm2) - - def test_real_with_real__dtype_failure(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(self.real_array.astype(int)) - self.assertFalse(dm1 == dm2) - - def test_real_with_lazy_failure(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(as_lazy_data(self.real_array)) - self.assertFalse(dm1 == dm2) - self.assertFalse(dm2 == dm1) - - def test_lazy_with_lazy(self): - dm1 = DataManager(as_lazy_data(self.real_array)) - dm2 = DataManager(as_lazy_data(self.real_array)) - self.assertEqual(dm1, dm2) - - def test_lazy_with_lazy_failure(self): - dm1 = DataManager(as_lazy_data(self.real_array)) - dm2 = DataManager(as_lazy_data(self.real_array) * 10) - self.assertFalse(dm1 == dm2) - - def test_lazy_with_lazy__dtype_failure(self): - dm1 = DataManager(as_lazy_data(self.real_array)) - dm2 = DataManager(as_lazy_data(self.real_array).astype(int)) - self.assertFalse(dm1 == dm2) - - def test_non_DataManager_failure(self): - dm = DataManager(np.array(0)) - self.assertFalse(dm == 0) - - -class Test___ne__(tests.IrisTest): - def setUp(self): - self.shape = (2, 3, 4) - self.size = np.prod(self.shape) - self.real_array = np.arange(self.size, dtype=float).reshape(self.shape) - - def test_real_with_real(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(np.ones(self.shape)) - self.assertNotEqual(dm1, dm2) - - def test_real_with_real_failure(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(self.real_array.copy()) - self.assertFalse(dm1 != dm2) - - def test_real_with_real__dtype(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(self.real_array.astype(int)) - self.assertNotEqual(dm1, dm2) - - def test_real_with_lazy(self): - dm1 = DataManager(self.real_array) - dm2 = DataManager(as_lazy_data(self.real_array)) - self.assertNotEqual(dm1, dm2) - self.assertNotEqual(dm2, dm1) - - def test_lazy_with_lazy(self): - dm1 = DataManager(as_lazy_data(self.real_array)) - dm2 = DataManager(as_lazy_data(self.real_array) * 10) - self.assertNotEqual(dm1, dm2) - - def test_lazy_with_lazy_failure(self): - dm1 = DataManager(as_lazy_data(self.real_array)) - dm2 = DataManager(as_lazy_data(self.real_array)) - self.assertFalse(dm1 != dm2) - - def test_lazy_with_lazy__dtype(self): - dm1 = DataManager(as_lazy_data(self.real_array)) - dm2 = DataManager(as_lazy_data(self.real_array).astype(int)) - self.assertNotEqual(dm1, dm2) - - def test_non_DataManager(self): - dm = DataManager(np.array(0)) - self.assertNotEqual(dm, 0) - - -class Test___repr__(tests.IrisTest): - def setUp(self): - self.real_array = np.array(123) - masked_array = ma.array([0, 1], mask=[0, 1]) - self.lazy_array = as_lazy_data(masked_array) - self.name = DataManager.__name__ - - def test_real(self): - dm = DataManager(self.real_array) - result = repr(dm) - expected = "{}({!r})".format(self.name, self.real_array) - self.assertEqual(result, expected) - - def test_lazy(self): - dm = DataManager(self.lazy_array) - result = repr(dm) - expected = "{}({!r})".format(self.name, self.lazy_array) - self.assertEqual(result, expected) - - -class Test__assert_axioms(tests.IrisTest): - def setUp(self): - self.real_array = np.array(0) - self.lazy_array = as_lazy_data(self.real_array) - self.dm = DataManager(self.real_array) - - def test_array_none(self): - self.dm._real_array = None - emsg = "Unexpected data state, got no lazy and no real data" - with self.assertRaisesRegex(AssertionError, emsg): - self.dm._assert_axioms() - - def test_array_all(self): - self.dm._lazy_array = self.lazy_array - emsg = "Unexpected data state, got lazy and real data" - with self.assertRaisesRegex(AssertionError, emsg): - self.dm._assert_axioms() - - -class Test__deepcopy(tests.IrisTest): - def setUp(self): - self.shape = (2, 3, 4) - self.size = np.prod(self.shape) - self.real_array = np.arange(self.size, dtype=float).reshape(self.shape) - self.memo = dict() - - def test_real(self): - dm = DataManager(self.real_array) - result = dm._deepcopy(self.memo) - self.assertEqual(dm, result) - - def test_lazy(self): - dm = DataManager(as_lazy_data(self.real_array)) - result = dm._deepcopy(self.memo) - self.assertEqual(dm, result) - - def test_real_with_real(self): - dm = DataManager(self.real_array) - data = self.real_array.copy() * 10 - result = dm._deepcopy(self.memo, data=data) - expected = DataManager(data) - self.assertEqual(result, expected) - self.assertIs(result._real_array, data) - - def test_real_with_lazy(self): - dm = DataManager(self.real_array) - data = as_lazy_data(self.real_array) * 10 - result = dm._deepcopy(self.memo, data=data) - expected = DataManager(data) - self.assertEqual(result, expected) - self.assertIs(result._lazy_array, data) - - def test_lazy_with_real(self): - dm = DataManager(as_lazy_data(self.real_array)) - data = self.real_array.copy() * 10 - result = dm._deepcopy(self.memo, data=data) - expected = DataManager(data) - self.assertEqual(result, expected) - self.assertIs(result._real_array, data) - - def test_lazy_with_lazy(self): - dm = DataManager(as_lazy_data(self.real_array)) - data = as_lazy_data(self.real_array) * 10 - result = dm._deepcopy(self.memo, data=data) - expected = DataManager(data) - self.assertEqual(result, expected) - self.assertIs(result._lazy_array, data) - - def test_real_with_real_failure(self): - dm = DataManager(self.real_array) - emsg = "Cannot copy" - with self.assertRaisesRegex(ValueError, emsg): - dm._deepcopy(self.memo, data=np.array(0)) - - def test_real_with_lazy_failure(self): - dm = DataManager(self.real_array) - emsg = "Cannot copy" - with self.assertRaisesRegex(ValueError, emsg): - dm._deepcopy(self.memo, data=as_lazy_data(np.array(0))) - - def test_lazy_with_real_failure(self): - dm = DataManager(as_lazy_data(self.real_array)) - emsg = "Cannot copy" - with self.assertRaisesRegex(ValueError, emsg): - dm._deepcopy(self.memo, data=np.array(0)) - - def test_lazy_with_lazy_failure(self): - dm = DataManager(as_lazy_data(self.real_array)) - emsg = "Cannot copy" - with self.assertRaisesRegex(ValueError, emsg): - dm._deepcopy(self.memo, data=as_lazy_data(np.array(0))) - - -class Test_data__getter(tests.IrisTest): - def setUp(self): - shape = (2, 3, 4) - size = np.prod(shape) - self.real_array = np.arange(size).reshape(shape) - self.lazy_array = as_lazy_data(self.real_array) - self.mask_array = ma.masked_array(self.real_array) - self.mask_array_masked = self.mask_array.copy() - self.mask_array_masked[0, 0, 0] = ma.masked - self.dtype = self.mask_array.dtype - self.fill_value = self.mask_array.fill_value - self.lazy_mask_array = as_lazy_data(self.mask_array) - self.lazy_mask_array_masked = as_lazy_data(self.mask_array_masked) - - def test_with_real_array(self): - dm = DataManager(self.real_array) - self.assertFalse(dm.has_lazy_data()) - result = dm.data - self.assertFalse(dm.has_lazy_data()) - self.assertIs(result, self.real_array) - - def test_with_lazy_array(self): - dm = DataManager(self.lazy_array) - self.assertTrue(dm.has_lazy_data()) - result = dm.data - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(result, self.real_array) - - def test_with_lazy_mask_array__not_masked(self): - dm = DataManager(self.lazy_mask_array) - self.assertTrue(dm.has_lazy_data()) - result = dm.data - self.assertFalse(dm.has_lazy_data()) - self.assertIsInstance(result, np.core.ndarray) - self.assertEqual(dm.dtype, self.dtype) - self.assertEqual(result.fill_value, self.fill_value) - self.assertArrayEqual(result, self.real_array) - - def test_with_lazy_mask_array__masked(self): - dm = DataManager(self.lazy_mask_array_masked) - self.assertTrue(dm.has_lazy_data()) - result = dm.data - self.assertFalse(dm.has_lazy_data()) - self.assertIsInstance(result, ma.MaskedArray) - self.assertEqual(dm.dtype, self.dtype) - self.assertEqual(result.fill_value, self.fill_value) - self.assertArrayEqual(result, self.mask_array_masked) - - def test_with_real_masked_constant(self): - masked_data = ma.masked_array([666], mask=True, dtype=np.dtype("f8")) - masked_constant = masked_data[0] - dm = DataManager(masked_constant) - result = dm.data - self.assertFalse(dm.has_lazy_data()) - self.assertIsInstance(result, ma.MaskedArray) - self.assertNotIsInstance(result, ma.core.MaskedConstant) - self.assertMaskedArrayEqual(result, masked_data) - - def test_with_lazy_masked_constant(self): - masked_data = ma.masked_array([666], mask=True) - masked_constant = masked_data[0] - lazy_masked_constant = as_lazy_data(masked_constant) - dm = DataManager(lazy_masked_constant) - result = dm.data - self.assertFalse(dm.has_lazy_data()) - self.assertIsInstance(result, ma.MaskedArray) - self.assertNotIsInstance(result, ma.core.MaskedConstant) - self.assertMaskedArrayEqual(result, masked_data) - - -class Test_data__setter(tests.IrisTest): - def test_zero_ndim_real_with_scalar_int(self): - value = 456 - dm = DataManager(np.array(123)) - self.assertFalse(dm.has_lazy_data()) - dm.data = value - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, np.array(value)) - - def test_zero_ndim_real_with_scalar_float(self): - value = 456.0 - dm = DataManager(np.array(123)) - self.assertFalse(dm.has_lazy_data()) - dm.data = value - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, np.array(value)) - - def test_zero_ndim_real_with_zero_ndim_real(self): - real_array = np.array(456) - dm = DataManager(np.array(123)) - self.assertFalse(dm.has_lazy_data()) - dm.data = real_array - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, real_array) - - def test_zero_ndim_real_with_zero_ndim_lazy(self): - lazy_array = as_lazy_data(np.array(456)) - dm = DataManager(np.array(123)) - self.assertFalse(dm.has_lazy_data()) - dm.data = lazy_array - self.assertTrue(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, lazy_array.compute()) - - def test_zero_ndim_lazy_with_zero_ndim_real(self): - real_array = np.array(456) - dm = DataManager(as_lazy_data(np.array(123))) - self.assertTrue(dm.has_lazy_data()) - dm.data = real_array - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, real_array) - - def test_zero_ndim_lazy_with_zero_ndim_lazy(self): - lazy_array = as_lazy_data(np.array(456)) - dm = DataManager(as_lazy_data(np.array(123))) - self.assertTrue(dm.has_lazy_data()) - dm.data = lazy_array - self.assertTrue(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, lazy_array.compute()) - - def test_zero_ndim_real_to_scalar_1d_real_promote(self): - real_array = np.array([456]) - dm = DataManager(np.array(123)) - self.assertFalse(dm.has_lazy_data()) - dm.data = real_array - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, real_array) - - def test_zero_ndim_real_to_scalar_1d_lazy_promote(self): - lazy_array = as_lazy_data(np.array([456])) - dm = DataManager(np.array(123)) - self.assertFalse(dm.has_lazy_data()) - dm.data = lazy_array - self.assertTrue(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, lazy_array.compute()) - - def test_zero_ndim_lazy_to_scalar_1d_real_promote(self): - real_array = np.array([456]) - dm = DataManager(as_lazy_data(np.array(123))) - self.assertTrue(dm.has_lazy_data()) - dm.data = real_array - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, real_array) - - def test_zero_ndim_lazy_to_scalar_1d_lazy_promote(self): - lazy_array = as_lazy_data(np.array([456])) - dm = DataManager(as_lazy_data(np.array(123))) - self.assertTrue(dm.has_lazy_data()) - dm.data = lazy_array - self.assertTrue(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, lazy_array.compute()) - - def test_scalar_1d_to_zero_ndim_fail(self): - dm = DataManager(np.array([123])) - emsg = r"Require data with shape \(1,\), got \(\)." - with self.assertRaisesRegex(ValueError, emsg): - dm.data = 456 - - def test_nd_real_to_nd_real(self): - shape = (2, 3, 4) - size = np.prod(shape) - real_array = np.arange(size).reshape(shape) - dm = DataManager(real_array * 10) - self.assertFalse(dm.has_lazy_data()) - dm.data = real_array - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, real_array) - - def test_nd_real_to_nd_lazy(self): - shape = (2, 3, 4) - size = np.prod(shape) - real_array = np.arange(size).reshape(shape) - lazy_array = as_lazy_data(real_array) * 10 - dm = DataManager(real_array) - self.assertFalse(dm.has_lazy_data()) - dm.data = lazy_array - self.assertTrue(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, lazy_array.compute()) - - def test_nd_lazy_to_nd_real(self): - shape = (2, 3, 4) - size = np.prod(shape) - real_array = np.arange(size).reshape(shape) - lazy_array = as_lazy_data(real_array) - dm = DataManager(lazy_array * 10) - self.assertTrue(dm.has_lazy_data()) - dm.data = real_array - self.assertFalse(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, real_array) - - def test_nd_lazy_to_nd_lazy(self): - shape = (2, 3, 4) - size = np.prod(shape) - real_array = np.arange(size).reshape(shape) - lazy_array = as_lazy_data(real_array) - dm = DataManager(lazy_array * 10) - self.assertTrue(dm.has_lazy_data()) - dm.data = lazy_array - self.assertTrue(dm.has_lazy_data()) - self.assertArrayEqual(dm.data, lazy_array.compute()) - - def test_coerce_to_ndarray(self): - shape = (2, 3) - size = np.prod(shape) - real_array = np.arange(size).reshape(shape) - matrix = np.matrix(real_array) - dm = DataManager(real_array) - dm.data = matrix - self.assertIsInstance(dm._real_array, np.core.ndarray) - self.assertIsInstance(dm.data, np.core.ndarray) - self.assertArrayEqual(dm.data, real_array) - - def test_real_masked_constant_to_array(self): - masked_data = ma.masked_array([666], mask=True, dtype=np.dtype("f8")) - masked_constant = masked_data[0] - dm = DataManager(masked_constant) - self.assertIsInstance(dm._real_array, ma.MaskedArray) - self.assertNotIsInstance(dm._real_array, ma.core.MaskedConstant) - self.assertIsInstance(dm.data, ma.MaskedArray) - self.assertNotIsInstance(dm.data, ma.core.MaskedConstant) - self.assertMaskedArrayEqual(dm.data, masked_data) - - -class Test_dtype(tests.IrisTest): - def setUp(self): - self.real_array = np.array(0, dtype=np.dtype("int64")) - self.lazy_array = as_lazy_data(np.array(0, dtype=np.dtype("float64"))) - - def test_real_array(self): - dm = DataManager(self.real_array) - self.assertEqual(dm.dtype, np.dtype("int64")) - - def test_lazy_array(self): - dm = DataManager(self.lazy_array) - self.assertEqual(dm.dtype, np.dtype("float64")) - - -class Test_ndim(tests.IrisTest): - def test_ndim_0(self): - real_array = np.array(0) - dm = DataManager(real_array) - self.assertEqual(dm.ndim, 0) - lazy_array = as_lazy_data(real_array) - dm = DataManager(lazy_array) - self.assertEqual(dm.ndim, 0) - - def test_ndim_nd(self): - shape = (2, 3, 4) - real_array = np.arange(24).reshape(shape) - dm = DataManager(real_array) - self.assertEqual(dm.ndim, len(shape)) - lazy_array = as_lazy_data(real_array) - dm = DataManager(lazy_array) - self.assertEqual(dm.ndim, len(shape)) - - -class Test_shape(tests.IrisTest): - def test_shape_scalar(self): - real_array = np.array(0) - dm = DataManager(real_array) - self.assertEqual(dm.shape, ()) - lazy_array = as_lazy_data(real_array) - dm = DataManager(lazy_array) - self.assertEqual(dm.shape, ()) - - def test_shape_nd(self): - shape = (2, 3, 4) - real_array = np.arange(24).reshape(shape) - dm = DataManager(real_array) - self.assertEqual(dm.shape, shape) - lazy_array = as_lazy_data(real_array) - dm = DataManager(lazy_array) - self.assertEqual(dm.shape, shape) - - -class Test_copy(tests.IrisTest): - def setUp(self): - self.method = "iris._data_manager.DataManager._deepcopy" - self.data = mock.sentinel.data - self.return_value = mock.sentinel.return_value - self.memo = {} - - def test(self): - dm = DataManager(np.array(0)) - kwargs = dict(data=self.data) - with mock.patch(self.method) as mocker: - mocker.return_value = self.return_value - result = dm.copy(data=self.data) - mocker.assert_called_once_with(self.memo, **kwargs) - self.assertIs(result, self.return_value) - - -class Test_core_data(tests.IrisTest): - def test_real_array(self): - real_array = np.array(0) - dm = DataManager(real_array) - self.assertIs(dm.core_data(), real_array) - - def test_lazy_array(self): - lazy_array = as_lazy_data(np.array(0)) - dm = DataManager(lazy_array) - self.assertIs(dm.core_data(), lazy_array) - - -class Test_has_lazy_data(tests.IrisTest): - def setUp(self): - self.real_array = np.array(0) - self.lazy_array = as_lazy_data(self.real_array) - - def test_with_lazy_array(self): - dm = DataManager(self.lazy_array) - self.assertTrue(dm.has_lazy_data()) - - def test_with_real_array(self): - dm = DataManager(self.real_array) - self.assertFalse(dm.has_lazy_data()) - - -class Test_lazy_data(tests.IrisTest): - def setUp(self): - self.real_array = np.array(0) - self.lazy_array = as_lazy_data(self.real_array) - - def test_with_real_array(self): - dm = DataManager(self.real_array) - self.assertFalse(dm.has_lazy_data()) - result = dm.lazy_data() - self.assertFalse(dm.has_lazy_data()) - self.assertEqual(result, self.lazy_array) - self.assertFalse(dm.has_lazy_data()) - - def test_with_lazy_array(self): - dm = DataManager(self.lazy_array) - self.assertTrue(dm.has_lazy_data()) - result = dm.lazy_data() - self.assertTrue(dm.has_lazy_data()) - self.assertIs(result, dm._lazy_array) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/__init__.py b/lib/iris/tests/unit/experimental/__init__.py deleted file mode 100644 index 438827bab2..0000000000 --- a/lib/iris/tests/unit/experimental/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental` package.""" diff --git a/lib/iris/tests/unit/experimental/raster/__init__.py b/lib/iris/tests/unit/experimental/raster/__init__.py deleted file mode 100644 index 5f85d810c9..0000000000 --- a/lib/iris/tests/unit/experimental/raster/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.raster` module.""" diff --git a/lib/iris/tests/unit/experimental/raster/test_export_geotiff.py b/lib/iris/tests/unit/experimental/raster/test_export_geotiff.py deleted file mode 100644 index a3b68ef761..0000000000 --- a/lib/iris/tests/unit/experimental/raster/test_export_geotiff.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.experimental.raster.export_geotiff` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import re - -import numpy as np - -try: - from osgeo import gdal - - from iris.experimental.raster import export_geotiff -except ImportError: - gdal = None - -from iris.coord_systems import GeogCS -from iris.coords import DimCoord -from iris.cube import Cube - - -@tests.skip_gdal -class TestDtypeAndValues(tests.IrisTest): - def _cube(self, dtype): - data = np.arange(12).reshape(3, 4).astype(dtype) + 20 - cube = Cube(data, "air_pressure_anomaly") - coord = DimCoord(np.arange(3), "latitude", units="degrees") - coord.guess_bounds() - cube.add_dim_coord(coord, 0) - coord = DimCoord(np.arange(4), "longitude", units="degrees") - coord.guess_bounds() - cube.add_dim_coord(coord, 1) - return cube - - def _check_dtype(self, dtype, gdal_dtype): - cube = self._cube(dtype) - with self.temp_filename(".tif") as temp_filename: - export_geotiff(cube, temp_filename) - dataset = gdal.Open(temp_filename, gdal.GA_ReadOnly) - band = dataset.GetRasterBand(1) - self.assertEqual(band.DataType, gdal_dtype) - self.assertEqual(band.ComputeRasterMinMax(1), (20, 31)) - - def test_int16(self): - self._check_dtype("i2", gdal.GDT_Int16) - - def test_int32(self): - self._check_dtype("i4", gdal.GDT_Int32) - - def test_uint8(self): - self._check_dtype("u1", gdal.GDT_Byte) - - def test_uint16(self): - self._check_dtype("u2", gdal.GDT_UInt16) - - def test_uint32(self): - self._check_dtype("u4", gdal.GDT_UInt32) - - def test_float32(self): - self._check_dtype("f4", gdal.GDT_Float32) - - def test_float64(self): - self._check_dtype("f8", gdal.GDT_Float64) - - def test_invalid(self): - cube = self._cube("i1") - with self.assertRaises(ValueError): - with self.temp_filename(".tif") as temp_filename: - export_geotiff(cube, temp_filename) - - -@tests.skip_gdal -class TestProjection(tests.IrisTest): - def _cube(self, ellipsoid=None): - data = np.arange(12).reshape(3, 4).astype("u1") - cube = Cube(data, "air_pressure_anomaly") - coord = DimCoord( - np.arange(3), "latitude", units="degrees", coord_system=ellipsoid - ) - coord.guess_bounds() - cube.add_dim_coord(coord, 0) - coord = DimCoord( - np.arange(4), "longitude", units="degrees", coord_system=ellipsoid - ) - coord.guess_bounds() - cube.add_dim_coord(coord, 1) - return cube - - def test_no_ellipsoid(self): - cube = self._cube() - with self.temp_filename(".tif") as temp_filename: - export_geotiff(cube, temp_filename) - dataset = gdal.Open(temp_filename, gdal.GA_ReadOnly) - self.assertEqual(dataset.GetProjection(), "") - - def test_sphere(self): - cube = self._cube(GeogCS(6377000)) - with self.temp_filename(".tif") as temp_filename: - export_geotiff(cube, temp_filename) - dataset = gdal.Open(temp_filename, gdal.GA_ReadOnly) - projection_string = dataset.GetProjection() - # String has embedded floating point values, - # Test with values to N decimal places, using a regular expression. - re_pattern = ( - r'GEOGCS\["unknown",DATUM\["unknown",' - r'SPHEROID\["unknown",637....,0\]\],PRIMEM\["Greenwich",0\],' - r'UNIT\["degree",0.01745[0-9]*,AUTHORITY\["EPSG","9122"\]\],' - r'AXIS\["Latitude",NORTH\],AXIS\["Longitude",EAST\]\]' - ) - re_exp = re.compile(re_pattern) - self.assertIsNotNone( - re_exp.match(projection_string), - "projection string {!r} does not match {!r}".format( - projection_string, re_pattern - ), - ) - - def test_ellipsoid(self): - cube = self._cube(GeogCS(6377000, 6360000)) - with self.temp_filename(".tif") as temp_filename: - export_geotiff(cube, temp_filename) - dataset = gdal.Open(temp_filename, gdal.GA_ReadOnly) - projection_string = dataset.GetProjection() - # String has embedded floating point values, - # Test with values to N decimal places, using a regular expression. - re_pattern = ( - r'GEOGCS\["unknown",DATUM\["unknown",' - r'SPHEROID\["unknown",637....,375.117[0-9]*\]\],' - r'PRIMEM\["Greenwich",0\],UNIT\["degree",0.01745[0-9]*,' - r'AUTHORITY\["EPSG","9122"\]\],AXIS\["Latitude",NORTH\],' - r'AXIS\["Longitude",EAST\]\]' - ) - re_exp = re.compile(re_pattern) - self.assertIsNotNone( - re_exp.match(projection_string), - "projection string {!r} does not match {!r}".format( - projection_string, re_pattern - ), - ) - - -@tests.skip_gdal -class TestGeoTransform(tests.IrisTest): - def test_(self): - data = np.arange(12).reshape(3, 4).astype(np.uint8) - cube = Cube(data, "air_pressure_anomaly") - coord = DimCoord([30, 40, 50], "latitude", units="degrees") - coord.guess_bounds() - cube.add_dim_coord(coord, 0) - coord = DimCoord([-10, -5, 0, 5], "longitude", units="degrees") - coord.guess_bounds() - cube.add_dim_coord(coord, 1) - with self.temp_filename(".tif") as temp_filename: - export_geotiff(cube, temp_filename) - dataset = gdal.Open(temp_filename, gdal.GA_ReadOnly) - self.assertEqual( - dataset.GetGeoTransform(), (-12.5, 5, 0, 55, 0, -10) - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/regrid/__init__.py b/lib/iris/tests/unit/experimental/regrid/__init__.py deleted file mode 100644 index 578c15f11c..0000000000 --- a/lib/iris/tests/unit/experimental/regrid/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.regrid` package.""" diff --git a/lib/iris/tests/unit/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py b/lib/iris/tests/unit/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py deleted file mode 100644 index 5ec3c956b9..0000000000 --- a/lib/iris/tests/unit/experimental/regrid/test_regrid_area_weighted_rectilinear_src_and_grid.py +++ /dev/null @@ -1,192 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function -:func:`iris.experimental.regrid.regrid_area_weighted_rectilinear_src_and_grid`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris.coord_systems import GeogCS -from iris.coords import DimCoord -from iris.cube import Cube -from iris.experimental.regrid import ( - regrid_area_weighted_rectilinear_src_and_grid as regrid, -) -from iris.tests.experimental.regrid.test_regrid_area_weighted_rectilinear_src_and_grid import ( - _resampled_grid, -) - - -class TestMdtol(tests.IrisTest): - # Tests to check the masking behaviour controlled by mdtol kwarg. - def setUp(self): - # A (3, 2, 4) cube with a masked element. - cube = Cube(np.ma.arange(24, dtype=np.int32).reshape((3, 2, 4))) - cs = GeogCS(6371229) - coord = DimCoord( - points=np.array([-1, 0, 1], dtype=np.int32), - standard_name="latitude", - units="degrees", - coord_system=cs, - ) - cube.add_dim_coord(coord, 0) - coord = DimCoord( - points=np.array([-1, 0, 1, 2], dtype=np.int32), - standard_name="longitude", - units="degrees", - coord_system=cs, - ) - cube.add_dim_coord(coord, 2) - cube.coord("latitude").guess_bounds() - cube.coord("longitude").guess_bounds() - cube.data[1, 1, 2] = ma.masked - self.src_cube = cube - # Create (7, 2, 9) grid cube. - self.grid_cube = _resampled_grid(cube, 2.3, 2.4) - - def test_default(self): - res = regrid(self.src_cube, self.grid_cube) - expected_mask = np.zeros((7, 2, 9), bool) - expected_mask[2:5, 1, 4:7] = True - self.assertArrayEqual(res.data.mask, expected_mask) - - def test_zero(self): - res = regrid(self.src_cube, self.grid_cube, mdtol=0) - expected_mask = np.zeros((7, 2, 9), bool) - expected_mask[2:5, 1, 4:7] = True - self.assertArrayEqual(res.data.mask, expected_mask) - - def test_one(self): - res = regrid(self.src_cube, self.grid_cube, mdtol=1) - expected_mask = np.zeros((7, 2, 9), bool) - # Only a single cell has all contributing cells masked. - expected_mask[3, 1, 5] = True - self.assertArrayEqual(res.data.mask, expected_mask) - - def test_fraction_below_min(self): - # Cells in target grid that overlap with the masked src cell - # have the following fractions (approx. due to spherical area). - # 4 5 6 7 - # 2 ---------------------- - # | 0.33 | 0.66 | 0.50 | - # 3 ---------------------- - # | 0.33 | 1.00 | 0.75 | - # 4 ---------------------- - # | 0.33 | 0.66 | 0.50 | - # 5 ---------------------- - # - - # Threshold less than minimum fraction. - mdtol = 0.2 - res = regrid(self.src_cube, self.grid_cube, mdtol=mdtol) - expected_mask = np.zeros((7, 2, 9), bool) - expected_mask[2:5, 1, 4:7] = True - self.assertArrayEqual(res.data.mask, expected_mask) - - def test_fraction_between_min_and_max(self): - # Threshold between min and max fraction. See - # test_fraction_below_min() comment for picture showing - # the fractions of masked data. - mdtol = 0.6 - res = regrid(self.src_cube, self.grid_cube, mdtol=mdtol) - expected_mask = np.zeros((7, 2, 9), bool) - expected_mask[2:5, 1, 5] = True - expected_mask[3, 1, 6] = True - self.assertArrayEqual(res.data.mask, expected_mask) - - def test_src_not_masked_array(self): - self.src_cube.data = self.src_cube.data.filled(1.0) - res = regrid(self.src_cube, self.grid_cube, mdtol=0.9) - self.assertFalse(ma.isMaskedArray(res.data)) - - def test_boolean_mask(self): - self.src_cube.data = np.ma.arange(24).reshape(3, 2, 4) - res = regrid(self.src_cube, self.grid_cube, mdtol=0.9) - self.assertEqual(ma.count_masked(res.data), 0) - - def test_scalar_no_overlap(self): - # Slice src so result collapses to a scalar. - src_cube = self.src_cube[:, 1, :] - # Regrid to a single cell with no overlap with masked src cells. - grid_cube = self.grid_cube[2, 1, 3] - res = regrid(src_cube, grid_cube, mdtol=0.8) - self.assertFalse(ma.isMaskedArray(res.data)) - - def test_scalar_with_overlap_below_mdtol(self): - # Slice src so result collapses to a scalar. - src_cube = self.src_cube[:, 1, :] - # Regrid to a single cell with 50% overlap with masked src cells. - grid_cube = self.grid_cube[3, 1, 4] - # Set threshold (mdtol) to greater than 0.5 (50%). - res = regrid(src_cube, grid_cube, mdtol=0.6) - self.assertEqual(ma.count_masked(res.data), 0) - - def test_scalar_with_overlap_above_mdtol(self): - # Slice src so result collapses to a scalar. - src_cube = self.src_cube[:, 1, :] - # Regrid to a single cell with 50% overlap with masked src cells. - grid_cube = self.grid_cube[3, 1, 4] - # Set threshold (mdtol) to less than 0.5 (50%). - res = regrid(src_cube, grid_cube, mdtol=0.4) - self.assertEqual(ma.count_masked(res.data), 1) - - -class TestWrapAround(tests.IrisTest): - def test_float_tolerant_equality(self): - # Ensure that floating point numbers are treated appropriately when - # introducing precision difference from wrap_around. - source = Cube([[1]]) - cs = GeogCS(6371229) - - bounds = np.array([[-91, 0]], dtype="float") - points = bounds.mean(axis=1) - lon_coord = DimCoord( - points, - bounds=bounds, - standard_name="longitude", - units="degrees", - coord_system=cs, - ) - source.add_aux_coord(lon_coord, 1) - - bounds = np.array([[-90, 90]], dtype="float") - points = bounds.mean(axis=1) - lat_coord = DimCoord( - points, - bounds=bounds, - standard_name="latitude", - units="degrees", - coord_system=cs, - ) - source.add_aux_coord(lat_coord, 0) - - grid = Cube([[0]]) - bounds = np.array([[270, 360]], dtype="float") - points = bounds.mean(axis=1) - lon_coord = DimCoord( - points, - bounds=bounds, - standard_name="longitude", - units="degrees", - coord_system=cs, - ) - grid.add_aux_coord(lon_coord, 1) - grid.add_aux_coord(lat_coord, 0) - - res = regrid(source, grid) - # The result should be equal to the source data and NOT be masked. - self.assertArrayEqual(res.data, np.array([1.0])) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/regrid/test_regrid_weighted_curvilinear_to_rectilinear.py b/lib/iris/tests/unit/experimental/regrid/test_regrid_weighted_curvilinear_to_rectilinear.py deleted file mode 100644 index b0908dd2e4..0000000000 --- a/lib/iris/tests/unit/experimental/regrid/test_regrid_weighted_curvilinear_to_rectilinear.py +++ /dev/null @@ -1,378 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function -:func:`iris.experimental.regrid.regrid_weighted_curvilinear_to_rectilinear`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import copy - -import numpy as np -import numpy.ma as ma - -import iris -from iris.coord_systems import GeogCS, LambertConformal -import iris.coords -from iris.coords import AuxCoord, DimCoord -import iris.cube -from iris.experimental.regrid import ( - regrid_weighted_curvilinear_to_rectilinear as regrid, -) -from iris.fileformats.pp import EARTH_RADIUS - -PLAIN_LATLON_CS = GeogCS(EARTH_RADIUS) - - -class Test(tests.IrisTest): - def setUp(self): - # Source cube. - self.test_src_name = "air_temperature" - self.test_src_units = "K" - self.test_src_data = ma.arange(1, 13, dtype=np.float64).reshape(3, 4) - self.test_src_attributes = dict(wibble="wobble") - self.test_scalar_coord = iris.coords.DimCoord( - [1], long_name="test_scalar_coord" - ) - self.src = iris.cube.Cube( - self.test_src_data, - standard_name=self.test_src_name, - units=self.test_src_units, - aux_coords_and_dims=[(self.test_scalar_coord, None)], - attributes=self.test_src_attributes, - ) - - # Source cube x-coordinates. - points = np.array( - [[10, 20, 200, 220], [110, 120, 180, 185], [190, 203, 211, 220]] - ) - self.src_x_positive = iris.coords.AuxCoord( - points, - standard_name="longitude", - units="degrees", - coord_system=PLAIN_LATLON_CS, - ) - self.src_x_transpose = iris.coords.AuxCoord( - points.T, - standard_name="longitude", - units="degrees", - coord_system=PLAIN_LATLON_CS, - ) - points = np.array( - [ - [-180, -176, -170, -150], - [-180, -179, -178, -177], - [-170, -168, -159, -140], - ] - ) - self.src_x_negative = iris.coords.AuxCoord( - points, - standard_name="longitude", - units="degrees", - coord_system=PLAIN_LATLON_CS, - ) - - # Source cube y-coordinates. - points = np.array([[0, 4, 3, 1], [5, 7, 10, 6], [12, 20, 15, 30]]) - self.src_y = iris.coords.AuxCoord( - points, - standard_name="latitude", - units="degrees", - coord_system=PLAIN_LATLON_CS, - ) - self.src_y_transpose = iris.coords.AuxCoord( - points.T, - standard_name="latitude", - units="degrees", - coord_system=PLAIN_LATLON_CS, - ) - - # Weights. - self.weight_factor = 10 - self.weights = np.asarray(self.test_src_data) * self.weight_factor - - # Target grid cube. - self.grid = iris.cube.Cube(np.zeros((2, 2))) - - # Target grid cube x-coordinates. - self.grid_x_inc = iris.coords.DimCoord( - [187, 200], - standard_name="longitude", - units="degrees", - bounds=[[180, 190], [190, 220]], - coord_system=PLAIN_LATLON_CS, - ) - self.grid_x_dec = iris.coords.DimCoord( - [200, 187], - standard_name="longitude", - units="degrees", - bounds=[[220, 190], [190, 180]], - coord_system=PLAIN_LATLON_CS, - ) - - # Target grid cube y-coordinates. - self.grid_y_inc = iris.coords.DimCoord( - [2, 10], - standard_name="latitude", - units="degrees", - bounds=[[0, 5], [5, 30]], - coord_system=PLAIN_LATLON_CS, - ) - self.grid_y_dec = iris.coords.DimCoord( - [10, 2], - standard_name="latitude", - units="degrees", - bounds=[[30, 5], [5, 0]], - coord_system=PLAIN_LATLON_CS, - ) - - def _weighted_mean(self, points): - points = np.asarray(points, dtype=np.float64) - weights = points * self.weight_factor - numerator = denominator = 0 - for point, weight in zip(points, weights): - numerator += point * weight - denominator += weight - return numerator / denominator - - def _expected_cube(self, data): - cube = iris.cube.Cube(data) - cube.metadata = copy.deepcopy(self.src.metadata) - grid_x = self.grid.coord(axis="x") - grid_y = self.grid.coord(axis="y") - cube.add_dim_coord(grid_x.copy(), self.grid.coord_dims(grid_x)) - cube.add_dim_coord(grid_y.copy(), self.grid.coord_dims(grid_y)) - src_x = self.src.coord(axis="x") - src_y = self.src.coord(axis="y") - for coord in self.src.aux_coords: - if coord is not src_x and coord is not src_y: - if not self.src.coord_dims(coord): - cube.add_aux_coord(coord) - return cube - - def test_aligned_src_x(self): - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_positive, (0, 1)) - self.grid.add_dim_coord(self.grid_y_inc, 0) - self.grid.add_dim_coord(self.grid_x_inc, 1) - result = regrid(self.src, self.weights, self.grid) - data = np.array( - [ - 0, - self._weighted_mean([3]), - self._weighted_mean([7, 8]), - self._weighted_mean([9, 10, 11]), - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[True, False], [False, False]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_non_latlon(self): - odd_coord_system = LambertConformal() - co_src_y = AuxCoord( - self.src_y.points, - standard_name="projection_y_coordinate", - units="km", - coord_system=odd_coord_system, - ) - co_src_x = AuxCoord( - self.src_x_positive.points, - standard_name="projection_x_coordinate", - units="km", - coord_system=odd_coord_system, - ) - co_grid_y = DimCoord( - self.grid_y_inc.points, - bounds=self.grid_y_inc.bounds, - standard_name="projection_y_coordinate", - units="km", - coord_system=odd_coord_system, - ) - co_grid_x = DimCoord( - self.grid_x_inc.points, - bounds=self.grid_x_inc.bounds, - standard_name="projection_x_coordinate", - units="km", - coord_system=odd_coord_system, - ) - self.src.add_aux_coord(co_src_y, (0, 1)) - self.src.add_aux_coord(co_src_x, (0, 1)) - self.grid.add_dim_coord(co_grid_y, 0) - self.grid.add_dim_coord(co_grid_x, 1) - result = regrid(self.src, self.weights, self.grid) - data = np.array( - [ - 0, - self._weighted_mean([3]), - self._weighted_mean([7, 8]), - self._weighted_mean([9, 10, 11]), - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[True, False], [False, False]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_src_xy_not_2d(self): - new_shape = (2, 2, 3) - # Reshape the source cube, including the X and Y coordinates, - # from (3, 4) to (2, 2, 3). - # This is really an "invalid" reshape, but should still work because - # the XY shape is actually irrelevant to the regrid operation. - src = iris.cube.Cube( - self.test_src_data.reshape(new_shape), - standard_name=self.test_src_name, - units=self.test_src_units, - aux_coords_and_dims=[(self.test_scalar_coord, None)], - attributes=self.test_src_attributes, - ) - co_src_y = self.src_y.copy(points=self.src_y.points.reshape(new_shape)) - co_src_x = self.src_x_positive.copy( - points=self.src_x_positive.points.reshape(new_shape) - ) - src.add_aux_coord(co_src_y, (0, 1, 2)) - src.add_aux_coord(co_src_x, (0, 1, 2)) - self.grid.add_dim_coord(self.grid_y_inc, 0) - self.grid.add_dim_coord(self.grid_x_inc, 1) - weights = self.weights.reshape(new_shape) - result = regrid(src, weights, self.grid) - # NOTE: set the grid of self.src to make '_expected_cube' work ... - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_positive, (0, 1)) - # ... given that, we expect exactly the same 'normal' result. - data = np.array( - [ - 0, - self._weighted_mean([3]), - self._weighted_mean([7, 8]), - self._weighted_mean([9, 10, 11]), - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[True, False], [False, False]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_aligned_src_x_mask(self): - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_positive, (0, 1)) - self.src.data[([1, 2, 2], [3, 0, 2])] = ma.masked - self.grid.add_dim_coord(self.grid_y_inc, 0) - self.grid.add_dim_coord(self.grid_x_inc, 1) - result = regrid(self.src, self.weights, self.grid) - data = np.array( - [ - 0, - self._weighted_mean([3]), - self._weighted_mean([7]), - self._weighted_mean([10]), - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[True, False], [False, False]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_aligned_src_x_zero_weights(self): - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_positive, (0, 1)) - self.grid.add_dim_coord(self.grid_y_inc, 0) - self.grid.add_dim_coord(self.grid_x_inc, 1) - self.weights[:, 2] = 0 - self.weights[1, :] = 0 - result = regrid(self.src, self.weights, self.grid) - data = np.array([0, 0, 0, self._weighted_mean([9, 10])]).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[True, True], [True, False]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_aligned_tgt_dec(self): - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_positive, (0, 1)) - self.grid.add_dim_coord(self.grid_y_dec, 0) - self.grid.add_dim_coord(self.grid_x_dec, 1) - result = regrid(self.src, self.weights, self.grid) - data = np.array( - [ - self._weighted_mean([10, 11, 12]), - self._weighted_mean([8, 9]), - self._weighted_mean([3, 4]), - 0, - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[False, False], [False, True]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_misaligned_src_x_negative(self): - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_negative, (0, 1)) - self.grid.add_dim_coord(self.grid_y_inc, 0) - self.grid.add_dim_coord(self.grid_x_inc, 1) - result = regrid(self.src, self.weights, self.grid) - data = np.array( - [ - self._weighted_mean([1, 2]), - self._weighted_mean([3, 4]), - self._weighted_mean([5, 6, 7, 8]), - self._weighted_mean([9, 10, 11]), - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[False, False], [False, False]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_misaligned_src_x_negative_mask(self): - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_negative, (0, 1)) - self.src.data[([0, 0, 1, 1, 2, 2], [1, 3, 1, 3, 1, 3])] = ma.masked - self.grid.add_dim_coord(self.grid_y_inc, 0) - self.grid.add_dim_coord(self.grid_x_inc, 1) - result = regrid(self.src, self.weights, self.grid) - data = np.array( - [ - self._weighted_mean([1]), - self._weighted_mean([3]), - self._weighted_mean([5, 7]), - self._weighted_mean([9, 11]), - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[False, False], [False, False]]) - self.assertArrayEqual(result.data.mask, mask) - - def test_misaligned_tgt_dec(self): - self.src.add_aux_coord(self.src_y, (0, 1)) - self.src.add_aux_coord(self.src_x_negative, (0, 1)) - self.grid.add_dim_coord(self.grid_y_dec, 0) - self.grid.add_dim_coord(self.grid_x_dec, 1) - result = regrid(self.src, self.weights, self.grid) - data = np.array( - [ - self._weighted_mean([10, 11, 12]), - self._weighted_mean([6, 7, 8, 9]), - self._weighted_mean([4]), - self._weighted_mean([2, 3]), - ] - ).reshape(2, 2) - expected = self._expected_cube(data) - self.assertEqual(result, expected) - mask = np.array([[False, False], [False, False]]) - self.assertArrayEqual(result.data.mask, mask) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/representation/__init__.py b/lib/iris/tests/unit/experimental/representation/__init__.py deleted file mode 100644 index c856263a5c..0000000000 --- a/lib/iris/tests/unit/experimental/representation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.representation` package.""" diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py deleted file mode 100644 index 8dc3cd7849..0000000000 --- a/lib/iris/tests/unit/experimental/representation/test_CubeListRepresentation.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.cube.CubeRepresentation` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from html import escape - -from iris.cube import CubeList -from iris.experimental.representation import CubeListRepresentation -import iris.tests.stock as stock - - -@tests.skip_data -class Test__instantiation(tests.IrisTest): - def setUp(self): - self.cubes = CubeList([stock.simple_3d()]) - self.representer = CubeListRepresentation(self.cubes) - - def test_ids(self): - self.assertEqual(id(self.cubes), self.representer.cubelist_id) - - -@tests.skip_data -class Test_make_content(tests.IrisTest): - def setUp(self): - self.cubes = CubeList([stock.simple_3d(), stock.lat_lon_cube()]) - self.cubes[0].rename("name & ") - self.representer = CubeListRepresentation(self.cubes) - self.content = self.representer.make_content() - - def test_repr_len(self): - self.assertEqual(len(self.cubes), len(self.content)) - - def test_summary_lines(self): - names = [c.name() for c in self.cubes] - for name, content in zip(names, self.content): - name = escape(name) - self.assertIn(name, content) - - def test__cube_name_summary_consistency(self): - # Just check the first cube in the CubeList. - single_cube_html = self.content[0] - # Get a "prettified" cube name, as it should be in the cubelist repr. - cube_name = self.cubes[0].name() - pretty_cube_name = cube_name.strip().replace("_", " ").title() - pretty_escaped_name = escape(pretty_cube_name) - self.assertIn(pretty_escaped_name, single_cube_html) - - -@tests.skip_data -class Test_repr_html(tests.IrisTest): - def setUp(self): - self.cubes = CubeList([stock.simple_3d(), stock.lat_lon_cube()]) - self.representer = CubeListRepresentation(self.cubes) - - def test_html_length(self): - html = self.representer.repr_html() - n_html_elems = html.count(" tag per cube. - self.assertEqual(len(self.cubes), n_html_elems) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py b/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py deleted file mode 100644 index e6b1425110..0000000000 --- a/lib/iris/tests/unit/experimental/representation/test_CubeRepresentation.py +++ /dev/null @@ -1,447 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.cube.CubeRepresentation` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from html import escape - -import numpy as np - -from iris.coords import AncillaryVariable, CellMeasure, CellMethod -from iris.cube import Cube -from iris.experimental.representation import CubeRepresentation -import iris.tests.stock as stock -from iris.tests.stock.mesh import sample_mesh - - -@tests.skip_data -class Test__instantiation(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_3d() - self.representer = CubeRepresentation(self.cube) - - def test_cube_attributes(self): - self.assertEqual(id(self.cube), self.representer.cube_id) - self.assertMultiLineEqual(str(self.cube), self.representer.cube_str) - - def test__heading_contents(self): - content = set(self.representer.sections_data.values()) - self.assertEqual(len(content), 1) - self.assertIsNone(list(content)[0]) - - -@tests.skip_data -class Test__get_dim_names(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_4d() - self.dim_names = [c.name() for c in self.cube.coords(dim_coords=True)] - self.representer = CubeRepresentation(self.cube) - - def test_basic(self): - result_names = self.representer._get_dim_names() - self.assertEqual(result_names, self.dim_names) - - def test_one_anonymous_dim(self): - self.cube.remove_coord("time") - expected_names = ["--"] - expected_names.extend(self.dim_names[1:]) - result_names = self.representer._get_dim_names() - self.assertEqual(result_names, expected_names) - - def test_anonymous_dims(self): - target_dims = [1, 3] - # Replicate this here as we're about to modify it. - expected_names = [c.name() for c in self.cube.coords(dim_coords=True)] - for dim in target_dims: - (this_dim_coord,) = self.cube.coords( - contains_dimension=dim, dim_coords=True - ) - self.cube.remove_coord(this_dim_coord) - expected_names[dim] = "--" - result_names = self.representer._get_dim_names() - self.assertEqual(result_names, expected_names) - - -@tests.skip_data -class Test__summary_content(tests.IrisTest): - def setUp(self): - self.cube = stock.lat_lon_cube() - # Check we're not tripped up by names containing spaces. - self.cube.rename("Electron density (&)") - self.cube.units = "1e11 e/m^3" - self.representer = CubeRepresentation(self.cube) - - def test_name(self): - # Check the cube name is being set and formatted correctly. - expected = escape(self.cube.name().replace("_", " ").title()) - result = self.representer.name - self.assertEqual(expected, result) - - def test_names(self): - # Check the dimension names used as column headings are split out and - # formatted correctly. - expected_coord_names = [ - c.name().replace("_", " ") - for c in self.cube.coords(dim_coords=True) - ] - result_coord_names = self.representer.names[1:] - for result in result_coord_names: - self.assertIn(result, expected_coord_names) - - def test_units(self): - # Check the units is being set correctly. - expected = self.cube.units - result = self.representer.units - self.assertEqual(expected, result) - - def test_shapes(self): - # Check cube dim lengths are split out correctly from the - # summary string. - expected = self.cube.shape - result = self.representer.shapes - self.assertEqual(expected, result) - - def test_ndims(self): - expected = self.cube.ndim - result = self.representer.ndims - self.assertEqual(expected, result) - - -@tests.skip_data -class Test__get_bits(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_4d() - cmth = CellMethod("mean", "time", "6hr") - self.cube.add_cell_method(cmth) - cms = CellMeasure([0, 1, 2, 3, 4, 5], long_name="foo") - self.cube.add_cell_measure(cms, 0) - avr = AncillaryVariable([0, 1, 2, 3, 4, 5], long_name="bar") - self.cube.add_ancillary_variable(avr, 0) - scms = CellMeasure([0], long_name="baz") - self.cube.add_cell_measure(scms) - self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) - - def test_population(self): - nonmesh_values = [ - value - for key, value in self.representer.sections_data.items() - if "Mesh" not in key - ] - for v in nonmesh_values: - self.assertIsNotNone(v) - - def test_headings__dimcoords(self): - contents = self.representer.sections_data["Dimension coordinates:"] - content_str = ",".join(content for content in contents) - dim_coords = [c.name() for c in self.cube.dim_coords] - for coord in dim_coords: - self.assertIn(coord, content_str) - - def test_headings__auxcoords(self): - contents = self.representer.sections_data["Auxiliary coordinates:"] - content_str = ",".join(content for content in contents) - aux_coords = [ - c.name() for c in self.cube.aux_coords if c.shape != (1,) - ] - for coord in aux_coords: - self.assertIn(coord, content_str) - - def test_headings__derivedcoords(self): - contents = self.representer.sections_data["Derived coordinates:"] - content_str = ",".join(content for content in contents) - derived_coords = [c.name() for c in self.cube.derived_coords] - for coord in derived_coords: - self.assertIn(coord, content_str) - - def test_headings__cellmeasures(self): - contents = self.representer.sections_data["Cell measures:"] - content_str = ",".join(content for content in contents) - cell_measures = [ - c.name() for c in self.cube.cell_measures() if c.shape != (1,) - ] - for coord in cell_measures: - self.assertIn(coord, content_str) - - def test_headings__ancillaryvars(self): - contents = self.representer.sections_data["Ancillary variables:"] - content_str = ",".join(content for content in contents) - ancillary_variables = [ - c.name() for c in self.cube.ancillary_variables() - ] - for coord in ancillary_variables: - self.assertIn(coord, content_str) - - def test_headings__scalarcellmeasures(self): - contents = self.representer.sections_data["Scalar cell measures:"] - content_str = ",".join(content for content in contents) - scalar_cell_measures = [ - c.name() for c in self.cube.cell_measures() if c.shape == (1,) - ] - for coord in scalar_cell_measures: - self.assertIn(coord, content_str) - - def test_headings__scalarcoords(self): - contents = self.representer.sections_data["Scalar coordinates:"] - content_str = ",".join(content for content in contents) - scalar_coords = [ - c.name() for c in self.cube.coords() if c.shape == (1,) - ] - for coord in scalar_coords: - self.assertIn(coord, content_str) - - def test_headings__attributes(self): - contents = self.representer.sections_data["Attributes:"] - content_str = ",".join(content for content in contents) - for attr_name, attr_value in self.cube.attributes.items(): - self.assertIn(attr_name, content_str) - self.assertIn(attr_value, content_str) - - def test_headings__cellmethods(self): - contents = self.representer.sections_data["Cell methods:"] - content_str = ",".join(content for content in contents) - for method in self.cube.cell_methods: - name = method.method - value = str(method)[len(name + ": ") :] - self.assertIn(name, content_str) - self.assertIn(value, content_str) - - -@tests.skip_data -class Test__make_header(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_3d() - self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) - self.header_emts = self.representer._make_header().split("\n") - - def test_name_and_units(self): - # Check the correct name and units are being written into the top-left - # table cell. - # This is found in the first cell after the `

wnSwtK0u=;AMxWG`#44T6^h#yo`FE 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 literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/iris-security-actions.png b/docs/src/developers_guide/assets/iris-security-actions.png new file mode 100644 index 0000000000000000000000000000000000000000..7cbe3a7dc24b76d02fefb77aeaffb7abb5130c43 GIT binary patch literal 40999 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 E9|dvzG5`Po literal 0 HcmV?d00001 diff --git a/docs/src/developers_guide/assets/iris-settings.png b/docs/src/developers_guide/assets/iris-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..70714235c27d7f31d26213726b29c9e36526e4a6 GIT binary patch literal 79459 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

nBUZQKtiEcI2Y1zNdI*f|*yx%G~XpL2h~ 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%0Agsn0B&Gd+-+goTaG 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

tags. - result_cols = self.header_emts[1:-1] - expected = self.cube.ndim + 1 - self.assertEqual(len(result_cols), expected) - - def test_row_headings(self): - # Get only the dimension heading cells and not the headings column. - dim_coord_names = [c.name() for c in self.cube.coords(dim_coords=True)] - dim_col_headings = self.header_emts[2:-1] - for coord_name, col_heading in zip(dim_coord_names, dim_col_headings): - self.assertIn(coord_name, col_heading) - - -@tests.skip_data -class Test__make_shapes_row(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_3d() - self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) - self.result = self.representer._make_shapes_row().split("\n") - - def test_row_title(self): - title_cell = self.result[1] - self.assertIn("Shape", title_cell) - - def test_shapes(self): - expected_shapes = self.cube.shape - result_shapes = self.result[2:-1] - for expected, result in zip(expected_shapes, result_shapes): - self.assertIn(str(expected), result) - - -@tests.skip_data -class Test__make_row(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_3d() - cm = CellMethod("mean", "time", "6hr") - self.cube.add_cell_method(cm) - self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) - - def test__title_row(self): - title = "Wibble:" - row = self.representer._make_row(title) - # A cell for the title, an empty cell for each cube dimension, plus row - # opening and closing tags. - expected_len = self.cube.ndim + 3 - self.assertEqual(len(row), expected_len) - # Check for specific content. - row_str = "\n".join(element for element in row) - self.assertIn(title.strip(":"), row_str) - expected_html_class = "iris-title" - self.assertIn(expected_html_class, row_str) - - def test__inclusion_row(self): - # An inclusion row has x/- to indicate whether a coordinate describes - # a dimension. - title = "time" - body = ["x", "-", "-", "-"] - row = self.representer._make_row(title, body) - # A cell for the title, a cell for each cube dimension, plus row - # opening and closing tags. - expected_len = len(body) + 3 - self.assertEqual(len(row), expected_len) - # Check for specific content. - row_str = "\n".join(element for element in row) - self.assertIn(title, row_str) - self.assertIn("x", row_str) - self.assertIn("-", row_str) - expected_html_class_1 = "iris-word-cell" - expected_html_class_2 = "iris-inclusion-cell" - self.assertIn(expected_html_class_1, row_str) - self.assertIn(expected_html_class_2, row_str) - # We do not expect a colspan to be set. - self.assertNotIn("colspan", row_str) - - def test__attribute_row(self): - # An attribute row does not contain inclusion indicators. - title = "source" - body = "Iris test case" - colspan = 5 - row = self.representer._make_row(title, body, colspan) - # We only expect two cells here: the row title cell and one other cell - # that spans a number of columns. We also need to open and close the - # tr html element, giving 4 bits making up the row. - self.assertEqual(len(row), 4) - # Check for specific content. - row_str = "\n".join(element for element in row) - self.assertIn(title, row_str) - self.assertIn(body, row_str) - # We expect a colspan to be set. - colspan_str = 'colspan="{}"'.format(colspan) - self.assertIn(colspan_str, row_str) - - -@tests.skip_data -class Test__make_content(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_3d() - self.representer = CubeRepresentation(self.cube) - self.representer._get_bits(self.representer._get_lines()) - self.result = self.representer._make_content() - - # Also provide an ultra-simple mesh cube, with only meshcoords. - mesh = sample_mesh() - meshco_x, meshco_y = mesh.to_MeshCoords("face") - mesh_cube = Cube(np.zeros(meshco_x.shape)) - mesh_cube.add_aux_coord(meshco_x, (0,)) - mesh_cube.add_aux_coord(meshco_y, (0,)) - self.mesh_cube = mesh_cube - self.mesh_representer = CubeRepresentation(self.mesh_cube) - self.mesh_representer._get_bits(self.mesh_representer._get_lines()) - self.mesh_result = self.mesh_representer._make_content() - - def test_included(self): - included = "Dimension coordinates" - self.assertIn(included, self.result) - dim_coord_names = [c.name() for c in self.cube.dim_coords] - for coord_name in dim_coord_names: - self.assertIn(coord_name, self.result) - - def test_not_included(self): - # `stock.simple_3d()` only contains the `Dimension coordinates` attr. - not_included = list(self.representer.sections_data.keys()) - not_included.pop(not_included.index("Dimension coordinates:")) - for heading in not_included: - self.assertNotIn(heading, self.result) - - def test_mesh_included(self): - # self.mesh_cube contains a `Mesh coordinates` section. - self.assertIn( - '', - self.mesh_result, - ) - # and a `Mesh:` section. - self.assertIn( - '', self.mesh_result - ) - mesh_coord_names = [ - c.name() for c in self.mesh_cube.coords(mesh_coords=True) - ] - for coord_name in mesh_coord_names: - self.assertIn(coord_name, self.result) - - def test_mesh_not_included(self): - # self.mesh_cube _only_ contains a `Mesh coordinates` section. - not_included = list(self.representer.sections_data.keys()) - not_included.pop(not_included.index("Mesh coordinates:")) - for heading in not_included: - self.assertNotIn(heading, self.result) - - def test_mesh_result(self): - # A plain snapshot of a simple meshcube case. - self.assertString(self.mesh_result) - - -class Test__make_content__string_attrs(tests.IrisTest): - # Check how we handle "multi-line" string attributes. - # NOTE: before the adoption of iris._representation.CubeSummary, these - # used to appear as extra items in sections_data, identifiable by - # their not containing a ":", and which required to be combined into a - # single cell. - # This case no longer occurs. For now, just snapshot some current - # 'correct' behaviours, for change security and any future refactoring. - - @staticmethod - def _cube_stringattribute_html(name, attr): - cube = Cube([0]) - cube.attributes[name] = attr - representer = CubeRepresentation(cube) - representer._get_bits(representer._get_lines()) - result = representer._make_content() - return result - - def test_simple_string_attribute(self): - html = self._cube_stringattribute_html( - "single-string", "single string" - ) - self.assertString(html) - - def test_long_string_attribute(self): - attr = "long string.. " * 20 - html = self._cube_stringattribute_html("long-string", attr) - self.assertString(html) - - def test_embedded_newlines_string_attribute(self): - attr = "string\nwith\nnewlines" - html = self._cube_stringattribute_html("newlines-string", attr) - self.assertString(html) - - def test_multi_string_attribute(self): - attr = ["vector", "of", "strings"] - html = self._cube_stringattribute_html("multi-string", attr) - self.assertString(html) - - -@tests.skip_data -class Test_repr_html(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_3d() - representer = CubeRepresentation(self.cube) - self.result = representer.repr_html() - - def test_contents_added(self): - included = "Dimension coordinates" - self.assertIn(included, self.result) - not_included = "Auxiliary coordinates" - self.assertNotIn(not_included, self.result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/stratify/__init__.py b/lib/iris/tests/unit/experimental/stratify/__init__.py deleted file mode 100644 index 7218455e76..0000000000 --- a/lib/iris/tests/unit/experimental/stratify/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.stratify` package.""" diff --git a/lib/iris/tests/unit/experimental/stratify/test_relevel.py b/lib/iris/tests/unit/experimental/stratify/test_relevel.py deleted file mode 100644 index 6958fa9a2f..0000000000 --- a/lib/iris/tests/unit/experimental/stratify/test_relevel.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.experimental.stratify.relevel` function. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from functools import partial - -import numpy as np -from numpy.testing import assert_array_equal - -from iris.coords import AuxCoord, DimCoord -import iris.tests.stock as stock - -try: - import stratify - - from iris.experimental.stratify import relevel -except ImportError: - stratify = None - - -@tests.skip_stratify -class Test(tests.IrisTest): - def setUp(self): - cube = stock.simple_3d()[:, :1, :1] - #: The data from which to get the levels. - self.src_levels = cube.copy() - #: The data to interpolate. - self.cube = cube.copy() - self.cube.rename("foobar") - self.cube *= 10 - self.coord = self.src_levels.coord("wibble") - self.axes = (self.coord, self.coord.name(), None, 0) - - def test_broadcast_fail_src_levels(self): - emsg = "Cannot broadcast the cube and src_levels" - data = np.arange(60).reshape(3, 4, 5) - with self.assertRaisesRegex(ValueError, emsg): - relevel(self.cube, AuxCoord(data), [1, 2, 3]) - - def test_broadcast_fail_tgt_levels(self): - emsg = "Cannot broadcast the cube and tgt_levels" - data = np.arange(60).reshape(3, 4, 5) - with self.assertRaisesRegex(ValueError, emsg): - relevel(self.cube, self.coord, data) - - def test_standard_input(self): - for axis in self.axes: - result = relevel( - self.cube, self.src_levels, [-1, 0, 5.5], axis=axis - ) - assert_array_equal( - result.data.flatten(), np.array([np.nan, 0, 55]) - ) - expected = DimCoord([-1, 0, 5.5], units=1, long_name="thingness") - self.assertEqual(expected, result.coord("thingness")) - - def test_non_monotonic(self): - for axis in self.axes: - result = relevel(self.cube, self.src_levels, [2, 3, 2], axis=axis) - assert_array_equal( - result.data.flatten(), np.array([20, 30, np.nan]) - ) - expected = AuxCoord([2, 3, 2], units=1, long_name="thingness") - self.assertEqual(result.coord("thingness"), expected) - - def test_static_level(self): - for axis in self.axes: - result = relevel(self.cube, self.src_levels, [2, 2], axis=axis) - assert_array_equal(result.data.flatten(), np.array([20, 20])) - - def test_coord_input(self): - source = AuxCoord(self.src_levels.data) - metadata = self.src_levels.metadata._asdict() - metadata["coord_system"] = None - metadata["climatological"] = None - source.metadata = metadata - - for axis in self.axes: - result = relevel(self.cube, source, [0, 12, 13], axis=axis) - self.assertEqual(result.shape, (3, 1, 1)) - assert_array_equal(result.data.flatten(), [0, 120, np.nan]) - - def test_custom_interpolator(self): - interpolator = partial(stratify.interpolate, interpolation="nearest") - - for axis in self.axes: - result = relevel( - self.cube, - self.src_levels, - [-1, 0, 6.5], - axis=axis, - interpolator=interpolator, - ) - assert_array_equal( - result.data.flatten(), np.array([np.nan, 0, 120]) - ) - - def test_multi_dim_target_levels(self): - interpolator = partial( - stratify.interpolate, - interpolation="linear", - extrapolation="linear", - ) - - for axis in self.axes: - result = relevel( - self.cube, - self.src_levels, - self.src_levels.data, - axis=axis, - interpolator=interpolator, - ) - assert_array_equal(result.data.flatten(), np.array([0, 120])) - self.assertCML(result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/ugrid/__init__.py b/lib/iris/tests/unit/experimental/ugrid/__init__.py deleted file mode 100644 index 7f55678f06..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/__init__.py b/lib/iris/tests/unit/experimental/ugrid/cf/__init__.py deleted file mode 100644 index 2e70f2cd5d..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/cf/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.cf` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py deleted file mode 100644 index bdf1d5e03b..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridAuxiliaryCoordinateVariable.py +++ /dev/null @@ -1,238 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridAuxiliaryCoordinateVariable` class. - -todo: fold these tests into cf tests when experimental.ugrid is folded into - standard behaviour. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.experimental.ugrid.cf import ( - CFUGridAuxiliaryCoordinateVariable, - logger, -) -from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( - netcdf_ugrid_variable, -) - - -def named_variable(name): - # Don't need to worry about dimensions or dtype for these tests. - return netcdf_ugrid_variable(name, "", int) - - -class TestIdentify(tests.IrisTest): - def setUp(self): - self.cf_identities = [ - "node_coordinates", - "edge_coordinates", - "face_coordinates", - "volume_coordinates", - ] - - def test_cf_identities(self): - subject_name = "ref_subject" - ref_subject = named_variable(subject_name) - vars_common = { - subject_name: ref_subject, - "ref_not_subject": named_variable("ref_not_subject"), - } - # ONLY expecting ref_subject, excluding ref_not_subject. - expected = { - subject_name: CFUGridAuxiliaryCoordinateVariable( - subject_name, ref_subject - ) - } - - for identity in self.cf_identities: - ref_source = named_variable("ref_source") - setattr(ref_source, identity, subject_name) - vars_all = dict({"ref_source": ref_source}, **vars_common) - result = CFUGridAuxiliaryCoordinateVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_duplicate_refs(self): - subject_name = "ref_subject" - ref_subject = named_variable(subject_name) - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for var in ref_source_vars.values(): - setattr(var, self.cf_identities[0], subject_name) - vars_all = dict( - { - subject_name: ref_subject, - "ref_not_subject": named_variable("ref_not_subject"), - }, - **ref_source_vars, - ) - - # ONLY expecting ref_subject, excluding ref_not_subject. - expected = { - subject_name: CFUGridAuxiliaryCoordinateVariable( - subject_name, ref_subject - ) - } - result = CFUGridAuxiliaryCoordinateVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_two_coords(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, self.cf_identities[ix], subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # Not expecting ref_not_subject. - expected = { - name: CFUGridAuxiliaryCoordinateVariable(name, var) - for name, var in ref_subject_vars.items() - } - result = CFUGridAuxiliaryCoordinateVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_two_part_ref(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - ref_source = named_variable("ref_source") - setattr(ref_source, self.cf_identities[0], " ".join(subject_names)) - vars_all = { - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - **ref_subject_vars, - } - - expected = { - name: CFUGridAuxiliaryCoordinateVariable(name, var) - for name, var in ref_subject_vars.items() - } - result = CFUGridAuxiliaryCoordinateVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_string_type_ignored(self): - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr(ref_source, self.cf_identities[0], subject_name) - vars_all = { - subject_name: netcdf_ugrid_variable(subject_name, "", np.bytes_), - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - result = CFUGridAuxiliaryCoordinateVariable.identify(vars_all) - self.assertDictEqual({}, result) - - def test_ignore(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, self.cf_identities[0], subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # ONLY expect the subject variable that hasn't been ignored. - expected_name = subject_names[0] - expected = { - expected_name: CFUGridAuxiliaryCoordinateVariable( - expected_name, ref_subject_vars[expected_name] - ) - } - result = CFUGridAuxiliaryCoordinateVariable.identify( - vars_all, ignore=subject_names[1] - ) - self.assertDictEqual(expected, result) - - def test_target(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - source_names = ("ref_source_1", "ref_source_2") - ref_source_vars = {name: named_variable(name) for name in source_names} - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, self.cf_identities[0], subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # ONLY expect the variable referenced by the named ref_source_var. - expected_name = subject_names[0] - expected = { - expected_name: CFUGridAuxiliaryCoordinateVariable( - expected_name, ref_subject_vars[expected_name] - ) - } - result = CFUGridAuxiliaryCoordinateVariable.identify( - vars_all, target=source_names[0] - ) - self.assertDictEqual(expected, result) - - def test_warn(self): - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr(ref_source, self.cf_identities[0], subject_name) - vars_all = { - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - # The warn kwarg and expected corresponding log level. - warn_and_level = {True: "WARNING", False: "DEBUG"} - - # Missing warning. - log_regex = rf"Missing CF-netCDF auxiliary coordinate variable {subject_name}.*" - for warn, level in warn_and_level.items(): - with self.assertLogs(logger, level=level, msg_regex=log_regex): - result = CFUGridAuxiliaryCoordinateVariable.identify( - vars_all, warn=warn - ) - self.assertDictEqual({}, result) - - # String variable warning. - log_regex = r".*is a CF-netCDF label variable.*" - for warn, level in warn_and_level.items(): - with self.assertLogs(logger, level=level, msg_regex=log_regex): - vars_all[subject_name] = netcdf_ugrid_variable( - subject_name, "", np.bytes_ - ) - result = CFUGridAuxiliaryCoordinateVariable.identify( - vars_all, warn=warn - ) - self.assertDictEqual({}, result) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py deleted file mode 100644 index 7d461b324a..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridConnectivityVariable.py +++ /dev/null @@ -1,224 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridConnectivityVariable` class. - -todo: fold these tests into cf tests when experimental.ugrid is folded into - standard behaviour. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.experimental.ugrid.cf import CFUGridConnectivityVariable, logger -from iris.experimental.ugrid.mesh import Connectivity -from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( - netcdf_ugrid_variable, -) - - -def named_variable(name): - # Don't need to worry about dimensions or dtype for these tests. - return netcdf_ugrid_variable(name, "", int) - - -class TestIdentify(tests.IrisTest): - def test_cf_identities(self): - subject_name = "ref_subject" - ref_subject = named_variable(subject_name) - vars_common = { - subject_name: ref_subject, - "ref_not_subject": named_variable("ref_not_subject"), - } - # ONLY expecting ref_subject, excluding ref_not_subject. - expected = { - subject_name: CFUGridConnectivityVariable( - subject_name, ref_subject - ) - } - - for identity in Connectivity.UGRID_CF_ROLES: - ref_source = named_variable("ref_source") - setattr(ref_source, identity, subject_name) - vars_all = dict({"ref_source": ref_source}, **vars_common) - result = CFUGridConnectivityVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_duplicate_refs(self): - subject_name = "ref_subject" - ref_subject = named_variable(subject_name) - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for var in ref_source_vars.values(): - setattr(var, Connectivity.UGRID_CF_ROLES[0], subject_name) - vars_all = dict( - { - subject_name: ref_subject, - "ref_not_subject": named_variable("ref_not_subject"), - }, - **ref_source_vars, - ) - - # ONLY expecting ref_subject, excluding ref_not_subject. - expected = { - subject_name: CFUGridConnectivityVariable( - subject_name, ref_subject - ) - } - result = CFUGridConnectivityVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_two_cf_roles(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, Connectivity.UGRID_CF_ROLES[ix], subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # Not expecting ref_not_subject. - expected = { - name: CFUGridConnectivityVariable(name, var) - for name, var in ref_subject_vars.items() - } - result = CFUGridConnectivityVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_two_part_ref_ignored(self): - # Not expected to handle more than one variable for a connectivity - # cf role - invalid UGRID. - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr( - ref_source, Connectivity.UGRID_CF_ROLES[0], subject_name + " foo" - ) - vars_all = { - subject_name: named_variable(subject_name), - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - result = CFUGridConnectivityVariable.identify(vars_all) - self.assertDictEqual({}, result) - - def test_string_type_ignored(self): - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr(ref_source, Connectivity.UGRID_CF_ROLES[0], subject_name) - vars_all = { - subject_name: netcdf_ugrid_variable(subject_name, "", np.bytes_), - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - result = CFUGridConnectivityVariable.identify(vars_all) - self.assertDictEqual({}, result) - - def test_ignore(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, Connectivity.UGRID_CF_ROLES[0], subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # ONLY expect the subject variable that hasn't been ignored. - expected_name = subject_names[0] - expected = { - expected_name: CFUGridConnectivityVariable( - expected_name, ref_subject_vars[expected_name] - ) - } - result = CFUGridConnectivityVariable.identify( - vars_all, ignore=subject_names[1] - ) - self.assertDictEqual(expected, result) - - def test_target(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - source_names = ("ref_source_1", "ref_source_2") - ref_source_vars = {name: named_variable(name) for name in source_names} - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, Connectivity.UGRID_CF_ROLES[0], subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # ONLY expect the variable referenced by the named ref_source_var. - expected_name = subject_names[0] - expected = { - expected_name: CFUGridConnectivityVariable( - expected_name, ref_subject_vars[expected_name] - ) - } - result = CFUGridConnectivityVariable.identify( - vars_all, target=source_names[0] - ) - self.assertDictEqual(expected, result) - - def test_warn(self): - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr(ref_source, Connectivity.UGRID_CF_ROLES[0], subject_name) - vars_all = { - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - # The warn kwarg and expected corresponding log level. - warn_and_level = {True: "WARNING", False: "DEBUG"} - - # Missing warning. - log_regex = rf"Missing CF-UGRID connectivity variable {subject_name}.*" - for warn, level in warn_and_level.items(): - with self.assertLogs(logger, level=level, msg_regex=log_regex): - result = CFUGridConnectivityVariable.identify( - vars_all, warn=warn - ) - self.assertDictEqual({}, result) - - # String variable warning. - log_regex = r".*is a CF-netCDF label variable.*" - for warn, level in warn_and_level.items(): - with self.assertLogs(logger, level=level, msg_regex=log_regex): - vars_all[subject_name] = netcdf_ugrid_variable( - subject_name, "", np.bytes_ - ) - result = CFUGridConnectivityVariable.identify( - vars_all, warn=warn - ) - self.assertDictEqual({}, result) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridGroup.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridGroup.py deleted file mode 100644 index a3a0e665bb..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridGroup.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridGroup` class. - -todo: fold these tests into cf tests when experimental.ugrid is folded into - standard behaviour. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest.mock import MagicMock - -from iris.experimental.ugrid.cf import ( - CFUGridAuxiliaryCoordinateVariable, - CFUGridConnectivityVariable, - CFUGridGroup, - CFUGridMeshVariable, -) -from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable - - -class Tests(tests.IrisTest): - def setUp(self): - self.cf_group = CFUGridGroup() - - def test_inherited(self): - coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") - self.cf_group[coord_var.cf_name] = coord_var - self.assertEqual( - coord_var, self.cf_group.coordinates[coord_var.cf_name] - ) - - def test_connectivities(self): - conn_var = MagicMock( - spec=CFUGridConnectivityVariable, cf_name="conn_var" - ) - self.cf_group[conn_var.cf_name] = conn_var - self.assertEqual( - conn_var, self.cf_group.connectivities[conn_var.cf_name] - ) - - def test_ugrid_coords(self): - coord_var = MagicMock( - spec=CFUGridAuxiliaryCoordinateVariable, cf_name="coord_var" - ) - self.cf_group[coord_var.cf_name] = coord_var - self.assertEqual( - coord_var, self.cf_group.ugrid_coords[coord_var.cf_name] - ) - - def test_meshes(self): - mesh_var = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var") - self.cf_group[mesh_var.cf_name] = mesh_var - self.assertEqual(mesh_var, self.cf_group.meshes[mesh_var.cf_name]) - - def test_non_data_names(self): - data_var = MagicMock(spec=CFDataVariable, cf_name="data_var") - coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") - conn_var = MagicMock( - spec=CFUGridConnectivityVariable, cf_name="conn_var" - ) - ugrid_coord_var = MagicMock( - spec=CFUGridAuxiliaryCoordinateVariable, cf_name="ugrid_coord_var" - ) - mesh_var = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var") - mesh_var2 = MagicMock(spec=CFUGridMeshVariable, cf_name="mesh_var2") - duplicate_name_var = MagicMock( - spec=CFUGridMeshVariable, cf_name="coord_var" - ) - - for var in ( - data_var, - coord_var, - conn_var, - ugrid_coord_var, - mesh_var, - mesh_var2, - duplicate_name_var, - ): - self.cf_group[var.cf_name] = var - - expected_names = [ - var.cf_name - for var in ( - coord_var, - conn_var, - ugrid_coord_var, - mesh_var, - mesh_var2, - ) - ] - expected = set(expected_names) - self.assertEqual(expected, self.cf_group.non_data_variable_names) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py deleted file mode 100644 index 08915f7cff..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridMeshVariable.py +++ /dev/null @@ -1,263 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridMeshVariable` class. - -todo: fold these tests into cf tests when experimental.ugrid is folded into - standard behaviour. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.experimental.ugrid.cf import CFUGridMeshVariable, logger -from iris.tests.unit.experimental.ugrid.cf.test_CFUGridReader import ( - netcdf_ugrid_variable, -) - - -def named_variable(name): - # Don't need to worry about dimensions or dtype for these tests. - return netcdf_ugrid_variable(name, "", int) - - -class TestIdentify(tests.IrisTest): - def setUp(self): - self.cf_identity = "mesh" - - def test_cf_role(self): - # Test that mesh variables can be identified by having `cf_role="mesh_topology"`. - match_name = "match" - match = named_variable(match_name) - setattr(match, "cf_role", "mesh_topology") - - not_match_name = f"not_{match_name}" - not_match = named_variable(not_match_name) - setattr(not_match, "cf_role", "foo") - - vars_all = {match_name: match, not_match_name: not_match} - - # ONLY expecting match, excluding not_match. - expected = {match_name: CFUGridMeshVariable(match_name, match)} - result = CFUGridMeshVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_cf_identity(self): - # Test that mesh variables can be identified by being another variable's - # `mesh` attribute. - subject_name = "ref_subject" - ref_subject = named_variable(subject_name) - ref_source = named_variable("ref_source") - setattr(ref_source, self.cf_identity, subject_name) - vars_all = { - subject_name: ref_subject, - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - # ONLY expecting ref_subject, excluding ref_not_subject. - expected = { - subject_name: CFUGridMeshVariable(subject_name, ref_subject) - } - result = CFUGridMeshVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_cf_role_and_identity(self): - # Test that identification can successfully handle a combination of - # mesh variables having `cf_role="mesh_topology"` AND being referenced as - # another variable's `mesh` attribute. - role_match_name = "match" - role_match = named_variable(role_match_name) - setattr(role_match, "cf_role", "mesh_topology") - ref_source_1 = named_variable("ref_source_1") - setattr(ref_source_1, self.cf_identity, role_match_name) - - subject_name = "ref_subject" - ref_subject = named_variable(subject_name) - ref_source_2 = named_variable("ref_source_2") - setattr(ref_source_2, self.cf_identity, subject_name) - - vars_all = { - role_match_name: role_match, - subject_name: ref_subject, - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source_1": ref_source_1, - "ref_source_2": ref_source_2, - } - - # Expecting role_match and ref_subject but excluding other variables. - expected = { - role_match_name: CFUGridMeshVariable(role_match_name, role_match), - subject_name: CFUGridMeshVariable(subject_name, ref_subject), - } - result = CFUGridMeshVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_duplicate_refs(self): - subject_name = "ref_subject" - ref_subject = named_variable(subject_name) - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for var in ref_source_vars.values(): - setattr(var, self.cf_identity, subject_name) - vars_all = dict( - { - subject_name: ref_subject, - "ref_not_subject": named_variable("ref_not_subject"), - }, - **ref_source_vars, - ) - - # ONLY expecting ref_subject, excluding ref_not_subject. - expected = { - subject_name: CFUGridMeshVariable(subject_name, ref_subject) - } - result = CFUGridMeshVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_two_refs(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, self.cf_identity, subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # Not expecting ref_not_subject. - expected = { - name: CFUGridMeshVariable(name, var) - for name, var in ref_subject_vars.items() - } - result = CFUGridMeshVariable.identify(vars_all) - self.assertDictEqual(expected, result) - - def test_two_part_ref_ignored(self): - # Not expected to handle more than one variable for a mesh - # cf role - invalid UGRID. - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr(ref_source, self.cf_identity, subject_name + " foo") - vars_all = { - subject_name: named_variable(subject_name), - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - result = CFUGridMeshVariable.identify(vars_all) - self.assertDictEqual({}, result) - - def test_string_type_ignored(self): - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr(ref_source, self.cf_identity, subject_name) - vars_all = { - subject_name: netcdf_ugrid_variable(subject_name, "", np.bytes_), - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - result = CFUGridMeshVariable.identify(vars_all) - self.assertDictEqual({}, result) - - def test_ignore(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - ref_source_vars = { - name: named_variable(name) - for name in ("ref_source_1", "ref_source_2") - } - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, self.cf_identity, subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # ONLY expect the subject variable that hasn't been ignored. - expected_name = subject_names[0] - expected = { - expected_name: CFUGridMeshVariable( - expected_name, ref_subject_vars[expected_name] - ) - } - result = CFUGridMeshVariable.identify( - vars_all, ignore=subject_names[1] - ) - self.assertDictEqual(expected, result) - - def test_target(self): - subject_names = ("ref_subject_1", "ref_subject_2") - ref_subject_vars = { - name: named_variable(name) for name in subject_names - } - - source_names = ("ref_source_1", "ref_source_2") - ref_source_vars = {name: named_variable(name) for name in source_names} - for ix, var in enumerate(ref_source_vars.values()): - setattr(var, self.cf_identity, subject_names[ix]) - vars_all = dict( - {"ref_not_subject": named_variable("ref_not_subject")}, - **ref_subject_vars, - **ref_source_vars, - ) - - # ONLY expect the variable referenced by the named ref_source_var. - expected_name = subject_names[0] - expected = { - expected_name: CFUGridMeshVariable( - expected_name, ref_subject_vars[expected_name] - ) - } - result = CFUGridMeshVariable.identify(vars_all, target=source_names[0]) - self.assertDictEqual(expected, result) - - def test_warn(self): - subject_name = "ref_subject" - ref_source = named_variable("ref_source") - setattr(ref_source, self.cf_identity, subject_name) - vars_all = { - "ref_not_subject": named_variable("ref_not_subject"), - "ref_source": ref_source, - } - - # The warn kwarg and expected corresponding log level. - warn_and_level = {True: "WARNING", False: "DEBUG"} - - # Missing warning. - log_regex = rf"Missing CF-UGRID mesh variable {subject_name}.*" - for warn, level in warn_and_level.items(): - with self.assertLogs(logger, level=level, msg_regex=log_regex): - result = CFUGridMeshVariable.identify(vars_all, warn=warn) - self.assertDictEqual({}, result) - - # String variable warning. - log_regex = r".*is a CF-netCDF label variable.*" - for warn, level in warn_and_level.items(): - with self.assertLogs(logger, level=level, msg_regex=log_regex): - vars_all[subject_name] = netcdf_ugrid_variable( - subject_name, "", np.bytes_ - ) - result = CFUGridMeshVariable.identify(vars_all, warn=warn) - self.assertDictEqual({}, result) diff --git a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py b/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py deleted file mode 100644 index d9de814b05..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/cf/test_CFUGridReader.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.cf.CFUGridGroup` class. - -todo: fold these tests into cf tests when experimental.ugrid is folded into - standard behaviour. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.experimental.ugrid.cf import ( - CFUGridAuxiliaryCoordinateVariable, - CFUGridConnectivityVariable, - CFUGridGroup, - CFUGridMeshVariable, - CFUGridReader, -) -from iris.fileformats.cf import CFCoordinateVariable, CFDataVariable -from iris.tests.unit.fileformats.cf.test_CFReader import netcdf_variable - - -def netcdf_ugrid_variable( - name, - dimensions, - dtype, - coordinates=None, -): - ncvar = netcdf_variable( - name=name, dimensions=dimensions, dtype=dtype, coordinates=coordinates - ) - - # Fill in all the extra UGRID attributes to prevent problems with getattr - # and Mock. Any attribute can be replaced in downstream setUp if present. - ugrid_attrs = ( - CFUGridAuxiliaryCoordinateVariable.cf_identities - + CFUGridConnectivityVariable.cf_identities - + [CFUGridMeshVariable.cf_identity] - ) - for attr in ugrid_attrs: - setattr(ncvar, attr, None) - - return ncvar - - -class Test_build_cf_groups(tests.IrisTest): - @classmethod - def setUpClass(cls): - # Replicating syntax from test_CFReader.Test_build_cf_groups__formula_terms. - cls.mesh = netcdf_ugrid_variable("mesh", "", int) - cls.node_x = netcdf_ugrid_variable("node_x", "node", float) - cls.node_y = netcdf_ugrid_variable("node_y", "node", float) - cls.face_x = netcdf_ugrid_variable("face_x", "face", float) - cls.face_y = netcdf_ugrid_variable("face_y", "face", float) - cls.face_nodes = netcdf_ugrid_variable( - "face_nodes", "face vertex", int - ) - cls.levels = netcdf_ugrid_variable("levels", "levels", int) - cls.data = netcdf_ugrid_variable( - "data", "levels face", float, coordinates="face_x face_y" - ) - - # Add necessary attributes for mesh recognition. - cls.mesh.cf_role = "mesh_topology" - cls.mesh.node_coordinates = "node_x node_y" - cls.mesh.face_coordinates = "face_x face_y" - cls.mesh.face_node_connectivity = "face_nodes" - cls.face_nodes.cf_role = "face_node_connectivity" - cls.data.mesh = "mesh" - - cls.variables = dict( - mesh=cls.mesh, - node_x=cls.node_x, - node_y=cls.node_y, - face_x=cls.face_x, - face_y=cls.face_y, - face_nodes=cls.face_nodes, - levels=cls.levels, - data=cls.data, - ) - ncattrs = mock.Mock(return_value=[]) - cls.dataset = mock.Mock( - file_format="NetCDF4", variables=cls.variables, ncattrs=ncattrs - ) - - def setUp(self): - # Restrict the CFUGridReader functionality to only performing - # translations and building first level cf-groups for variables. - self.patch("iris.experimental.ugrid.cf.CFUGridReader._reset") - self.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ) - cf_reader = CFUGridReader("dummy") - self.cf_group = cf_reader.cf_group - - def test_inherited(self): - for expected_var, collection in ( - [CFCoordinateVariable("levels", self.levels), "coordinates"], - [CFDataVariable("data", self.data), "data_variables"], - ): - expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, getattr(self.cf_group, collection)) - - def test_connectivities(self): - expected_var = CFUGridConnectivityVariable( - "face_nodes", self.face_nodes - ) - expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, self.cf_group.connectivities) - - def test_mesh(self): - expected_var = CFUGridMeshVariable("mesh", self.mesh) - expected = {expected_var.cf_name: expected_var} - self.assertDictEqual(expected, self.cf_group.meshes) - - def test_ugrid_coords(self): - names = [ - f"{loc}_{ax}" for loc in ("node", "face") for ax in ("x", "y") - ] - expected = { - name: CFUGridAuxiliaryCoordinateVariable(name, getattr(self, name)) - for name in names - } - self.assertDictEqual(expected, self.cf_group.ugrid_coords) - - def test_is_cf_ugrid_group(self): - self.assertIsInstance(self.cf_group, CFUGridGroup) diff --git a/lib/iris/tests/unit/experimental/ugrid/load/__init__.py b/lib/iris/tests/unit/experimental/ugrid/load/__init__.py deleted file mode 100644 index 36c9108dc2..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/load/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.load` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py b/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py deleted file mode 100644 index 1203633297..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_ParseUgridOnLoad.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.load.ParseUgridOnLoad` class. - -todo: remove this module when experimental.ugrid is folded into standard behaviour. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD, ParseUGridOnLoad - - -class TestClass(tests.IrisTest): - @classmethod - def setUpClass(cls): - cls.cls = ParseUGridOnLoad() - - def test_default(self): - self.assertFalse(self.cls) - - def test_context(self): - self.assertFalse(self.cls) - with self.cls.context(): - self.assertTrue(self.cls) - self.assertFalse(self.cls) - - -class TestConstant(tests.IrisTest): - @classmethod - def setUpClass(cls): - cls.constant = PARSE_UGRID_ON_LOAD - - def test_default(self): - self.assertFalse(self.constant) - - def test_context(self): - self.assertFalse(self.constant) - with self.constant.context(): - self.assertTrue(self.constant) - self.assertFalse(self.constant) diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py b/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py deleted file mode 100644 index 4de11d5610..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_load_mesh.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.experimental.ugrid.load.load_mesh` function. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD, load_mesh - - -class Tests(tests.IrisTest): - # All 'real' tests have been done for load_meshes(). Here we just check - # that load_mesh() works with load_meshes() correctly, using mocking. - def setUp(self): - self.load_meshes_mock = self.patch( - "iris.experimental.ugrid.load.load_meshes" - ) - # The expected return from load_meshes - a dict of files, each with - # a list of meshes. - self.load_meshes_mock.return_value = {"file": ["mesh"]} - - def test_calls_load_meshes(self): - args = [("file_1", "file_2"), "my_var_name"] - with PARSE_UGRID_ON_LOAD.context(): - _ = load_mesh(args) - self.assertTrue(self.load_meshes_mock.called_with(args)) - - def test_returns_mesh(self): - with PARSE_UGRID_ON_LOAD.context(): - mesh = load_mesh([]) - self.assertEqual(mesh, "mesh") - - def test_single_mesh(self): - # Override the load_meshes_mock return values to provoke errors. - def common(ret_val): - self.load_meshes_mock.return_value = ret_val - with self.assertRaisesRegex(ValueError, "Expecting 1 mesh.*"): - with PARSE_UGRID_ON_LOAD.context(): - _ = load_mesh([]) - - # Too many. - common({"file": ["mesh1", "mesh2"]}) - # Too few. - common({"file": []}) diff --git a/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py b/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py deleted file mode 100644 index 310e68248a..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/load/test_load_meshes.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.experimental.ugrid.load.load_meshes` function. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from pathlib import Path -from shutil import rmtree -import tempfile -from uuid import uuid4 - -from iris.experimental.ugrid.load import ( - PARSE_UGRID_ON_LOAD, - load_meshes, - logger, -) -from iris.tests.stock.netcdf import ncgen_from_cdl - - -def setUpModule(): - global TMP_DIR - TMP_DIR = Path(tempfile.mkdtemp()) - - -def tearDownModule(): - if TMP_DIR is not None: - rmtree(TMP_DIR) - - -def cdl_to_nc(cdl): - cdl_path = str(TMP_DIR / "tst.cdl") - nc_path = str(TMP_DIR / f"{uuid4()}.nc") - # Use ncgen to convert this into an actual (temporary) netCDF file. - ncgen_from_cdl(cdl_str=cdl, cdl_path=cdl_path, nc_path=nc_path) - return nc_path - - -class TestsBasic(tests.IrisTest): - def setUp(self): - self.ref_cdl = """ - netcdf mesh_test { - dimensions: - node = 3 ; - face = 1 ; - vertex = 3 ; - levels = 2 ; - variables: - int mesh ; - mesh:cf_role = "mesh_topology" ; - mesh:topology_dimension = 2 ; - mesh:node_coordinates = "node_x node_y" ; - mesh:face_node_connectivity = "face_nodes" ; - float node_x(node) ; - node_x:standard_name = "longitude" ; - float node_y(node) ; - node_y:standard_name = "latitude" ; - int face_nodes(face, vertex) ; - face_nodes:cf_role = "face_node_connectivity" ; - face_nodes:start_index = 0 ; - int levels(levels) ; - float node_data(levels, node) ; - node_data:coordinates = "node_x node_y" ; - node_data:location = "node" ; - node_data:mesh = "mesh" ; - data: - mesh = 0; - node_x = 0., 2., 1.; - node_y = 0., 0., 1.; - face_nodes = 0, 1, 2; - levels = 1, 2; - node_data = 0., 0., 0.; - } - """ - self.nc_path = cdl_to_nc(self.ref_cdl) - - def add_second_mesh(self): - second_name = "mesh2" - cdl_extra = f""" - int {second_name} ; - {second_name}:cf_role = "mesh_topology" ; - {second_name}:topology_dimension = 2 ; - {second_name}:node_coordinates = "node_x node_y" ; - {second_name}:face_coordinates = "face_x face_y" ; - {second_name}:face_node_connectivity = "face_nodes" ; - """ - vars_string = "variables:" - vars_start = self.ref_cdl.index(vars_string) + len(vars_string) - new_cdl = ( - self.ref_cdl[:vars_start] + cdl_extra + self.ref_cdl[vars_start:] - ) - return new_cdl, second_name - - def test_with_data(self): - nc_path = cdl_to_nc(self.ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) - - files = list(meshes.keys()) - self.assertEqual(1, len(files)) - file_meshes = meshes[files[0]] - self.assertEqual(1, len(file_meshes)) - mesh = file_meshes[0] - self.assertEqual("mesh", mesh.var_name) - - def test_no_data(self): - cdl_lines = self.ref_cdl.split("\n") - cdl_lines = filter( - lambda line: ':mesh = "mesh"' not in line, cdl_lines - ) - ref_cdl = "\n".join(cdl_lines) - - nc_path = cdl_to_nc(ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) - - files = list(meshes.keys()) - self.assertEqual(1, len(files)) - file_meshes = meshes[files[0]] - self.assertEqual(1, len(file_meshes)) - mesh = file_meshes[0] - self.assertEqual("mesh", mesh.var_name) - - def test_no_mesh(self): - cdl_lines = self.ref_cdl.split("\n") - cdl_lines = filter( - lambda line: all( - [s not in line for s in (':mesh = "mesh"', "mesh_topology")] - ), - cdl_lines, - ) - ref_cdl = "\n".join(cdl_lines) - - nc_path = cdl_to_nc(ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) - - self.assertDictEqual({}, meshes) - - def test_multi_files(self): - files_count = 3 - nc_paths = [cdl_to_nc(self.ref_cdl) for _ in range(files_count)] - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_paths) - self.assertEqual(files_count, len(meshes)) - - def test_multi_meshes(self): - ref_cdl, second_name = self.add_second_mesh() - nc_path = cdl_to_nc(ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_path) - - files = list(meshes.keys()) - self.assertEqual(1, len(files)) - file_meshes = meshes[files[0]] - self.assertEqual(2, len(file_meshes)) - mesh_names = [mesh.var_name for mesh in file_meshes] - self.assertIn("mesh", mesh_names) - self.assertIn(second_name, mesh_names) - - def test_var_name(self): - second_cdl, second_name = self.add_second_mesh() - cdls = [self.ref_cdl, second_cdl] - nc_paths = [cdl_to_nc(cdl) for cdl in cdls] - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes(nc_paths, second_name) - - files = list(meshes.keys()) - self.assertEqual(1, len(files)) - file_meshes = meshes[files[0]] - self.assertEqual(1, len(file_meshes)) - self.assertEqual(second_name, file_meshes[0].var_name) - - def test_no_parsing(self): - nc_path = cdl_to_nc(self.ref_cdl) - with self.assertRaisesRegex( - ValueError, ".*Must be True to enable mesh loading." - ): - _ = load_meshes(nc_path) - - def test_invalid_scheme(self): - with self.assertRaisesRegex( - ValueError, "Iris cannot handle the URI scheme:.*" - ): - with PARSE_UGRID_ON_LOAD.context(): - _ = load_meshes("foo://bar") - - @tests.skip_data - def test_non_nc(self): - log_regex = r"Ignoring non-NetCDF file:.*" - with self.assertLogs(logger, level="INFO", msg_regex=log_regex): - with PARSE_UGRID_ON_LOAD.context(): - meshes = load_meshes( - tests.get_data_path(["PP", "simple_pp", "global.pp"]) - ) - self.assertDictEqual({}, meshes) - - -class TestsHttp(tests.IrisTest): - # Tests of HTTP (OpenDAP) loading need mocking since we can't have tests - # that rely on 3rd party servers. - def setUp(self): - self.format_agent_mock = self.patch( - "iris.fileformats.FORMAT_AGENT.get_spec" - ) - - def test_http(self): - url = "http://foo" - with PARSE_UGRID_ON_LOAD.context(): - _ = load_meshes(url) - self.format_agent_mock.assert_called_with(url, None) - - def test_mixed_sources(self): - url = "http://foo" - file = TMP_DIR / f"{uuid4()}.nc" - file.touch() - glob = f"{TMP_DIR}/*.nc" - - with PARSE_UGRID_ON_LOAD.context(): - _ = load_meshes([url, glob]) - file_uris = [ - call[0][0] for call in self.format_agent_mock.call_args_list - ] - for source in (url, Path(file).name): - self.assertIn(source, file_uris) diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/__init__.py b/lib/iris/tests/unit/experimental/ugrid/mesh/__init__.py deleted file mode 100644 index 4ce979d845..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.mesh` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Connectivity.py b/lib/iris/tests/unit/experimental/ugrid/mesh/test_Connectivity.py deleted file mode 100644 index 9a81c79d44..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Connectivity.py +++ /dev/null @@ -1,368 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.experimental.ugrid.mesh.Connectivity` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from xml.dom import minidom - -import numpy as np -from numpy import ma - -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.experimental.ugrid.mesh import Connectivity - - -class TestStandard(tests.IrisTest): - def setUp(self): - # Crete an instance, with non-default arguments to allow testing of - # correct property setting. - self.kwargs = { - "indices": np.linspace(1, 12, 12, dtype=int).reshape((4, -1)), - "cf_role": "face_node_connectivity", - "long_name": "my_face_nodes", - "var_name": "face_nodes", - "attributes": {"notes": "this is a test"}, - "start_index": 1, - "location_axis": 1, - } - self.connectivity = Connectivity(**self.kwargs) - - def test_cf_role(self): - self.assertEqual(self.kwargs["cf_role"], self.connectivity.cf_role) - - def test_location(self): - expected = self.kwargs["cf_role"].split("_")[0] - self.assertEqual(expected, self.connectivity.location) - - def test_connected(self): - expected = self.kwargs["cf_role"].split("_")[1] - self.assertEqual(expected, self.connectivity.connected) - - def test_start_index(self): - self.assertEqual( - self.kwargs["start_index"], self.connectivity.start_index - ) - - def test_location_axis(self): - self.assertEqual( - self.kwargs["location_axis"], self.connectivity.location_axis - ) - - def test_indices(self): - self.assertArrayEqual( - self.kwargs["indices"], self.connectivity.indices - ) - - def test_read_only(self): - attributes = ("indices", "cf_role", "start_index", "location_axis") - for attribute in attributes: - self.assertRaisesRegex( - AttributeError, - "can't set attribute", - setattr, - self.connectivity, - attribute, - 1, - ) - - def test_transpose(self): - expected_dim = 1 - self.kwargs["location_axis"] - expected_indices = self.kwargs["indices"].transpose() - new_connectivity = self.connectivity.transpose() - self.assertEqual(expected_dim, new_connectivity.location_axis) - self.assertArrayEqual(expected_indices, new_connectivity.indices) - - def test_lazy_indices(self): - self.assertTrue(is_lazy_data(self.connectivity.lazy_indices())) - - def test_core_indices(self): - self.assertArrayEqual( - self.kwargs["indices"], self.connectivity.core_indices() - ) - - def test_has_lazy_indices(self): - self.assertFalse(self.connectivity.has_lazy_indices()) - - def test_lazy_location_lengths(self): - self.assertTrue( - is_lazy_data(self.connectivity.lazy_location_lengths()) - ) - - def test_location_lengths(self): - expected = [4, 4, 4] - self.assertArrayEqual(expected, self.connectivity.location_lengths()) - - def test___str__(self): - expected = "\n".join( - [ - "Connectivity : my_face_nodes / (unknown)", - " data: [", - " [ 1, 2, 3],", - " [ 4, 5, 6],", - " [ 7, 8, 9],", - " [10, 11, 12]]", - " shape: (4, 3)", - " dtype: int64", - " long_name: 'my_face_nodes'", - " var_name: 'face_nodes'", - " attributes:", - " notes 'this is a test'", - " cf_role: 'face_node_connectivity'", - " start_index: 1", - " location_axis: 1", - ] - ) - self.assertEqual(expected, self.connectivity.__str__()) - - def test___repr__(self): - expected = "" - self.assertEqual(expected, self.connectivity.__repr__()) - - def test_xml_element(self): - doc = minidom.Document() - connectivity_element = self.connectivity.xml_element(doc) - self.assertEqual(connectivity_element.tagName, "connectivity") - for attribute in ("cf_role", "start_index", "location_axis"): - self.assertIn(attribute, connectivity_element.attributes) - - def test___eq__(self): - equivalent_kwargs = self.kwargs - equivalent_kwargs["indices"] = self.kwargs["indices"].transpose() - equivalent_kwargs["location_axis"] = 1 - self.kwargs["location_axis"] - equivalent = Connectivity(**equivalent_kwargs) - self.assertFalse( - np.array_equal(equivalent.indices, self.connectivity.indices) - ) - self.assertEqual(equivalent, self.connectivity) - - def test_different(self): - different_kwargs = self.kwargs - different_kwargs["indices"] = self.kwargs["indices"].transpose() - different = Connectivity(**different_kwargs) - self.assertNotEqual(different, self.connectivity) - - def test_no_cube_dims(self): - self.assertRaises(NotImplementedError, self.connectivity.cube_dims, 1) - - def test_shape(self): - self.assertEqual(self.kwargs["indices"].shape, self.connectivity.shape) - - def test_ndim(self): - self.assertEqual(self.kwargs["indices"].ndim, self.connectivity.ndim) - - def test___getitem_(self): - subset = self.connectivity[:, 0:1] - self.assertArrayEqual(self.kwargs["indices"][:, 0:1], subset.indices) - - def test_copy(self): - new_indices = np.linspace(11, 16, 6, dtype=int).reshape((3, -1)) - copy_connectivity = self.connectivity.copy(new_indices) - self.assertArrayEqual(new_indices, copy_connectivity.indices) - - def test_indices_by_location(self): - expected = self.kwargs["indices"].transpose() - self.assertArrayEqual( - expected, self.connectivity.indices_by_location() - ) - - def test_indices_by_location_input(self): - expected = as_lazy_data(self.kwargs["indices"].transpose()) - by_location = self.connectivity.indices_by_location( - self.connectivity.lazy_indices() - ) - self.assertArrayEqual(expected, by_location) - - -class TestAltIndices(tests.IrisTest): - def setUp(self): - mask = ([0, 0, 0, 0, 1] * 2) + [0, 0, 0, 1, 1] - data = np.linspace(1, 15, 15, dtype=int).reshape((-1, 5)) - self.masked_indices = ma.array(data=data, mask=mask) - self.lazy_indices = as_lazy_data(data) - - def common(self, indices): - connectivity = Connectivity( - indices=indices, cf_role="face_node_connectivity" - ) - self.assertArrayEqual(indices, connectivity.indices) - - def test_int32(self): - indices = np.linspace(1, 9, 9, dtype=np.int32).reshape((-1, 3)) - self.common(indices) - - def test_uint32(self): - indices = np.linspace(1, 9, 9, dtype=np.uint32).reshape((-1, 3)) - self.common(indices) - - def test_lazy(self): - self.common(self.lazy_indices) - - def test_masked(self): - self.common(self.masked_indices) - - def test_masked_lazy(self): - self.common(as_lazy_data(self.masked_indices)) - - def test_has_lazy_indices(self): - connectivity = Connectivity( - indices=self.lazy_indices, cf_role="face_node_connectivity" - ) - self.assertTrue(connectivity.has_lazy_indices()) - - -class TestValidations(tests.IrisTest): - def test_start_index(self): - kwargs = { - "indices": np.linspace(1, 9, 9, dtype=int).reshape((-1, 3)), - "cf_role": "face_node_connectivity", - "start_index": 2, - } - self.assertRaisesRegex( - ValueError, "Invalid start_index .", Connectivity, **kwargs - ) - - def test_location_axis(self): - kwargs = { - "indices": np.linspace(1, 9, 9, dtype=int).reshape((-1, 3)), - "cf_role": "face_node_connectivity", - "location_axis": 2, - } - self.assertRaisesRegex( - ValueError, "Invalid location_axis .", Connectivity, **kwargs - ) - - def test_cf_role(self): - kwargs = { - "indices": np.linspace(1, 9, 9, dtype=int).reshape((-1, 3)), - "cf_role": "error", - } - self.assertRaisesRegex( - ValueError, "Invalid cf_role .", Connectivity, **kwargs - ) - - def test_indices_int(self): - kwargs = { - "indices": np.linspace(1, 9, 9).reshape((-1, 3)), - "cf_role": "face_node_connectivity", - } - self.assertRaisesRegex( - ValueError, - "dtype must be numpy integer subtype", - Connectivity, - **kwargs, - ) - - def test_indices_start_index(self): - kwargs = { - "indices": np.linspace(-9, -1, 9, dtype=int).reshape((-1, 3)), - "cf_role": "face_node_connectivity", - } - self.assertRaisesRegex( - ValueError, " < start_index", Connectivity, **kwargs - ) - - def test_indices_dims_low(self): - kwargs = { - "indices": np.linspace(1, 9, 9, dtype=int), - "cf_role": "face_node_connectivity", - } - self.assertRaisesRegex( - ValueError, "Expected 2-dimensional shape,", Connectivity, **kwargs - ) - - def test_indices_dims_high(self): - kwargs = { - "indices": np.linspace(1, 12, 12, dtype=int).reshape((-1, 3, 2)), - "cf_role": "face_node_connectivity", - } - self.assertRaisesRegex( - ValueError, "Expected 2-dimensional shape,", Connectivity, **kwargs - ) - - def test_indices_locations_edge(self): - kwargs = { - "indices": np.linspace(1, 9, 9, dtype=int).reshape((-1, 3)), - "cf_role": "edge_node_connectivity", - } - self.assertRaisesRegex( - ValueError, - "Not all edges meet requirement: len=2", - Connectivity, - **kwargs, - ) - - def test_indices_locations_face(self): - kwargs = { - "indices": np.linspace(1, 6, 6, dtype=int).reshape((-1, 2)), - "cf_role": "face_node_connectivity", - } - self.assertRaisesRegex( - ValueError, - "Not all faces meet requirement: len>=3", - Connectivity, - **kwargs, - ) - - def test_indices_locations_volume_face(self): - kwargs = { - "indices": np.linspace(1, 9, 9, dtype=int).reshape((-1, 3)), - "cf_role": "volume_face_connectivity", - } - self.assertRaisesRegex( - ValueError, - "Not all volumes meet requirement: len>=4", - Connectivity, - **kwargs, - ) - - def test_indices_locations_volume_edge(self): - kwargs = { - "indices": np.linspace(1, 12, 12, dtype=int).reshape((-1, 3)), - "cf_role": "volume_edge_connectivity", - } - self.assertRaisesRegex( - ValueError, - "Not all volumes meet requirement: len>=6", - Connectivity, - **kwargs, - ) - - def test_indices_locations_alt_dim(self): - """The transposed equivalent of `test_indices_locations_volume_face`.""" - kwargs = { - "indices": np.linspace(1, 9, 9, dtype=int).reshape((3, -1)), - "cf_role": "volume_face_connectivity", - "location_axis": 1, - } - self.assertRaisesRegex( - ValueError, - "Not all volumes meet requirement: len>=4", - Connectivity, - **kwargs, - ) - - def test_indices_locations_masked(self): - mask = ([0, 0, 0] * 2) + [0, 0, 1] - data = np.linspace(1, 9, 9, dtype=int).reshape((3, -1)) - kwargs = { - "indices": ma.array(data=data, mask=mask), - "cf_role": "face_node_connectivity", - } - # Validation of individual location sizes (denoted by masks) only - # available through explicit call of Connectivity.validate_indices(). - connectivity = Connectivity(**kwargs) - self.assertRaisesRegex( - ValueError, - "Not all faces meet requirement: len>=3", - connectivity.validate_indices, - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py b/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py deleted file mode 100644 index f39f3706ee..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh.py +++ /dev/null @@ -1,1348 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`mesh` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.coords import AuxCoord -from iris.exceptions import ConnectivityNotFoundError, CoordinateNotFoundError -from iris.experimental.ugrid import mesh, metadata -from iris.experimental.ugrid.mesh import logger - - -class TestMeshCommon(tests.IrisTest): - @classmethod - def setUpClass(cls): - # A collection of minimal coords and connectivities describing an - # equilateral triangle. - cls.NODE_LON = AuxCoord( - [0, 2, 1], - standard_name="longitude", - long_name="long_name", - var_name="node_lon", - attributes={"test": 1}, - ) - cls.NODE_LAT = AuxCoord( - [0, 0, 1], standard_name="latitude", var_name="node_lat" - ) - cls.EDGE_LON = AuxCoord( - [1, 1.5, 0.5], standard_name="longitude", var_name="edge_lon" - ) - cls.EDGE_LAT = AuxCoord( - [0, 0.5, 0.5], standard_name="latitude", var_name="edge_lat" - ) - cls.FACE_LON = AuxCoord( - [0.5], standard_name="longitude", var_name="face_lon" - ) - cls.FACE_LAT = AuxCoord( - [0.5], standard_name="latitude", var_name="face_lat" - ) - - cls.EDGE_NODE = mesh.Connectivity( - [[0, 1], [1, 2], [2, 0]], - cf_role="edge_node_connectivity", - long_name="long_name", - var_name="var_name", - attributes={"test": 1}, - ) - cls.FACE_NODE = mesh.Connectivity( - [[0, 1, 2]], cf_role="face_node_connectivity" - ) - cls.FACE_EDGE = mesh.Connectivity( - [[0, 1, 2]], cf_role="face_edge_connectivity" - ) - # (Actually meaningless:) - cls.FACE_FACE = mesh.Connectivity( - [[0, 0, 0]], cf_role="face_face_connectivity" - ) - # (Actually meaningless:) - cls.EDGE_FACE = mesh.Connectivity( - [[0, 0], [0, 0], [0, 0]], cf_role="edge_face_connectivity" - ) - cls.BOUNDARY_NODE = mesh.Connectivity( - [[0, 1], [1, 2], [2, 0]], cf_role="boundary_node_connectivity" - ) - - -class TestProperties1D(TestMeshCommon): - # Tests that can re-use a single instance for greater efficiency. - @classmethod - def setUpClass(cls): - super().setUpClass() - # Mesh kwargs with topology_dimension=1 and all applicable - # arguments populated - this tests correct property setting. - cls.kwargs = { - "topology_dimension": 1, - "node_coords_and_axes": ((cls.NODE_LON, "x"), (cls.NODE_LAT, "y")), - "connectivities": [cls.EDGE_NODE], - "long_name": "my_topology_mesh", - "var_name": "mesh", - "attributes": {"notes": "this is a test"}, - "node_dimension": "NodeDim", - "edge_dimension": "EdgeDim", - "edge_coords_and_axes": ((cls.EDGE_LON, "x"), (cls.EDGE_LAT, "y")), - } - cls.mesh = mesh.Mesh(**cls.kwargs) - - def test__metadata_manager(self): - self.assertEqual( - self.mesh._metadata_manager.cls.__name__, - metadata.MeshMetadata.__name__, - ) - - def test___getstate__(self): - expected = ( - self.mesh._metadata_manager, - self.mesh._coord_manager, - self.mesh._connectivity_manager, - ) - self.assertEqual(expected, self.mesh.__getstate__()) - - def test___repr__(self): - expected = "" - self.assertEqual(expected, repr(self.mesh)) - - def test___str__(self): - expected = [ - "Mesh : 'my_topology_mesh'", - " topology_dimension: 1", - " node", - " node_dimension: 'NodeDim'", - " node coordinates", - " ", - " ", - " edge", - " edge_dimension: 'EdgeDim'", - ( - " edge_node_connectivity: " - "" - ), - " edge coordinates", - " ", - " ", - " long_name: 'my_topology_mesh'", - " var_name: 'mesh'", - " attributes:", - " notes 'this is a test'", - ] - self.assertEqual(expected, str(self.mesh).split("\n")) - - def test___eq__(self): - # The dimension names do not participate in equality. - equivalent_kwargs = self.kwargs.copy() - equivalent_kwargs["node_dimension"] = "something_else" - equivalent = mesh.Mesh(**equivalent_kwargs) - self.assertEqual(equivalent, self.mesh) - - def test_different(self): - different_kwargs = self.kwargs.copy() - different_kwargs["long_name"] = "new_name" - different = mesh.Mesh(**different_kwargs) - self.assertNotEqual(different, self.mesh) - - different_kwargs = self.kwargs.copy() - ncaa = self.kwargs["node_coords_and_axes"] - new_lat = ncaa[1][0].copy(points=ncaa[1][0].points + 1) - new_ncaa = (ncaa[0], (new_lat, "y")) - different_kwargs["node_coords_and_axes"] = new_ncaa - different = mesh.Mesh(**different_kwargs) - self.assertNotEqual(different, self.mesh) - - different_kwargs = self.kwargs.copy() - conns = self.kwargs["connectivities"] - new_conn = conns[0].copy(conns[0].indices + 1) - different_kwargs["connectivities"] = new_conn - different = mesh.Mesh(**different_kwargs) - self.assertNotEqual(different, self.mesh) - - def test_all_connectivities(self): - expected = mesh.Mesh1DConnectivities(self.EDGE_NODE) - self.assertEqual(expected, self.mesh.all_connectivities) - - def test_all_coords(self): - expected = mesh.Mesh1DCoords( - self.NODE_LON, self.NODE_LAT, self.EDGE_LON, self.EDGE_LAT - ) - self.assertEqual(expected, self.mesh.all_coords) - - def test_boundary_node(self): - with self.assertRaises(AttributeError): - _ = self.mesh.boundary_node_connectivity - - def test_cf_role(self): - self.assertEqual("mesh_topology", self.mesh.cf_role) - # Read only. - self.assertRaises(AttributeError, setattr, self.mesh.cf_role, "foo", 1) - - def test_connectivities(self): - # General results. Method intended for inheritance. - positive_kwargs = ( - {"item": self.EDGE_NODE}, - {"item": "long_name"}, - {"long_name": "long_name"}, - {"var_name": "var_name"}, - {"attributes": {"test": 1}}, - {"cf_role": "edge_node_connectivity"}, - ) - - fake_connectivity = tests.mock.Mock( - __class__=mesh.Connectivity, cf_role="fake" - ) - negative_kwargs = ( - {"item": fake_connectivity}, - {"item": "foo"}, - {"standard_name": "air_temperature"}, - {"long_name": "foo"}, - {"var_name": "foo"}, - {"attributes": {"test": 2}}, - {"cf_role": "foo"}, - ) - - func = self.mesh.connectivities - for kwargs in positive_kwargs: - self.assertEqual([self.EDGE_NODE], func(**kwargs)) - for kwargs in negative_kwargs: - self.assertEqual([], func(**kwargs)) - - def test_connectivities_elements(self): - # topology_dimension-specific results. Method intended to be overridden. - positive_kwargs = ( - {"contains_node": True}, - {"contains_edge": True}, - {"contains_node": True, "contains_edge": True}, - ) - negative_kwargs = ( - {"contains_node": False}, - {"contains_edge": False}, - {"contains_edge": True, "contains_node": False}, - {"contains_edge": False, "contains_node": False}, - ) - - func = self.mesh.connectivities - for kwargs in positive_kwargs: - self.assertEqual([self.EDGE_NODE], func(**kwargs)) - for kwargs in negative_kwargs: - self.assertEqual([], func(**kwargs)) - - log_regex = r".*filter for non-existent.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.assertEqual([], func(contains_face=True)) - - def test_coord(self): - # See Mesh.coords tests for thorough coverage of cases. - func = self.mesh.coord - exception = CoordinateNotFoundError - self.assertRaisesRegex( - exception, ".*but found 2", func, include_nodes=True - ) - self.assertRaisesRegex(exception, ".*but found none", func, axis="t") - - def test_coords(self): - # General results. Method intended for inheritance. - positive_kwargs = ( - {"item": self.NODE_LON}, - {"item": "longitude"}, - {"standard_name": "longitude"}, - {"long_name": "long_name"}, - {"var_name": "node_lon"}, - {"attributes": {"test": 1}}, - ) - - fake_coord = AuxCoord([0]) - negative_kwargs = ( - {"item": fake_coord}, - {"item": "foo"}, - {"standard_name": "air_temperature"}, - {"long_name": "foo"}, - {"var_name": "foo"}, - {"attributes": {"test": 2}}, - ) - - func = self.mesh.coords - for kwargs in positive_kwargs: - self.assertIn(self.NODE_LON, func(**kwargs)) - for kwargs in negative_kwargs: - self.assertNotIn(self.NODE_LON, func(**kwargs)) - - def test_coords_elements(self): - # topology_dimension-specific results. Method intended to be overridden. - all_expected = { - "node_x": self.NODE_LON, - "node_y": self.NODE_LAT, - "edge_x": self.EDGE_LON, - "edge_y": self.EDGE_LAT, - } - - kwargs_expected = ( - ({"axis": "x"}, ["node_x", "edge_x"]), - ({"axis": "y"}, ["node_y", "edge_y"]), - ({"include_nodes": True}, ["node_x", "node_y"]), - ({"include_edges": True}, ["edge_x", "edge_y"]), - ({"include_nodes": False}, ["edge_x", "edge_y"]), - ({"include_edges": False}, ["node_x", "node_y"]), - ( - {"include_nodes": True, "include_edges": True}, - ["node_x", "node_y", "edge_x", "edge_y"], - ), - ({"include_nodes": False, "include_edges": False}, []), - ( - {"include_nodes": False, "include_edges": True}, - ["edge_x", "edge_y"], - ), - ) - - func = self.mesh.coords - for kwargs, expected in kwargs_expected: - expected = [all_expected[k] for k in expected if k in all_expected] - self.assertEqual(expected, func(**kwargs)) - - log_regex = r".*filter non-existent.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.assertEqual([], func(include_faces=True)) - - def test_edge_dimension(self): - self.assertEqual( - self.kwargs["edge_dimension"], self.mesh.edge_dimension - ) - - def test_edge_coords(self): - expected = mesh.MeshEdgeCoords(self.EDGE_LON, self.EDGE_LAT) - self.assertEqual(expected, self.mesh.edge_coords) - - def test_edge_face(self): - with self.assertRaises(AttributeError): - _ = self.mesh.edge_face_connectivity - - def test_edge_node(self): - self.assertEqual(self.EDGE_NODE, self.mesh.edge_node_connectivity) - - def test_face_coords(self): - with self.assertRaises(AttributeError): - _ = self.mesh.face_coords - - def test_face_dimension(self): - self.assertIsNone(self.mesh.face_dimension) - - def test_face_edge(self): - with self.assertRaises(AttributeError): - _ = self.mesh.face_edge_connectivity - - def test_face_face(self): - with self.assertRaises(AttributeError): - _ = self.mesh.face_face_connectivity - - def test_face_node(self): - with self.assertRaises(AttributeError): - _ = self.mesh.face_node_connectivity - - def test_node_coords(self): - expected = mesh.MeshNodeCoords(self.NODE_LON, self.NODE_LAT) - self.assertEqual(expected, self.mesh.node_coords) - - def test_node_dimension(self): - self.assertEqual( - self.kwargs["node_dimension"], self.mesh.node_dimension - ) - - def test_topology_dimension(self): - self.assertEqual( - self.kwargs["topology_dimension"], self.mesh.topology_dimension - ) - # Read only. - self.assertRaises( - AttributeError, setattr, self.mesh.topology_dimension, "foo", 1 - ) - - -class TestProperties2D(TestProperties1D): - # Additional/specialised tests for topology_dimension=2. - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.kwargs["topology_dimension"] = 2 - cls.kwargs["connectivities"] = ( - cls.FACE_NODE, - cls.EDGE_NODE, - cls.FACE_EDGE, - cls.FACE_FACE, - cls.EDGE_FACE, - cls.BOUNDARY_NODE, - ) - cls.kwargs["face_dimension"] = "FaceDim" - cls.kwargs["face_coords_and_axes"] = ( - (cls.FACE_LON, "x"), - (cls.FACE_LAT, "y"), - ) - cls.mesh = mesh.Mesh(**cls.kwargs) - - def test___repr__(self): - expected = "" - self.assertEqual(expected, repr(self.mesh)) - - def test___str__(self): - expected = [ - "Mesh : 'my_topology_mesh'", - " topology_dimension: 2", - " node", - " node_dimension: 'NodeDim'", - " node coordinates", - " ", - " ", - " edge", - " edge_dimension: 'EdgeDim'", - ( - " edge_node_connectivity: " - "" - ), - " edge coordinates", - " ", - " ", - " face", - " face_dimension: 'FaceDim'", - ( - " face_node_connectivity: " - "" - ), - " face coordinates", - " ", - " ", - " optional connectivities", - ( - " face_face_connectivity: " - "" - ), - ( - " face_edge_connectivity: " - "" - ), - ( - " edge_face_connectivity: " - "" - ), - " long_name: 'my_topology_mesh'", - " var_name: 'mesh'", - " attributes:", - " notes 'this is a test'", - ] - self.assertEqual(expected, str(self.mesh).split("\n")) - - # Test some different options of the str() operation here. - def test___str__noedgecoords(self): - mesh_kwargs = self.kwargs.copy() - del mesh_kwargs["edge_coords_and_axes"] - alt_mesh = mesh.Mesh(**mesh_kwargs) - expected = [ - "Mesh : 'my_topology_mesh'", - " topology_dimension: 2", - " node", - " node_dimension: 'NodeDim'", - " node coordinates", - " ", - " ", - " edge", - " edge_dimension: 'EdgeDim'", - ( - " edge_node_connectivity: " - "" - ), - " face", - " face_dimension: 'FaceDim'", - ( - " face_node_connectivity: " - "" - ), - " face coordinates", - " ", - " ", - " optional connectivities", - ( - " face_face_connectivity: " - "" - ), - ( - " face_edge_connectivity: " - "" - ), - ( - " edge_face_connectivity: " - "" - ), - " long_name: 'my_topology_mesh'", - " var_name: 'mesh'", - " attributes:", - " notes 'this is a test'", - ] - self.assertEqual(expected, str(alt_mesh).split("\n")) - - def test_all_connectivities(self): - expected = mesh.Mesh2DConnectivities( - self.FACE_NODE, - self.EDGE_NODE, - self.FACE_EDGE, - self.FACE_FACE, - self.EDGE_FACE, - self.BOUNDARY_NODE, - ) - self.assertEqual(expected, self.mesh.all_connectivities) - - def test_all_coords(self): - expected = mesh.Mesh2DCoords( - self.NODE_LON, - self.NODE_LAT, - self.EDGE_LON, - self.EDGE_LAT, - self.FACE_LON, - self.FACE_LAT, - ) - self.assertEqual(expected, self.mesh.all_coords) - - def test_boundary_node(self): - self.assertEqual( - self.BOUNDARY_NODE, self.mesh.boundary_node_connectivity - ) - - def test_connectivity(self): - # See Mesh.connectivities tests for thorough coverage of cases. - # Can only test Mesh.connectivity for 2D since we need >1 connectivity. - func = self.mesh.connectivity - exception = ConnectivityNotFoundError - self.assertRaisesRegex( - exception, ".*but found 3", func, contains_node=True - ) - self.assertRaisesRegex( - exception, - ".*but found none", - func, - contains_node=False, - contains_edge=False, - contains_face=False, - ) - - def test_connectivities_elements(self): - kwargs_expected = ( - ( - {"contains_node": True}, - [self.EDGE_NODE, self.FACE_NODE, self.BOUNDARY_NODE], - ), - ( - {"contains_edge": True}, - [self.EDGE_NODE, self.FACE_EDGE, self.EDGE_FACE], - ), - ( - {"contains_face": True}, - [ - self.FACE_NODE, - self.FACE_EDGE, - self.FACE_FACE, - self.EDGE_FACE, - ], - ), - ( - {"contains_node": False}, - [self.FACE_EDGE, self.EDGE_FACE, self.FACE_FACE], - ), - ( - {"contains_edge": False}, - [self.FACE_NODE, self.BOUNDARY_NODE, self.FACE_FACE], - ), - ({"contains_face": False}, [self.EDGE_NODE, self.BOUNDARY_NODE]), - ( - {"contains_edge": True, "contains_face": True}, - [self.FACE_EDGE, self.EDGE_FACE], - ), - ( - {"contains_node": False, "contains_edge": False}, - [self.FACE_FACE], - ), - ( - {"contains_node": True, "contains_edge": False}, - [self.FACE_NODE, self.BOUNDARY_NODE], - ), - ( - { - "contains_node": False, - "contains_edge": False, - "contains_face": False, - }, - [], - ), - ) - func = self.mesh.connectivities - for kwargs, expected in kwargs_expected: - result = func(**kwargs) - self.assertEqual(len(expected), len(result)) - for item in expected: - self.assertIn(item, result) - - def test_coords_elements(self): - all_expected = { - "node_x": self.NODE_LON, - "node_y": self.NODE_LAT, - "edge_x": self.EDGE_LON, - "edge_y": self.EDGE_LAT, - "face_x": self.FACE_LON, - "face_y": self.FACE_LAT, - } - - kwargs_expected = ( - ({"axis": "x"}, ["node_x", "edge_x", "face_x"]), - ({"axis": "y"}, ["node_y", "edge_y", "face_y"]), - ({"include_nodes": True}, ["node_x", "node_y"]), - ({"include_edges": True}, ["edge_x", "edge_y"]), - ( - {"include_nodes": False}, - ["edge_x", "edge_y", "face_x", "face_y"], - ), - ( - {"include_edges": False}, - ["node_x", "node_y", "face_x", "face_y"], - ), - ( - {"include_faces": False}, - ["node_x", "node_y", "edge_x", "edge_y"], - ), - ( - {"include_faces": True, "include_edges": True}, - ["edge_x", "edge_y", "face_x", "face_y"], - ), - ( - {"include_faces": False, "include_edges": False}, - ["node_x", "node_y"], - ), - ( - {"include_faces": False, "include_edges": True}, - ["edge_x", "edge_y"], - ), - ) - - func = self.mesh.coords - for kwargs, expected in kwargs_expected: - expected = [all_expected[k] for k in expected if k in all_expected] - self.assertEqual(expected, func(**kwargs)) - - def test_edge_face(self): - self.assertEqual(self.EDGE_FACE, self.mesh.edge_face_connectivity) - - def test_face_coords(self): - expected = mesh.MeshFaceCoords(self.FACE_LON, self.FACE_LAT) - self.assertEqual(expected, self.mesh.face_coords) - - def test_face_dimension(self): - self.assertEqual( - self.kwargs["face_dimension"], self.mesh.face_dimension - ) - - def test_face_edge(self): - self.assertEqual(self.FACE_EDGE, self.mesh.face_edge_connectivity) - - def test_face_face(self): - self.assertEqual(self.FACE_FACE, self.mesh.face_face_connectivity) - - def test_face_node(self): - self.assertEqual(self.FACE_NODE, self.mesh.face_node_connectivity) - - -class Test__str__various(TestMeshCommon): - # Some extra testing for the str() operation : based on 1D meshes as simpler - def setUp(self): - # All the tests here want modified meshes, so use standard setUp to - # create afresh for each test, allowing them to modify it. - super().setUp() - # Mesh kwargs with topology_dimension=1 and all applicable - # arguments populated - this tests correct property setting. - self.kwargs = { - "topology_dimension": 1, - "node_coords_and_axes": ( - (self.NODE_LON, "x"), - (self.NODE_LAT, "y"), - ), - "connectivities": [self.EDGE_NODE], - "long_name": "my_topology_mesh", - "var_name": "mesh", - "attributes": {"notes": "this is a test"}, - "node_dimension": "NodeDim", - "edge_dimension": "EdgeDim", - "edge_coords_and_axes": ( - (self.EDGE_LON, "x"), - (self.EDGE_LAT, "y"), - ), - } - self.mesh = mesh.Mesh(**self.kwargs) - - def test___repr__basic(self): - expected = "" - self.assertEqual(expected, repr(self.mesh)) - - def test___repr__varname(self): - self.mesh.long_name = None - expected = "" - self.assertEqual(expected, repr(self.mesh)) - - def test___repr__noname(self): - self.mesh.long_name = None - self.mesh.var_name = None - expected = "" - self.assertRegex(repr(self.mesh), expected) - - def test___str__noattributes(self): - self.mesh.attributes = None - self.assertNotIn("attributes", str(self.mesh)) - - def test___str__emptyattributes(self): - self.mesh.attributes.clear() - self.assertNotIn("attributes", str(self.mesh)) - - def test__str__longstringattribute(self): - self.mesh.attributes["long_string"] = ( - "long_x_10_long_x_20_long_x_30_long_x_40_" - "long_x_50_long_x_60_long_x_70_long_x_80_" - ) - result = str(self.mesh) - # Note: initial single-quote, but no final one : this is correct ! - expected = ( - "'long_x_10_long_x_20_long_x_30_long_x_40_" - "long_x_50_long_x_60_long_x_70..." - ) - self.assertIn(expected + ":END", result + ":END") - - def test___str__units_stdname(self): - # These are usually missing, but they *can* be present. - mesh_kwargs = self.kwargs.copy() - mesh_kwargs["standard_name"] = "height" # Odd choice ! - mesh_kwargs["units"] = "m" - alt_mesh = mesh.Mesh(**mesh_kwargs) - result = str(alt_mesh) - # We expect these to appear at the end. - expected = "\n".join( - [ - " edge coordinates", - " ", - " ", - " standard_name: 'height'", - " long_name: 'my_topology_mesh'", - " var_name: 'mesh'", - " units: Unit('m')", - " attributes:", - " notes 'this is a test'", - ] - ) - self.assertTrue(result.endswith(expected)) - - -class TestOperations1D(TestMeshCommon): - # Tests that cannot re-use an existing Mesh instance, instead need a new - # one each time. - def setUp(self): - self.mesh = mesh.Mesh( - topology_dimension=1, - node_coords_and_axes=((self.NODE_LON, "x"), (self.NODE_LAT, "y")), - connectivities=self.EDGE_NODE, - ) - - @staticmethod - def new_connectivity(connectivity, new_len=False): - """Provide a new connectivity recognisably different from the original.""" - # NOTE: assumes non-transposed connectivity (location_axis=0). - if new_len: - shape = (connectivity.shape[0] + 1, connectivity.shape[1]) - else: - shape = connectivity.shape - return connectivity.copy(np.zeros(shape, dtype=int)) - - @staticmethod - def new_coord(coord, new_shape=False): - """Provide a new coordinate recognisably different from the original.""" - if new_shape: - shape = tuple([i + 1 for i in coord.shape]) - else: - shape = coord.shape - return coord.copy(np.zeros(shape)) - - def test___setstate__(self): - false_metadata_manager = "foo" - false_coord_manager = "bar" - false_connectivity_manager = "baz" - self.mesh.__setstate__( - ( - false_metadata_manager, - false_coord_manager, - false_connectivity_manager, - ) - ) - - self.assertEqual(false_metadata_manager, self.mesh._metadata_manager) - self.assertEqual(false_coord_manager, self.mesh._coord_manager) - self.assertEqual( - false_connectivity_manager, self.mesh._connectivity_manager - ) - - def test_add_connectivities(self): - # Cannot test ADD - 1D - nothing extra to add beyond minimum. - - for new_len in (False, True): - # REPLACE connectivities, first with one of the same length, then - # with one of different length. - edge_node = self.new_connectivity(self.EDGE_NODE, new_len) - self.mesh.add_connectivities(edge_node) - self.assertEqual( - mesh.Mesh1DConnectivities(edge_node), - self.mesh.all_connectivities, - ) - - def test_add_connectivities_duplicates(self): - edge_node_one = self.EDGE_NODE - edge_node_two = self.new_connectivity(self.EDGE_NODE) - self.mesh.add_connectivities(edge_node_one, edge_node_two) - self.assertEqual( - edge_node_two, - self.mesh.edge_node_connectivity, - ) - - def test_add_connectivities_invalid(self): - self.assertRaisesRegex( - TypeError, - "Expected Connectivity.*", - self.mesh.add_connectivities, - "foo", - ) - - face_node = self.FACE_NODE - log_regex = r"Not adding connectivity.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.mesh.add_connectivities(face_node) - - def test_add_coords(self): - # ADD coords. - edge_kwargs = {"edge_x": self.EDGE_LON, "edge_y": self.EDGE_LAT} - self.mesh.add_coords(**edge_kwargs) - self.assertEqual( - mesh.MeshEdgeCoords(**edge_kwargs), - self.mesh.edge_coords, - ) - - for new_shape in (False, True): - # REPLACE coords, first with ones of the same shape, then with ones - # of different shape. - node_kwargs = { - "node_x": self.new_coord(self.NODE_LON, new_shape), - "node_y": self.new_coord(self.NODE_LAT, new_shape), - } - edge_kwargs = { - "edge_x": self.new_coord(self.EDGE_LON, new_shape), - "edge_y": self.new_coord(self.EDGE_LAT, new_shape), - } - self.mesh.add_coords(**node_kwargs, **edge_kwargs) - self.assertEqual( - mesh.MeshNodeCoords(**node_kwargs), - self.mesh.node_coords, - ) - self.assertEqual( - mesh.MeshEdgeCoords(**edge_kwargs), - self.mesh.edge_coords, - ) - - def test_add_coords_face(self): - self.assertRaises( - TypeError, - self.mesh.add_coords, - face_x=self.FACE_LON, - face_y=self.FACE_LAT, - ) - - def test_add_coords_invalid(self): - func = self.mesh.add_coords - self.assertRaisesRegex( - TypeError, ".*requires to be an 'AuxCoord'.*", func, node_x="foo" - ) - self.assertRaisesRegex( - TypeError, ".*requires a x-axis like.*", func, node_x=self.NODE_LAT - ) - climatological = AuxCoord( - [0], - bounds=[-1, 1], - standard_name="longitude", - climatological=True, - units="Days since 1970", - ) - self.assertRaisesRegex( - TypeError, - ".*cannot be a climatological.*", - func, - node_x=climatological, - ) - wrong_shape = self.NODE_LON.copy([0]) - self.assertRaisesRegex( - ValueError, ".*requires to have shape.*", func, node_x=wrong_shape - ) - - def test_add_coords_single(self): - # ADD coord. - edge_x = self.EDGE_LON - expected = mesh.MeshEdgeCoords(edge_x=edge_x, edge_y=None) - self.mesh.add_coords(edge_x=edge_x) - self.assertEqual(expected, self.mesh.edge_coords) - - # REPLACE coords. - node_x = self.new_coord(self.NODE_LON) - edge_x = self.new_coord(self.EDGE_LON) - expected_nodes = mesh.MeshNodeCoords( - node_x=node_x, node_y=self.mesh.node_coords.node_y - ) - expected_edges = mesh.MeshEdgeCoords(edge_x=edge_x, edge_y=None) - self.mesh.add_coords(node_x=node_x, edge_x=edge_x) - self.assertEqual(expected_nodes, self.mesh.node_coords) - self.assertEqual(expected_edges, self.mesh.edge_coords) - - # Attempt to REPLACE coords with those of DIFFERENT SHAPE. - node_x = self.new_coord(self.NODE_LON, new_shape=True) - edge_x = self.new_coord(self.EDGE_LON, new_shape=True) - node_kwarg = {"node_x": node_x} - edge_kwarg = {"edge_x": edge_x} - both_kwargs = dict(**node_kwarg, **edge_kwarg) - for kwargs in (node_kwarg, edge_kwarg, both_kwargs): - self.assertRaisesRegex( - ValueError, - ".*requires to have shape.*", - self.mesh.add_coords, - **kwargs, - ) - - def test_add_coords_single_face(self): - self.assertRaises( - TypeError, self.mesh.add_coords, face_x=self.FACE_LON - ) - - def test_dimension_names(self): - # Test defaults. - default = mesh.Mesh1DNames("Mesh1d_node", "Mesh1d_edge") - self.assertEqual(default, self.mesh.dimension_names()) - - log_regex = r"Not setting face_dimension.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.mesh.dimension_names("foo", "bar", "baz") - self.assertEqual( - mesh.Mesh1DNames("foo", "bar"), - self.mesh.dimension_names(), - ) - - self.mesh.dimension_names_reset(True, True, True) - self.assertEqual(default, self.mesh.dimension_names()) - - # Single. - self.mesh.dimension_names(edge="foo") - self.assertEqual("foo", self.mesh.edge_dimension) - self.mesh.dimension_names_reset(edge=True) - self.assertEqual(default, self.mesh.dimension_names()) - - def test_edge_dimension_set(self): - self.mesh.edge_dimension = "foo" - self.assertEqual("foo", self.mesh.edge_dimension) - - def test_face_dimension_set(self): - log_regex = r"Not setting face_dimension.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.mesh.face_dimension = "foo" - self.assertIsNone(self.mesh.face_dimension) - - def test_node_dimension_set(self): - self.mesh.node_dimension = "foo" - self.assertEqual("foo", self.mesh.node_dimension) - - def test_remove_connectivities(self): - """ - Test that remove() mimics the connectivities() method correctly, - and prevents removal of mandatory connectivities. - - """ - positive_kwargs = ( - {"item": self.EDGE_NODE}, - {"item": "long_name"}, - {"long_name": "long_name"}, - {"var_name": "var_name"}, - {"attributes": {"test": 1}}, - {"cf_role": "edge_node_connectivity"}, - {"contains_node": True}, - {"contains_edge": True}, - {"contains_edge": True, "contains_node": True}, - ) - - fake_connectivity = tests.mock.Mock( - __class__=mesh.Connectivity, cf_role="fake" - ) - negative_kwargs = ( - {"item": fake_connectivity}, - {"item": "foo"}, - {"standard_name": "air_temperature"}, - {"long_name": "foo"}, - {"var_name": "foo"}, - {"attributes": {"test": 2}}, - {"cf_role": "foo"}, - {"contains_node": False}, - {"contains_edge": False}, - {"contains_edge": True, "contains_node": False}, - {"contains_edge": False, "contains_node": False}, - ) - - log_regex = r"Ignoring request to remove.*" - for kwargs in positive_kwargs: - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.mesh.remove_connectivities(**kwargs) - self.assertEqual(self.EDGE_NODE, self.mesh.edge_node_connectivity) - for kwargs in negative_kwargs: - with self.assertLogs(logger, level="DEBUG") as log: - # Check that the only debug log is the one we inserted. - logger.debug("foo", extra=dict(cls=None)) - self.mesh.remove_connectivities(**kwargs) - self.assertEqual(1, len(log.records)) - self.assertEqual(self.EDGE_NODE, self.mesh.edge_node_connectivity) - - def test_remove_coords(self): - # Test that remove() mimics the coords() method correctly, - # and prevents removal of mandatory coords. - positive_kwargs = ( - {"item": self.NODE_LON}, - {"item": "longitude"}, - {"standard_name": "longitude"}, - {"long_name": "long_name"}, - {"var_name": "node_lon"}, - {"attributes": {"test": 1}}, - ) - - fake_coord = AuxCoord([0]) - negative_kwargs = ( - {"item": fake_coord}, - {"item": "foo"}, - {"standard_name": "air_temperature"}, - {"long_name": "foo"}, - {"var_name": "foo"}, - {"attributes": {"test": 2}}, - ) - - log_regex = r"Ignoring request to remove.*" - for kwargs in positive_kwargs: - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.mesh.remove_coords(**kwargs) - self.assertEqual(self.NODE_LON, self.mesh.node_coords.node_x) - for kwargs in negative_kwargs: - with self.assertLogs(logger, level="DEBUG") as log: - # Check that the only debug log is the one we inserted. - logger.debug("foo", extra=dict(cls=None)) - self.mesh.remove_coords(**kwargs) - self.assertEqual(1, len(log.records)) - self.assertEqual(self.NODE_LON, self.mesh.node_coords.node_x) - - # Test removal of optional connectivity. - self.mesh.add_coords(edge_x=self.EDGE_LON) - # Attempt to remove a non-existent coord. - self.mesh.remove_coords(self.EDGE_LAT) - # Confirm that EDGE_LON is still there. - self.assertEqual(self.EDGE_LON, self.mesh.edge_coords.edge_x) - # Remove EDGE_LON and confirm success. - self.mesh.remove_coords(self.EDGE_LON) - self.assertEqual(None, self.mesh.edge_coords.edge_x) - - def test_to_MeshCoord(self): - location = "node" - axis = "x" - result = self.mesh.to_MeshCoord(location, axis) - self.assertIsInstance(result, mesh.MeshCoord) - self.assertEqual(location, result.location) - self.assertEqual(axis, result.axis) - - def test_to_MeshCoord_face(self): - location = "face" - axis = "x" - self.assertRaises( - CoordinateNotFoundError, self.mesh.to_MeshCoord, location, axis - ) - - def test_to_MeshCoords(self): - location = "node" - result = self.mesh.to_MeshCoords(location) - self.assertEqual(len(self.mesh.AXES), len(result)) - for ix, axis in enumerate(self.mesh.AXES): - coord = result[ix] - self.assertIsInstance(coord, mesh.MeshCoord) - self.assertEqual(location, coord.location) - self.assertEqual(axis, coord.axis) - - def test_to_MeshCoords_face(self): - location = "face" - self.assertRaises( - CoordinateNotFoundError, self.mesh.to_MeshCoords, location - ) - - -class TestOperations2D(TestOperations1D): - # Additional/specialised tests for topology_dimension=2. - def setUp(self): - self.mesh = mesh.Mesh( - topology_dimension=2, - node_coords_and_axes=((self.NODE_LON, "x"), (self.NODE_LAT, "y")), - connectivities=(self.FACE_NODE), - ) - - def test_add_connectivities(self): - # ADD connectivities. - kwargs = { - "edge_node": self.EDGE_NODE, - "face_edge": self.FACE_EDGE, - "face_face": self.FACE_FACE, - "edge_face": self.EDGE_FACE, - "boundary_node": self.BOUNDARY_NODE, - } - expected = mesh.Mesh2DConnectivities( - face_node=self.mesh.face_node_connectivity, **kwargs - ) - self.mesh.add_connectivities(*kwargs.values()) - self.assertEqual(expected, self.mesh.all_connectivities) - - # REPLACE connectivities. - kwargs["face_node"] = self.FACE_NODE - for new_len in (False, True): - # First replace with ones of same length, then with ones of - # different length. - kwargs = { - k: self.new_connectivity(v, new_len) for k, v in kwargs.items() - } - self.mesh.add_connectivities(*kwargs.values()) - self.assertEqual( - mesh.Mesh2DConnectivities(**kwargs), - self.mesh.all_connectivities, - ) - - def test_add_connectivities_inconsistent(self): - # ADD Connectivities. - self.mesh.add_connectivities(self.EDGE_NODE) - face_edge = self.new_connectivity(self.FACE_EDGE, new_len=True) - edge_face = self.new_connectivity(self.EDGE_FACE, new_len=True) - for args in ([face_edge], [edge_face], [face_edge, edge_face]): - self.assertRaisesRegex( - ValueError, - "inconsistent .* counts.", - self.mesh.add_connectivities, - *args, - ) - - # REPLACE Connectivities - self.mesh.add_connectivities(self.FACE_EDGE, self.EDGE_FACE) - for args in ([face_edge], [edge_face], [face_edge, edge_face]): - self.assertRaisesRegex( - ValueError, - "inconsistent .* counts.", - self.mesh.add_connectivities, - *args, - ) - - def test_add_connectivities_invalid(self): - fake_cf_role = tests.mock.Mock( - __class__=mesh.Connectivity, cf_role="foo" - ) - log_regex = r"Not adding connectivity.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - self.mesh.add_connectivities(fake_cf_role) - - def test_add_coords_face(self): - # ADD coords. - kwargs = {"face_x": self.FACE_LON, "face_y": self.FACE_LAT} - self.mesh.add_coords(**kwargs) - self.assertEqual( - mesh.MeshFaceCoords(**kwargs), - self.mesh.face_coords, - ) - - for new_shape in (False, True): - # REPLACE coords, first with ones of the same shape, then with ones - # of different shape. - kwargs = { - "face_x": self.new_coord(self.FACE_LON, new_shape), - "face_y": self.new_coord(self.FACE_LAT, new_shape), - } - self.mesh.add_coords(**kwargs) - self.assertEqual( - mesh.MeshFaceCoords(**kwargs), - self.mesh.face_coords, - ) - - def test_add_coords_single_face(self): - # ADD coord. - face_x = self.FACE_LON - expected = mesh.MeshFaceCoords(face_x=face_x, face_y=None) - self.mesh.add_coords(face_x=face_x) - self.assertEqual(expected, self.mesh.face_coords) - - # REPLACE coord. - face_x = self.new_coord(self.FACE_LON) - expected = mesh.MeshFaceCoords(face_x=face_x, face_y=None) - self.mesh.add_coords(face_x=face_x) - self.assertEqual(expected, self.mesh.face_coords) - - # Attempt to REPLACE coord with that of DIFFERENT SHAPE. - face_x = self.new_coord(self.FACE_LON, new_shape=True) - self.assertRaisesRegex( - ValueError, - ".*requires to have shape.*", - self.mesh.add_coords, - face_x=face_x, - ) - - def test_dimension_names(self): - # Test defaults. - default = mesh.Mesh2DNames("Mesh2d_node", "Mesh2d_edge", "Mesh2d_face") - self.assertEqual(default, self.mesh.dimension_names()) - - self.mesh.dimension_names("foo", "bar", "baz") - self.assertEqual( - mesh.Mesh2DNames("foo", "bar", "baz"), - self.mesh.dimension_names(), - ) - - self.mesh.dimension_names_reset(True, True, True) - self.assertEqual(default, self.mesh.dimension_names()) - - # Single. - self.mesh.dimension_names(face="foo") - self.assertEqual("foo", self.mesh.face_dimension) - self.mesh.dimension_names_reset(face=True) - self.assertEqual(default, self.mesh.dimension_names()) - - def test_face_dimension_set(self): - self.mesh.face_dimension = "foo" - self.assertEqual("foo", self.mesh.face_dimension) - - def test_remove_connectivities(self): - """Do what 1D test could not - test removal of optional connectivity.""" - - # Add an optional connectivity. - self.mesh.add_connectivities(self.FACE_FACE) - # Attempt to remove a non-existent connectivity. - self.mesh.remove_connectivities(self.EDGE_NODE) - # Confirm that FACE_FACE is still there. - self.assertEqual(self.FACE_FACE, self.mesh.face_face_connectivity) - # Remove FACE_FACE and confirm success. - self.mesh.remove_connectivities(contains_face=True) - self.assertEqual(None, self.mesh.face_face_connectivity) - - def test_remove_coords(self): - """Test the face argument.""" - super().test_remove_coords() - self.mesh.add_coords(face_x=self.FACE_LON) - self.assertEqual(self.FACE_LON, self.mesh.face_coords.face_x) - self.mesh.remove_coords(include_faces=True) - self.assertEqual(None, self.mesh.face_coords.face_x) - - def test_to_MeshCoord_face(self): - self.mesh.add_coords(face_x=self.FACE_LON) - location = "face" - axis = "x" - result = self.mesh.to_MeshCoord(location, axis) - self.assertIsInstance(result, mesh.MeshCoord) - self.assertEqual(location, result.location) - self.assertEqual(axis, result.axis) - - def test_to_MeshCoords_face(self): - self.mesh.add_coords(face_x=self.FACE_LON, face_y=self.FACE_LAT) - location = "face" - result = self.mesh.to_MeshCoords(location) - self.assertEqual(len(self.mesh.AXES), len(result)) - for ix, axis in enumerate(self.mesh.AXES): - coord = result[ix] - self.assertIsInstance(coord, mesh.MeshCoord) - self.assertEqual(location, coord.location) - self.assertEqual(axis, coord.axis) - - -class InitValidation(TestMeshCommon): - def test_invalid_topology(self): - kwargs = { - "topology_dimension": 0, - "node_coords_and_axes": ( - (self.NODE_LON, "x"), - (self.NODE_LAT, "y"), - ), - "connectivities": self.EDGE_NODE, - } - self.assertRaisesRegex( - ValueError, - "Expected 'topology_dimension'.*", - mesh.Mesh, - **kwargs, - ) - - def test_invalid_axes(self): - kwargs = { - "topology_dimension": 2, - "connectivities": self.FACE_NODE, - } - self.assertRaisesRegex( - ValueError, - "Invalid axis specified for node.*", - mesh.Mesh, - node_coords_and_axes=( - (self.NODE_LON, "foo"), - (self.NODE_LAT, "y"), - ), - **kwargs, - ) - kwargs["node_coords_and_axes"] = ( - (self.NODE_LON, "x"), - (self.NODE_LAT, "y"), - ) - self.assertRaisesRegex( - ValueError, - "Invalid axis specified for edge.*", - mesh.Mesh, - edge_coords_and_axes=((self.EDGE_LON, "foo"),), - **kwargs, - ) - self.assertRaisesRegex( - ValueError, - "Invalid axis specified for face.*", - mesh.Mesh, - face_coords_and_axes=((self.FACE_LON, "foo"),), - **kwargs, - ) - - # Several arg safety checks in __init__ currently unreachable given earlier checks. - - def test_minimum_connectivities(self): - # Further validations are tested in add_connectivity tests. - kwargs = { - "topology_dimension": 1, - "node_coords_and_axes": ( - (self.NODE_LON, "x"), - (self.NODE_LAT, "y"), - ), - "connectivities": (self.FACE_NODE,), - } - self.assertRaisesRegex( - ValueError, - ".*requires a edge_node_connectivity.*", - mesh.Mesh, - **kwargs, - ) - - def test_minimum_coords(self): - # Further validations are tested in add_coord tests. - kwargs = { - "topology_dimension": 1, - "node_coords_and_axes": ((self.NODE_LON, "x"), (None, "y")), - "connectivities": (self.FACE_NODE,), - } - self.assertRaisesRegex( - ValueError, - ".*is a required coordinate.*", - mesh.Mesh, - **kwargs, - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py b/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py deleted file mode 100644 index 03e2793fd9..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_MeshCoord.py +++ /dev/null @@ -1,943 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.mesh.MeshCoord`. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import re -import unittest.mock as mock - -import dask.array as da -import numpy as np -import pytest - -from iris._lazy_data import as_lazy_data, is_lazy_data -from iris.common.metadata import BaseMetadata, CoordMetadata -from iris.coords import AuxCoord, Coord -from iris.cube import Cube -from iris.experimental.ugrid.mesh import Connectivity, Mesh, MeshCoord -import iris.tests.stock.mesh -from iris.tests.stock.mesh import sample_mesh, sample_meshcoord - - -class Test___init__(tests.IrisTest): - def setUp(self): - mesh = sample_mesh() - self.mesh = mesh - self.meshcoord = sample_meshcoord(mesh=mesh) - - def test_basic(self): - meshcoord = self.meshcoord - self.assertEqual(meshcoord.mesh, self.mesh) - self.assertEqual(meshcoord.location, "face") - self.assertEqual(meshcoord.axis, "x") - self.assertIsInstance(meshcoord, MeshCoord) - self.assertIsInstance(meshcoord, Coord) - - def test_derived_properties(self): - # Check the derived properties of the meshcoord against the correct - # underlying mesh coordinate. - for axis in Mesh.AXES: - meshcoord = sample_meshcoord(axis=axis) - face_x_coord = meshcoord.mesh.coord(include_faces=True, axis=axis) - for key in face_x_coord.metadata._fields: - meshval = getattr(meshcoord, key) - # All relevant attributes are derived from the face coord. - self.assertEqual(meshval, getattr(face_x_coord, key)) - - def test_fail_bad_mesh(self): - with self.assertRaisesRegex(TypeError, "must be a.*Mesh"): - sample_meshcoord(mesh=mock.sentinel.odd) - - def test_valid_locations(self): - for loc in Mesh.ELEMENTS: - meshcoord = sample_meshcoord(location=loc) - self.assertEqual(meshcoord.location, loc) - - def test_fail_bad_location(self): - with self.assertRaisesRegex(ValueError, "not a valid Mesh location"): - sample_meshcoord(location="bad") - - def test_fail_bad_axis(self): - with self.assertRaisesRegex(ValueError, "not a valid Mesh axis"): - sample_meshcoord(axis="q") - - -class Test__readonly_properties(tests.IrisTest): - def setUp(self): - self.meshcoord = sample_meshcoord() - - def test_fixed_metadata(self): - # Check that you cannot set any of these on an existing MeshCoord. - meshcoord = self.meshcoord - for prop in ("mesh", "location", "axis"): - with self.assertRaisesRegex(AttributeError, "can't set"): - setattr(meshcoord, prop, mock.sentinel.odd) - - def test_coord_system(self): - # The property exists, =None, can set to None, can not set otherwise. - self.assertTrue(hasattr(self.meshcoord, "coord_system")) - self.assertIsNone(self.meshcoord.coord_system) - self.meshcoord.coord_system = None - with self.assertRaisesRegex(ValueError, "Cannot set.* MeshCoord"): - self.meshcoord.coord_system = 1 - - def test_set_climatological(self): - # The property exists, =False, can set to False, can not set otherwise. - self.assertTrue(hasattr(self.meshcoord, "climatological")) - self.assertFalse(self.meshcoord.climatological) - self.meshcoord.climatological = False - with self.assertRaisesRegex(ValueError, "Cannot set.* MeshCoord"): - self.meshcoord.climatological = True - - -class Test__inherited_properties(tests.IrisTest): - """ - Check the settability and effect on equality of the common BaseMetadata - properties inherited from Coord : i.e. names/units/attributes. - - Though copied from the mesh at creation, they are also changeable. - - """ - - def setUp(self): - self.meshcoord = sample_meshcoord() - - def test_inherited_properties(self): - # Check that these are settable, and affect equality. - meshcoord = self.meshcoord - # Add an existing attribute, so we can change it. - meshcoord.attributes["thing"] = 7 - for prop in BaseMetadata._fields: - meshcoord2 = meshcoord.copy() - if "name" in prop: - # Use a standard-name, can do for any of them. - setattr(meshcoord2, prop, "height") - elif prop == "units": - meshcoord2.units = "Pa" - elif prop == "attributes": - meshcoord2.attributes["thing"] = 77 - self.assertNotEqual(meshcoord2, meshcoord) - - -class Test__points_and_bounds(tests.IrisTest): - # Basic method testing only, for 3 locations with simple array values. - # See Test_MeshCoord__dataviews for more detailed checks. - def test_node(self): - meshcoord = sample_meshcoord(location="node") - n_nodes = ( - iris.tests.stock.mesh._TEST_N_NODES - ) # n-nodes default for sample mesh - self.assertIsNone(meshcoord.core_bounds()) - self.assertArrayAllClose(meshcoord.points, 1100 + np.arange(n_nodes)) - - def test_edge(self): - meshcoord = sample_meshcoord(location="edge") - points, bounds = meshcoord.core_points(), meshcoord.core_bounds() - self.assertEqual(points.shape, meshcoord.shape) - self.assertEqual(bounds.shape, meshcoord.shape + (2,)) - self.assertArrayAllClose( - meshcoord.points, [2100, 2101, 2102, 2103, 2104] - ) - self.assertArrayAllClose( - meshcoord.bounds, - [ - (1105, 1106), - (1107, 1108), - (1109, 1110), - (1111, 1112), - (1113, 1114), - ], - ) - - def test_face(self): - meshcoord = sample_meshcoord(location="face") - points, bounds = meshcoord.core_points(), meshcoord.core_bounds() - self.assertEqual(points.shape, meshcoord.shape) - self.assertEqual(bounds.shape, meshcoord.shape + (4,)) - self.assertArrayAllClose(meshcoord.points, [3100, 3101, 3102]) - self.assertArrayAllClose( - meshcoord.bounds, - [ - (1100, 1101, 1102, 1103), - (1104, 1105, 1106, 1107), - (1108, 1109, 1110, 1111), - ], - ) - - -class Test___eq__(tests.IrisTest): - def setUp(self): - self.mesh = sample_mesh() - - def _create_common_mesh(self, **kwargs): - return sample_meshcoord(mesh=self.mesh, **kwargs) - - def test_identical_mesh(self): - meshcoord1 = self._create_common_mesh() - meshcoord2 = self._create_common_mesh() - self.assertEqual(meshcoord2, meshcoord1) - - def test_equal_mesh(self): - mesh1 = sample_mesh() - mesh2 = sample_mesh() - meshcoord1 = sample_meshcoord(mesh=mesh1) - meshcoord2 = sample_meshcoord(mesh=mesh2) - self.assertEqual(meshcoord2, meshcoord1) - - def test_different_mesh(self): - mesh1 = sample_mesh() - mesh2 = sample_mesh() - mesh2.long_name = "new_name" - meshcoord1 = sample_meshcoord(mesh=mesh1) - meshcoord2 = sample_meshcoord(mesh=mesh2) - self.assertNotEqual(meshcoord2, meshcoord1) - - def test_different_location(self): - meshcoord = self._create_common_mesh() - meshcoord2 = self._create_common_mesh(location="node") - self.assertNotEqual(meshcoord2, meshcoord) - - def test_different_axis(self): - meshcoord = self._create_common_mesh() - meshcoord2 = self._create_common_mesh(axis="y") - self.assertNotEqual(meshcoord2, meshcoord) - - -class Test__copy(tests.IrisTest): - def test_basic(self): - meshcoord = sample_meshcoord() - meshcoord2 = meshcoord.copy() - self.assertIsNot(meshcoord2, meshcoord) - self.assertEqual(meshcoord2, meshcoord) - # In this case, they should share *NOT* copy the Mesh object. - self.assertIs(meshcoord2.mesh, meshcoord.mesh) - - def test_fail_copy_newpoints(self): - meshcoord = sample_meshcoord() - with self.assertRaisesRegex(ValueError, "Cannot change the content"): - meshcoord.copy(points=meshcoord.points) - - def test_fail_copy_newbounds(self): - meshcoord = sample_meshcoord() - with self.assertRaisesRegex(ValueError, "Cannot change the content"): - meshcoord.copy(bounds=meshcoord.bounds) - - -class Test__getitem__(tests.IrisTest): - def test_slice_wholeslice_1tuple(self): - # The only slicing case that we support, to enable cube slicing. - meshcoord = sample_meshcoord() - meshcoord2 = meshcoord[:,] - self.assertIsNot(meshcoord2, meshcoord) - self.assertEqual(meshcoord2, meshcoord) - # In this case, we should *NOT* copy the linked Mesh object. - self.assertIs(meshcoord2.mesh, meshcoord.mesh) - - def test_slice_whole_slice_singlekey(self): - # A slice(None) also fails, if not presented in a 1-tuple. - meshcoord = sample_meshcoord() - with self.assertRaisesRegex(ValueError, "Cannot index"): - meshcoord[:] - - def test_fail_slice_part(self): - meshcoord = sample_meshcoord() - with self.assertRaisesRegex(ValueError, "Cannot index"): - meshcoord[:1] - - -class Test__str_repr(tests.IrisTest): - def setUp(self): - mesh = sample_mesh() - self.mesh = mesh - # Give mesh itself a name: makes a difference between str and repr. - self.mesh.rename("test_mesh") - self.meshcoord = sample_meshcoord(mesh=mesh) - - def _expected_elements_regexp( - self, - standard_name="longitude", - long_name=None, - attributes=False, - location="face", - axis="x", - var_name=None, - ): - # Printed name is standard or long -- we don't have a case with neither - coord_name = standard_name or long_name - # Construct regexp in 'sections' - # NB each consumes upto first non-space in the next line - regexp = f"MeshCoord : {coord_name} / [^\n]+\n *" - regexp += r"mesh: \\n *" - regexp += f"location: '{location}'\n *" - - # Now some optional sections : whichever comes first will match - # arbitrary content leading up to it. - matched_upto = False - - def upto_first_expected(regexp, matched_any_upto): - if not matched_any_upto: - regexp += ".*" - matched_any_upto = True - return regexp, matched_any_upto - - if standard_name: - regexp, matched_upto = upto_first_expected(regexp, matched_upto) - regexp += f"standard_name: '{standard_name}'\n *" - if long_name: - regexp, matched_upto = upto_first_expected(regexp, matched_upto) - regexp += f"long_name: '{long_name}'\n *" - if var_name: - regexp, matched_upto = upto_first_expected(regexp, matched_upto) - regexp += f"var_name: '{var_name}'\n *" - if attributes: - # if we expected attributes, they should come next - # TODO: change this when each attribute goes on a new line - regexp, matched_upto = upto_first_expected(regexp, matched_upto) - # match 'attributes:' followed by N*lines with larger indent - regexp += "attributes:(\n [^ \n]+ +[^ \n]+)+\n " - # After those items, expect 'axis' next - # N.B. this FAILS if we had attributes when we didn't expect them - regexp += f"axis: '{axis}'$" # N.B. this is always the end - - # Compile regexp, also allowing matches across newlines - regexp = re.compile(regexp, flags=re.DOTALL) - return regexp - - def test_repr(self): - # A simple check for the condensed form. - result = repr(self.meshcoord) - expected = ( - "" - ) - self.assertEqual(expected, result) - - def test_repr_lazy(self): - # Displays lazy content (and does not realise!). - self.meshcoord.points = as_lazy_data(self.meshcoord.points) - self.meshcoord.bounds = as_lazy_data(self.meshcoord.bounds) - self.assertTrue(self.meshcoord.has_lazy_points()) - self.assertTrue(self.meshcoord.has_lazy_bounds()) - - result = repr(self.meshcoord) - self.assertTrue(self.meshcoord.has_lazy_points()) - self.assertTrue(self.meshcoord.has_lazy_bounds()) - - expected = ( - "+bounds shape(3,)>" - ) - self.assertEqual(expected, result) - - def test_repr__nameless_mesh(self): - # Check what it does when the Mesh doesn't have a name. - self.mesh.long_name = None - assert self.mesh.name() == "unknown" - result = repr(self.meshcoord) - re_expected = ( - r".MeshCoord: longitude / \(unknown\) " - r"mesh\(.Mesh object at 0x[^>]+.\) location\(face\) " - ) - self.assertRegex(result, re_expected) - - def test__str__(self): - # Basic output contains mesh, location, standard_name, long_name, - # attributes, mesh, location and axis - result = str(self.meshcoord) - re_expected = self._expected_elements_regexp() - self.assertRegex(result, re_expected) - - def test__str__lazy(self): - # Displays lazy content (and does not realise!). - self.meshcoord.points = as_lazy_data(self.meshcoord.points) - self.meshcoord.bounds = as_lazy_data(self.meshcoord.bounds) - - result = str(self.meshcoord) - self.assertTrue(self.meshcoord.has_lazy_points()) - self.assertTrue(self.meshcoord.has_lazy_bounds()) - - self.assertIn("points: ", result) - self.assertIn("bounds: ", result) - re_expected = self._expected_elements_regexp() - self.assertRegex(result, re_expected) - - def test_alternative_location_and_axis(self): - meshcoord = sample_meshcoord(mesh=self.mesh, location="edge", axis="y") - result = str(meshcoord) - re_expected = self._expected_elements_regexp( - standard_name="latitude", - long_name=None, - location="edge", - axis="y", - attributes=None, - ) - self.assertRegex(result, re_expected) - # Basic output contains standard_name, long_name, attributes - - def test_str_no_long_name(self): - mesh = self.mesh - # Remove the long_name of the node coord in the mesh. - node_coord = mesh.coord(include_nodes=True, axis="x") - node_coord.long_name = None - # Make a new meshcoord, based on the modified mesh. - meshcoord = sample_meshcoord(mesh=self.mesh) - result = str(meshcoord) - re_expected = self._expected_elements_regexp(long_name=False) - self.assertRegex(result, re_expected) - - def test_str_no_attributes(self): - mesh = self.mesh - # No attributes on the node coord in the mesh. - node_coord = mesh.coord(include_nodes=True, axis="x") - node_coord.attributes = None - # Make a new meshcoord, based on the modified mesh. - meshcoord = sample_meshcoord(mesh=self.mesh) - result = str(meshcoord) - re_expected = self._expected_elements_regexp(attributes=False) - self.assertRegex(result, re_expected) - - def test_str_empty_attributes(self): - mesh = self.mesh - # Empty attributes dict on the node coord in the mesh. - node_coord = mesh.coord(include_nodes=True, axis="x") - node_coord.attributes.clear() - # Make a new meshcoord, based on the modified mesh. - meshcoord = sample_meshcoord(mesh=self.mesh) - result = str(meshcoord) - re_expected = self._expected_elements_regexp(attributes=False) - self.assertRegex(result, re_expected) - - -class Test_cube_containment(tests.IrisTest): - # Check that we can put a MeshCoord into a cube, and have it behave just - # like a regular AuxCoord. - def setUp(self): - meshcoord = sample_meshcoord() - data_shape = (2,) + meshcoord.shape - cube = Cube(np.zeros(data_shape)) - cube.add_aux_coord(meshcoord, 1) - self.meshcoord = meshcoord - self.cube = cube - - def test_added_to_cube(self): - meshcoord = self.meshcoord - cube = self.cube - self.assertIn(meshcoord, cube.coords()) - - def test_cube_dims(self): - meshcoord = self.meshcoord - cube = self.cube - self.assertEqual(meshcoord.cube_dims(cube), (1,)) - self.assertEqual(cube.coord_dims(meshcoord), (1,)) - - def test_find_by_name(self): - meshcoord = self.meshcoord - # hack to give it a long name - meshcoord.long_name = "odd_case" - cube = self.cube - self.assertIs(cube.coord(standard_name="longitude"), meshcoord) - self.assertIs(cube.coord(long_name="odd_case"), meshcoord) - - def test_find_by_axis(self): - meshcoord = self.meshcoord - cube = self.cube - self.assertIs(cube.coord(axis="x"), meshcoord) - self.assertEqual(cube.coords(axis="y"), []) - - # NOTE: the meshcoord.axis takes precedence over the older - # "guessed axis" approach. So the standard_name does not control it. - meshcoord.rename("latitude") - self.assertIs(cube.coord(axis="x"), meshcoord) - self.assertEqual(cube.coords(axis="y"), []) - - def test_cube_copy(self): - # Check that we can copy a cube, and get a MeshCoord == the original. - # Note: currently must have the *same* mesh, as for MeshCoord.copy(). - meshcoord = self.meshcoord - cube = self.cube - cube2 = cube.copy() - meshco2 = cube2.coord(meshcoord) - self.assertIsNot(meshco2, meshcoord) - self.assertEqual(meshco2, meshcoord) - - def test_cube_nonmesh_slice(self): - # Check that we can slice a cube on a non-mesh dimension, and get a - # meshcoord == original. - # Note: currently this must have the *same* mesh, as for .copy(). - meshcoord = self.meshcoord - cube = self.cube - cube2 = cube[:1] # Make a reduced copy, slicing the non-mesh dim - meshco2 = cube2.coord(meshcoord) - self.assertIsNot(meshco2, meshcoord) - self.assertEqual(meshco2, meshcoord) - - def test_cube_mesh_partslice(self): - # Check that we can *not* get a partial MeshCoord slice, as the - # MeshCoord refuses to be sliced. - # Instead, you get an AuxCoord created from the MeshCoord. - meshcoord = self.meshcoord - cube = self.cube - cube2 = cube[:, :1] # Make a reduced copy, slicing the mesh dim - - # The resulting coord can not be identified with the original. - # (i.e. metadata does not match) - co_matches = cube2.coords(meshcoord) - self.assertEqual(co_matches, []) - - # The resulting coord is an AuxCoord instead of a MeshCoord, but the - # values match. - co2 = cube2.coord(meshcoord.name()) - self.assertFalse(isinstance(co2, MeshCoord)) - self.assertIsInstance(co2, AuxCoord) - self.assertArrayAllClose(co2.points, meshcoord.points[:1]) - self.assertArrayAllClose(co2.bounds, meshcoord.bounds[:1]) - - -class Test_auxcoord_conversion(tests.IrisTest): - def test_basic(self): - meshcoord = sample_meshcoord() - auxcoord = AuxCoord.from_coord(meshcoord) - for propname, auxval in auxcoord.metadata._asdict().items(): - meshval = getattr(meshcoord, propname) - self.assertEqual(auxval, meshval) - # Also check array content. - self.assertArrayAllClose(auxcoord.points, meshcoord.points) - self.assertArrayAllClose(auxcoord.bounds, meshcoord.bounds) - - -class Test_MeshCoord__dataviews(tests.IrisTest): - """ - Fuller testing of points and bounds calculations and behaviour. - Including connectivity missing-points (non-square faces). - - """ - - def setUp(self): - self._make_test_meshcoord() - - def _make_test_meshcoord( - self, - lazy_sources=False, - location="face", - inds_start_index=0, - inds_location_axis=0, - facenodes_changes=None, - ): - # Construct a miniature face-nodes mesh for testing. - # NOTE: we will make our connectivity arrays with standard - # start_index=0 and location_axis=0 : We only adjust that (if required) when - # creating the actual connectivities. - face_nodes_array = np.array( - [ - [0, 2, 1, 3], - [1, 3, 10, 13], - [2, 7, 9, 19], - [ - 3, - 4, - 7, - -1, - ], # This one has a "missing" point (it's a triangle) - [8, 1, 7, 2], - ] - ) - # Connectivity uses *masked* for missing points. - face_nodes_array = np.ma.masked_less(face_nodes_array, 0) - if facenodes_changes: - facenodes_changes = facenodes_changes.copy() - facenodes_changes.pop("n_extra_bad_points") - for indices, value in facenodes_changes.items(): - face_nodes_array[indices] = value - - # Construct a miniature edge-nodes mesh for testing. - edge_nodes_array = np.array([[0, 2], [1, 3], [1, 4], [3, 7]]) - # Connectivity uses *masked* for missing points. - edge_nodes_array = np.ma.masked_less(edge_nodes_array, 0) - - n_faces = face_nodes_array.shape[0] - n_edges = edge_nodes_array.shape[0] - n_nodes = int(face_nodes_array.max() + 1) - self.NODECOORDS_BASENUM = 1100.0 - self.EDGECOORDS_BASENUM = 1200.0 - self.FACECOORDS_BASENUM = 1300.0 - node_xs = self.NODECOORDS_BASENUM + np.arange(n_nodes) - edge_xs = self.EDGECOORDS_BASENUM + np.arange(n_edges) - face_xs = self.FACECOORDS_BASENUM + np.arange(n_faces) - - # Record all these for re-use in tests - self.n_faces = n_faces - self.n_nodes = n_nodes - self.face_xs = face_xs - self.node_xs = node_xs - self.edge_xs = edge_xs - self.face_nodes_array = face_nodes_array - self.edge_nodes_array = edge_nodes_array - - # convert source data to Dask arrays if asked. - if lazy_sources: - - def lazify(arr): - return da.from_array(arr, chunks=-1, meta=np.ndarray) - - node_xs = lazify(node_xs) - face_xs = lazify(face_xs) - edge_xs = lazify(edge_xs) - face_nodes_array = lazify(face_nodes_array) - edge_nodes_array = lazify(edge_nodes_array) - - # Build a mesh with this info stored in it. - co_nodex = AuxCoord( - node_xs, standard_name="longitude", long_name="node_x", units=1 - ) - co_facex = AuxCoord( - face_xs, standard_name="longitude", long_name="face_x", units=1 - ) - co_edgex = AuxCoord( - edge_xs, standard_name="longitude", long_name="edge_x", units=1 - ) - # N.B. the Mesh requires 'Y's as well. - co_nodey = co_nodex.copy() - co_nodey.rename("latitude") - co_nodey.long_name = "node_y" - co_facey = co_facex.copy() - co_facey.rename("latitude") - co_facey.long_name = "face_y" - co_edgey = co_edgex.copy() - co_edgey.rename("edge_y") - co_edgey.long_name = "edge_y" - - face_node_conn = Connectivity( - inds_start_index - + ( - face_nodes_array.transpose() - if inds_location_axis == 1 - else face_nodes_array - ), - cf_role="face_node_connectivity", - long_name="face_nodes", - start_index=inds_start_index, - location_axis=inds_location_axis, - ) - - edge_node_conn = Connectivity( - inds_start_index - + ( - edge_nodes_array.transpose() - if inds_location_axis == 1 - else edge_nodes_array - ), - cf_role="edge_node_connectivity", - long_name="edge_nodes", - start_index=inds_start_index, - location_axis=inds_location_axis, - ) - - self.mesh = Mesh( - topology_dimension=2, - node_coords_and_axes=[(co_nodex, "x"), (co_nodey, "y")], - connectivities=[face_node_conn, edge_node_conn], - face_coords_and_axes=[(co_facex, "x"), (co_facey, "y")], - edge_coords_and_axes=[(co_edgex, "x"), (co_edgey, "y")], - ) - - # Construct a test meshcoord. - meshcoord = MeshCoord(mesh=self.mesh, location=location, axis="x") - self.meshcoord = meshcoord - return meshcoord - - def _check_expected_points_values(self): - # The points are just the face_x-s - meshcoord = self.meshcoord - self.assertArrayAllClose(meshcoord.points, self.face_xs) - - def _check_expected_bounds_values(self, facenodes_changes=None): - mesh_coord = self.meshcoord - # The bounds are selected node_x-s, ==> node_number + coords-offset - result = mesh_coord.bounds - # N.B. result should be masked where the masked indices are. - expected = self.NODECOORDS_BASENUM + self.face_nodes_array - if facenodes_changes: - # ALSO include any "bad" values in that calculation. - bad_values = (self.face_nodes_array < 0) | ( - self.face_nodes_array >= self.n_nodes - ) - expected[bad_values] = np.ma.masked - # Check there are *some* masked points. - n_missing_expected = 1 - if facenodes_changes: - n_missing_expected += facenodes_changes["n_extra_bad_points"] - self.assertEqual(np.count_nonzero(expected.mask), n_missing_expected) - # Check results match, *including* location of masked points. - self.assertMaskedArrayAlmostEqual(result, expected) - - def test_points_values(self): - """Basic points content check, on real data.""" - meshcoord = self.meshcoord - self.assertFalse(meshcoord.has_lazy_points()) - self.assertFalse(meshcoord.has_lazy_bounds()) - self._check_expected_points_values() - - def test_bounds_values(self): - """Basic bounds contents check.""" - meshcoord = self.meshcoord - self.assertFalse(meshcoord.has_lazy_points()) - self.assertFalse(meshcoord.has_lazy_bounds()) - self._check_expected_bounds_values() - - def test_lazy_points_values(self): - """Check lazy points calculation on lazy inputs.""" - # Remake the test data with lazy source coords. - meshcoord = self._make_test_meshcoord(lazy_sources=True) - self.assertTrue(meshcoord.has_lazy_points()) - self.assertTrue(meshcoord.has_lazy_bounds()) - # Check values, as previous. - self._check_expected_points_values() - - def test_lazy_bounds_values(self): - meshcoord = self._make_test_meshcoord(lazy_sources=True) - self.assertTrue(meshcoord.has_lazy_points()) - self.assertTrue(meshcoord.has_lazy_bounds()) - # Check values, as previous. - self._check_expected_bounds_values() - - def test_edge_points(self): - meshcoord = self._make_test_meshcoord(location="edge") - result = meshcoord.points - self.assertArrayAllClose(result, self.edge_xs) - - def test_edge_bounds(self): - meshcoord = self._make_test_meshcoord(location="edge") - result = meshcoord.bounds - # The bounds are selected node_x-s : all == node_number + 100.0 - expected = self.NODECOORDS_BASENUM + self.edge_nodes_array - # NB simpler than faces : no possibility of missing points - self.assertArrayAlmostEqual(result, expected) - - def test_bounds_connectivity__location_axis_1(self): - # Test with a transposed indices array. - self._make_test_meshcoord(inds_location_axis=1) - self._check_expected_bounds_values() - - def test_bounds_connectivity__start_index_1(self): - # Test 1-based indices. - self._make_test_meshcoord(inds_start_index=1) - self._check_expected_bounds_values() - - def test_meshcoord_leaves_originals_lazy(self): - self._make_test_meshcoord(lazy_sources=True) - mesh = self.mesh - meshcoord = self.meshcoord - - # Fetch the relevant source objects from the mesh. - def fetch_sources_from_mesh(): - return ( - mesh.coord(include_nodes=True, axis="x"), - mesh.coord(include_faces=True, axis="x"), - mesh.face_node_connectivity, - ) - - # Check all the source coords are lazy. - for coord in fetch_sources_from_mesh(): - # Note: not all are actual Coords, so can't use 'has_lazy_points'. - self.assertTrue(is_lazy_data(coord._core_values())) - - # Calculate both points + bounds of the meshcoord - self.assertTrue(meshcoord.has_lazy_points()) - self.assertTrue(meshcoord.has_lazy_bounds()) - meshcoord.points - meshcoord.bounds - self.assertFalse(meshcoord.has_lazy_points()) - self.assertFalse(meshcoord.has_lazy_bounds()) - - # Check all the source coords are still lazy. - for coord in fetch_sources_from_mesh(): - # Note: not all are actual Coords, so can't use 'has_lazy_points'. - self.assertTrue(is_lazy_data(coord._core_values())) - - def _check_bounds_bad_index_values(self, lazy): - facenodes_modify = { - # nothing wrong with this one - (2, 1): 1, - # extra missing point, normal "missing" indicator - (3, 3): np.ma.masked, - # bad index > n_nodes - (4, 2): 100, - # NOTE: **can't** set an index < 0, as it is rejected by the - # Connectivity validity check. - # Indicate how many "extra" missing results this should cause. - "n_extra_bad_points": 2, - } - self._make_test_meshcoord( - facenodes_changes=facenodes_modify, lazy_sources=lazy - ) - self._check_expected_bounds_values() - - def test_bounds_badvalues__real(self): - self._check_bounds_bad_index_values(lazy=False) - - def test_bounds_badvalues__lazy(self): - self._check_bounds_bad_index_values(lazy=True) - - -class Test__metadata: - def setup_mesh(self, location, axis): - # Create a standard test mesh + attach it to the test instance. - mesh = sample_mesh() - - # Modify the metadata of specific coordinates used in this test. - def select_coord(location, axis): - kwargs = {f"include_{location}s": True, "axis": axis} - return mesh.coord(**kwargs) - - node_coord = select_coord("node", axis) - location_coord = select_coord(location, axis) - for i_place, coord in enumerate((node_coord, location_coord)): - coord.standard_name = "longitude" if axis == "x" else "latitude" - coord.units = "degrees" - coord.long_name = f"long_name_{i_place}" - coord.var_name = f"var_name_{i_place}" - coord.attributes = {"att": i_place} - - # attach all the relevant testcase context to the test instance. - self.mesh = mesh - self.location = location - self.axis = axis - self.location_coord = location_coord - self.node_coord = node_coord - - def coord_metadata_matches(self, test_coord, ref_coord): - # Check that two coords match, in all the basic Coord identity/phenomenon - # metadata fields -- so it works even between coords of different subclasses. - for key in CoordMetadata._fields: - assert getattr(test_coord, key) == getattr(ref_coord, key) - - @pytest.fixture(params=["face", "edge"]) - def location_face_or_edge(self, request): - # Fixture to parametrise over location = face/edge - return request.param - - @pytest.fixture(params=["x", "y"]) - def axis_x_or_y(self, request): - # Fixture to parametrise over axis = X/Y - return request.param - - def test_node_meshcoord(self, axis_x_or_y): - # MeshCoord metadata matches that of the relevant node coord. - self.setup_mesh(location="node", axis=axis_x_or_y) - meshcoord = self.mesh.to_MeshCoord( - location=self.location, axis=self.axis - ) - self.coord_metadata_matches(meshcoord, self.node_coord) - - def test_faceedge_basic(self, location_face_or_edge, axis_x_or_y): - # MeshCoord metadata matches that of the face/edge ("points") coord. - self.setup_mesh(location_face_or_edge, axis_x_or_y) - meshcoord = self.mesh.to_MeshCoord( - location=self.location, axis=self.axis - ) - self.coord_metadata_matches(meshcoord, self.location_coord) - - @pytest.mark.parametrize( - "fieldname", ["long_name", "var_name", "attributes"] - ) - def test_faceedge_dontcare_fields( - self, location_face_or_edge, axis_x_or_y, fieldname - ): - # Check that it's ok for the face/edge and node coords to have different - # long-name, var-name or attributes. - self.setup_mesh(location_face_or_edge, axis_x_or_y) - if fieldname == "attributes": - different_value = {"myattrib": "different attributes"} - else: - # others are just arbitrary strings. - different_value = "different" - setattr(self.location_coord, fieldname, different_value) - # Mostly.. just check this does not cause an error, as it would do if we - # modified "standard_name" or "units" (see other tests) ... - meshcoord = self.mesh.to_MeshCoord( - location=self.location, axis=self.axis - ) - # ... but also, check that the result matches the expected face/edge coord. - self.coord_metadata_matches(meshcoord, self.location_coord) - - def test_faceedge_fail_mismatched_stdnames( - self, location_face_or_edge, axis_x_or_y - ): - # Different "standard_name" for node and face/edge causes an error. - self.setup_mesh(location_face_or_edge, axis_x_or_y) - node_name = f"projection_{axis_x_or_y}_coordinate" - self.node_coord.standard_name = node_name - location_name = "longitude" if axis_x_or_y == "x" else "latitude" - msg = ( - "Node coordinate .*" - f"disagrees with the {location_face_or_edge} coordinate .*, " - 'in having a "standard_name" value of ' - f"'{node_name}' instead of '{location_name}'" - ) - with pytest.raises(ValueError, match=msg): - self.mesh.to_MeshCoord( - location=location_face_or_edge, axis=axis_x_or_y - ) - - def test_faceedge_fail_missing_stdnames( - self, location_face_or_edge, axis_x_or_y - ): - # "standard_name" compared with None also causes an error. - self.setup_mesh(location_face_or_edge, axis_x_or_y) - self.node_coord.standard_name = None - # N.B. in the absence of a standard-name, we **must** provide an extra ".axis" - # property, or the coordinate cannot be correctly identified in the Mesh. - # This is a bit of a kludge, but works with current code. - self.node_coord.axis = axis_x_or_y - - location_name = "longitude" if axis_x_or_y == "x" else "latitude" - msg = ( - "Node coordinate .*" - f"disagrees with the {location_face_or_edge} coordinate .*, " - 'in having a "standard_name" value of ' - f"None instead of '{location_name}'" - ) - with pytest.raises(ValueError, match=msg): - self.mesh.to_MeshCoord( - location=location_face_or_edge, axis=axis_x_or_y - ) - - def test_faceedge_fail_mismatched_units( - self, location_face_or_edge, axis_x_or_y - ): - # Different "units" for node and face/edge causes an error. - self.setup_mesh(location_face_or_edge, axis_x_or_y) - self.node_coord.units = "hPa" - msg = ( - "Node coordinate .*" - f"disagrees with the {location_face_or_edge} coordinate .*, " - 'in having a "units" value of ' - "'hPa' instead of 'degrees'" - ) - with pytest.raises(ValueError, match=msg): - self.mesh.to_MeshCoord( - location=location_face_or_edge, axis=axis_x_or_y - ) - - def test_faceedge_missing_units(self, location_face_or_edge, axis_x_or_y): - # Units compared with a None ("unknown") is not an error. - self.setup_mesh(location_face_or_edge, axis_x_or_y) - self.node_coord.units = None - # This is OK - meshcoord = self.mesh.to_MeshCoord( - location=self.location, axis=self.axis - ) - # ... but also, check that the result matches the expected face/edge coord. - self.coord_metadata_matches(meshcoord, self.location_coord) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh__from_coords.py b/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh__from_coords.py deleted file mode 100644 index edd34f94a1..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/mesh/test_Mesh__from_coords.py +++ /dev/null @@ -1,253 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :meth:`iris.experimental.ugrid.mesh.Mesh.from_coords`. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.coords import AuxCoord, DimCoord -from iris.experimental.ugrid import logger -from iris.experimental.ugrid.mesh import Connectivity, Mesh -from iris.tests.stock import simple_2d_w_multidim_coords - - -class Test1Dim(tests.IrisTest): - def setUp(self): - self.lon = DimCoord( - points=[0.5, 1.5, 2.5], - bounds=[[0, 1], [1, 2], [2, 3]], - standard_name="longitude", - long_name="edge longitudes", - var_name="lon", - units="degrees", - attributes={"test": 1}, - ) - # Should be fine with either a DimCoord or an AuxCoord. - self.lat = AuxCoord( - points=[0.5, 2.5, 1.5], - bounds=[[0, 1], [2, 3], [1, 2]], - standard_name="latitude", - long_name="edge_latitudes", - var_name="lat", - units="degrees", - attributes={"test": 1}, - ) - - def create(self): - return Mesh.from_coords(self.lon, self.lat) - - def test_dimensionality(self): - mesh = self.create() - self.assertEqual(1, mesh.topology_dimension) - - self.assertArrayEqual( - [0, 1, 1, 2, 2, 3], mesh.node_coords.node_x.points - ) - self.assertArrayEqual( - [0, 1, 2, 3, 1, 2], mesh.node_coords.node_y.points - ) - self.assertArrayEqual([0.5, 1.5, 2.5], mesh.edge_coords.edge_x.points) - self.assertArrayEqual([0.5, 2.5, 1.5], mesh.edge_coords.edge_y.points) - self.assertIsNone(getattr(mesh, "face_coords", None)) - - for conn_name in Connectivity.UGRID_CF_ROLES: - conn = getattr(mesh, conn_name, None) - if conn_name == "edge_node_connectivity": - self.assertArrayEqual([[0, 1], [2, 3], [4, 5]], conn.indices) - else: - self.assertIsNone(conn) - - def test_node_metadata(self): - mesh = self.create() - pairs = [ - (self.lon, mesh.node_coords.node_x), - (self.lat, mesh.node_coords.node_y), - ] - for expected_coord, actual_coord in pairs: - for attr in ("standard_name", "long_name", "units", "attributes"): - expected = getattr(expected_coord, attr) - actual = getattr(actual_coord, attr) - self.assertEqual(expected, actual) - self.assertIsNone(actual_coord.var_name) - - def test_centre_metadata(self): - mesh = self.create() - pairs = [ - (self.lon, mesh.edge_coords.edge_x), - (self.lat, mesh.edge_coords.edge_y), - ] - for expected_coord, actual_coord in pairs: - for attr in ("standard_name", "long_name", "units", "attributes"): - expected = getattr(expected_coord, attr) - actual = getattr(actual_coord, attr) - self.assertEqual(expected, actual) - self.assertIsNone(actual_coord.var_name) - - def test_mesh_metadata(self): - # Inappropriate to guess these values from the input coords. - mesh = self.create() - for attr in ( - "standard_name", - "long_name", - "var_name", - ): - self.assertIsNone(getattr(mesh, attr)) - self.assertTrue(mesh.units.is_unknown()) - self.assertDictEqual({}, mesh.attributes) - - def test_lazy(self): - self.lon = AuxCoord.from_coord(self.lon) - self.lon = self.lon.copy( - self.lon.lazy_points(), self.lon.lazy_bounds() - ) - self.lat = self.lat.copy( - self.lat.lazy_points(), self.lat.lazy_bounds() - ) - - mesh = self.create() - for coord in list(mesh.all_coords): - if coord is not None: - self.assertTrue(coord.has_lazy_points()) - for conn in list(mesh.all_connectivities): - if conn is not None: - self.assertTrue(conn.has_lazy_indices()) - - def test_coord_shape_mismatch(self): - lat_orig = self.lat.copy(self.lat.points, self.lat.bounds) - self.lat = lat_orig.copy( - points=lat_orig.points, bounds=np.tile(lat_orig.bounds, 2) - ) - with self.assertRaisesRegex( - ValueError, "bounds shapes are not identical" - ): - _ = self.create() - - self.lat = lat_orig.copy( - points=lat_orig.points[-1], bounds=lat_orig.bounds[-1] - ) - with self.assertRaisesRegex( - ValueError, "points shapes are not identical" - ): - _ = self.create() - - def test_reorder(self): - # Swap the coords. - self.lat, self.lon = self.lon, self.lat - mesh = self.create() - # Confirm that the coords have been swapped back to the 'correct' order. - self.assertEqual("longitude", mesh.node_coords.node_x.standard_name) - self.assertEqual("latitude", mesh.node_coords.node_y.standard_name) - - def test_non_xy(self): - for coord in self.lon, self.lat: - coord.standard_name = None - lon_name, lat_name = [ - coord.long_name for coord in (self.lon, self.lat) - ] - # Swap the coords. - self.lat, self.lon = self.lon, self.lat - with self.assertLogs(logger, "INFO", "Unable to find 'X' and 'Y'"): - mesh = self.create() - # Confirm that the coords have not been swapped back. - self.assertEqual(lat_name, mesh.node_coords.node_x.long_name) - self.assertEqual(lon_name, mesh.node_coords.node_y.long_name) - - -class Test2Dim(Test1Dim): - def setUp(self): - super().setUp() - - self.lon.bounds = [[0, 0.5, 1], [1, 1.5, 2], [2, 2.5, 3]] - self.lon.long_name = "triangle longitudes" - self.lat.bounds = [[0, 1, 0], [2, 3, 2], [1, 2, 1]] - self.lat.long_name = "triangle latitudes" - - def test_dimensionality(self): - mesh = self.create() - self.assertEqual(2, mesh.topology_dimension) - - self.assertArrayEqual( - [0, 0.5, 1, 1, 1.5, 2, 2, 2.5, 3], mesh.node_coords.node_x.points - ) - self.assertArrayEqual( - [0, 1, 0, 2, 3, 2, 1, 2, 1], mesh.node_coords.node_y.points - ) - self.assertIsNone(mesh.edge_coords.edge_x) - self.assertIsNone(mesh.edge_coords.edge_y) - self.assertArrayEqual([0.5, 1.5, 2.5], mesh.face_coords.face_x.points) - self.assertArrayEqual([0.5, 2.5, 1.5], mesh.face_coords.face_y.points) - - for conn_name in Connectivity.UGRID_CF_ROLES: - conn = getattr(mesh, conn_name, None) - if conn_name == "face_node_connectivity": - self.assertArrayEqual( - [[0, 1, 2], [3, 4, 5], [6, 7, 8]], conn.indices - ) - else: - self.assertIsNone(conn) - - def test_centre_metadata(self): - mesh = self.create() - pairs = [ - (self.lon, mesh.face_coords.face_x), - (self.lat, mesh.face_coords.face_y), - ] - for expected_coord, actual_coord in pairs: - for attr in ("standard_name", "long_name", "units", "attributes"): - expected = getattr(expected_coord, attr) - actual = getattr(actual_coord, attr) - self.assertEqual(expected, actual) - self.assertIsNone(actual_coord.var_name) - - def test_mixed_shapes(self): - self.lon = AuxCoord.from_coord(self.lon) - lon_bounds = np.array([[0, 0, 1, 1], [1, 1, 2, 2], [2, 3, 2.5, 999]]) - self.lon.bounds = np.ma.masked_equal(lon_bounds, 999) - - lat_bounds = np.array([[0, 1, 1, 0], [1, 2, 2, 1], [2, 2, 3, 999]]) - self.lat.bounds = np.ma.masked_equal(lat_bounds, 999) - - mesh = self.create() - self.assertArrayEqual( - mesh.face_node_connectivity.location_lengths(), [4, 4, 3] - ) - self.assertEqual(mesh.node_coords.node_x.points[-1], 0.0) - self.assertEqual(mesh.node_coords.node_y.points[-1], 0.0) - - -class TestInvalidBounds(tests.IrisTest): - """Invalid bounds not supported.""" - - def test_no_bounds(self): - lon = AuxCoord(points=[0.5, 1.5, 2.5]) - lat = AuxCoord(points=[0, 1, 2]) - with self.assertRaisesRegex(ValueError, "bounds missing from"): - _ = Mesh.from_coords(lon, lat) - - def test_1_bound(self): - lon = AuxCoord(points=[0.5, 1.5, 2.5], bounds=[[0], [1], [2]]) - lat = AuxCoord(points=[0, 1, 2], bounds=[[0.5], [1.5], [2.5]]) - with self.assertRaisesRegex( - ValueError, r"Expected coordinate bounds.shape \(n, >=2\)" - ): - _ = Mesh.from_coords(lon, lat) - - -class TestInvalidPoints(tests.IrisTest): - """Only 1D coords supported.""" - - def test_2d_coord(self): - cube = simple_2d_w_multidim_coords()[:3, :3] - coord_1, coord_2 = cube.coords() - with self.assertRaisesRegex( - ValueError, "Expected coordinate ndim == 1" - ): - _ = Mesh.from_coords(coord_1, coord_2) diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py b/lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py deleted file mode 100644 index 2d2d040c1d..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.metadata` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/test_ConnectivityMetadata.py b/lib/iris/tests/unit/experimental/ugrid/metadata/test_ConnectivityMetadata.py deleted file mode 100644 index af92e69b08..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/test_ConnectivityMetadata.py +++ /dev/null @@ -1,774 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.metadata.ConnectivityMetadata`. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata -from iris.experimental.ugrid.metadata import ConnectivityMetadata - - -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.cf_role = mock.sentinel.cf_role - self.start_index = mock.sentinel.start_index - self.location_axis = mock.sentinel.location_axis - self.cls = ConnectivityMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - cf_role=self.cf_role, - start_index=self.start_index, - location_axis=self.location_axis, - ) - fmt = ( - "ConnectivityMetadata(standard_name={!r}, long_name={!r}, " - "var_name={!r}, units={!r}, attributes={!r}, cf_role={!r}, " - "start_index={!r}, location_axis={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - self.cf_role, - self.start_index, - self.location_axis, - ) - self.assertEqual(expected, repr(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - "cf_role", - "start_index", - "location_axis", - ) - self.assertEqual(self.cls._fields, expected) - - def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) - - -class Test__eq__(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - cf_role=sentinel.cf_role, - start_index=sentinel.start_index, - location_axis=sentinel.location_axis, - ) - self.dummy = sentinel.dummy - self.cls = ConnectivityMetadata - # The "location_axis" member is stateful only, and does not participate in - # lenient/strict equivalence. - self.members_no_location_axis = filter( - lambda member: member != "location_axis", self.cls._members - ) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value - metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_members_none(self): - for member in self.members_no_location_axis: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_location_axis_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["location_axis"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_members(self): - for member in self.members_no_location_axis: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_location_axis(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["location_axis"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_members(self): - for member in self.members_no_location_axis: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_location_axis(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["location_axis"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_members_none(self): - for member in self.members_no_location_axis: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_location_axis_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["location_axis"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = ConnectivityMetadata - self.one = self.cls(1, 1, 1, 1, 1, 1, 1, 1) - self.two = self.cls(1, 1, 1, 2, 1, 1, 1, 1) - self.none = self.cls(1, 1, 1, None, 1, 1, 1, 1) - self.attributes = self.cls(1, 1, 1, 1, 10, 1, 1, 1) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes(self): - result = self.one < self.attributes - self.assertFalse(result) - result = self.attributes < self.one - self.assertFalse(result) - - -class Test_combine(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - cf_role=sentinel.cf_role, - start_index=sentinel.start_index, - location_axis=sentinel.location_axis, - ) - self.dummy = sentinel.dummy - self.cls = ConnectivityMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.combine.__doc__, self.cls.combine.__doc__ - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = right.copy() - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["units"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - -class Test_difference(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - cf_role=sentinel.cf_role, - start_index=sentinel.start_index, - location_axis=sentinel.location_axis, - ) - self.dummy = sentinel.dummy - self.cls = ConnectivityMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.difference.__doc__, self.cls.difference.__doc__ - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - member_value = getattr(lmetadata, member) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (member_value, None) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = (None, member_value) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["units"] = (left["units"], right["units"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["units"] = lexpected["units"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_strict_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - -class Test_equal(tests.IrisTest): - def setUp(self): - self.cls = ConnectivityMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshCoordMetadata.py b/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshCoordMetadata.py deleted file mode 100644 index 5c96fb7856..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshCoordMetadata.py +++ /dev/null @@ -1,732 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.metadata.MeshCoordMetadata`. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata -from iris.experimental.ugrid.metadata import MeshCoordMetadata - - -class Test__identity(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.location = mock.sentinel.location - self.axis = mock.sentinel.axis - self.cls = MeshCoordMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - location=self.location, - axis=self.axis, - ) - fmt = ( - "MeshCoordMetadata(standard_name={!r}, long_name={!r}, " - "var_name={!r}, units={!r}, attributes={!r}, " - "location={!r}, axis={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - self.location, - self.axis, - ) - self.assertEqual(expected, repr(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - "location", - "axis", - ) - self.assertEqual(self.cls._fields, expected) - - def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) - - -class Test__eq__(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - location=sentinel.location, - axis=sentinel.axis, - ) - self.dummy = sentinel.dummy - self.cls = MeshCoordMetadata - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value - metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_none_nonmember(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = MeshCoordMetadata - values = [1] * len(self.cls._fields) - self.one = self.cls(*values) - - values_two = values[:] - values_two[2] = 2 - self.two = self.cls(*values_two) - - values_none = values[:] - values_none[2] = None - self.none = self.cls(*values_none) - - values_attrs = values[:] - values_attrs[4] = 10 - self.attributes = self.cls(*values_attrs) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes(self): - result = self.one < self.attributes - self.assertFalse(result) - result = self.attributes < self.one - self.assertFalse(result) - - -class Test_combine(tests.IrisTest): - def setUp(self): - self.cls = MeshCoordMetadata - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - location=sentinel.location, - axis=sentinel.axis, - ) - self.dummy = sentinel.dummy - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.combine.__doc__, self.cls.combine.__doc__ - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = right.copy() - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - -class Test_difference(tests.IrisTest): - def setUp(self): - self.cls = MeshCoordMetadata - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - location=sentinel.location, - axis=sentinel.axis, - ) - self.dummy = sentinel.dummy - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.difference.__doc__, self.cls.difference.__doc__ - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - member_value = getattr(lmetadata, member) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (member_value, None) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = (None, member_value) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_strict_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - -class Test_equal(tests.IrisTest): - def setUp(self): - self.cls = MeshCoordMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshMetadata.py b/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshMetadata.py deleted file mode 100644 index a8b25dc2e7..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/metadata/test_MeshMetadata.py +++ /dev/null @@ -1,783 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.experimental.ugrid.metadata.MeshMetadata`. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -import unittest.mock as mock -from unittest.mock import sentinel - -from iris.common.lenient import _LENIENT, _qualname -from iris.common.metadata import BaseMetadata -from iris.experimental.ugrid.metadata import MeshMetadata - - -class Test(tests.IrisTest): - def setUp(self): - self.standard_name = mock.sentinel.standard_name - self.long_name = mock.sentinel.long_name - self.var_name = mock.sentinel.var_name - self.units = mock.sentinel.units - self.attributes = mock.sentinel.attributes - self.topology_dimension = mock.sentinel.topology_dimension - self.node_dimension = mock.sentinel.node_dimension - self.edge_dimension = mock.sentinel.edge_dimension - self.face_dimension = mock.sentinel.face_dimension - self.cls = MeshMetadata - - def test_repr(self): - metadata = self.cls( - standard_name=self.standard_name, - long_name=self.long_name, - var_name=self.var_name, - units=self.units, - attributes=self.attributes, - topology_dimension=self.topology_dimension, - node_dimension=self.node_dimension, - edge_dimension=self.edge_dimension, - face_dimension=self.face_dimension, - ) - fmt = ( - "MeshMetadata(standard_name={!r}, long_name={!r}, " - "var_name={!r}, units={!r}, attributes={!r}, " - "topology_dimension={!r}, node_dimension={!r}, " - "edge_dimension={!r}, face_dimension={!r})" - ) - expected = fmt.format( - self.standard_name, - self.long_name, - self.var_name, - self.units, - self.attributes, - self.topology_dimension, - self.node_dimension, - self.edge_dimension, - self.face_dimension, - ) - self.assertEqual(expected, repr(metadata)) - - def test__fields(self): - expected = ( - "standard_name", - "long_name", - "var_name", - "units", - "attributes", - "topology_dimension", - "node_dimension", - "edge_dimension", - "face_dimension", - ) - self.assertEqual(self.cls._fields, expected) - - def test_bases(self): - self.assertTrue(issubclass(self.cls, BaseMetadata)) - - -class Test__eq__(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - topology_dimension=sentinel.topology_dimension, - node_dimension=sentinel.node_dimension, - edge_dimension=sentinel.edge_dimension, - face_dimension=sentinel.face_dimension, - ) - self.dummy = sentinel.dummy - self.cls = MeshMetadata - # The "node_dimension", "edge_dimension" and "face_dimension" members - # are stateful only; they do not participate in lenient/strict equivalence. - self.members_dim_names = filter( - lambda member: member - in ("node_dimension", "edge_dimension", "face_dimension"), - self.cls._members, - ) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.__eq__.__doc__, self.cls.__eq__.__doc__) - - def test_lenient_service(self): - qualname___eq__ = _qualname(self.cls.__eq__) - self.assertIn(qualname___eq__, _LENIENT) - self.assertTrue(_LENIENT[qualname___eq__]) - self.assertTrue(_LENIENT[self.cls.__eq__]) - - def test_call(self): - other = sentinel.other - return_value = sentinel.return_value - metadata = self.cls(*(None,) * len(self.cls._fields)) - with mock.patch.object( - BaseMetadata, "__eq__", return_value=return_value - ) as mocker: - result = metadata.__eq__(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_topology_dim_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["topology_dimension"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_same_dim_names_none(self): - for member in self.members_dim_names: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_topology_dim(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["topology_dimension"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_lenient_different_dim_names(self): - for member in self.members_dim_names: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_topology_dim(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["topology_dimension"] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_dim_names(self): - for member in self.members_dim_names: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_topology_dim_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["topology_dimension"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertFalse(lmetadata.__eq__(rmetadata)) - self.assertFalse(rmetadata.__eq__(lmetadata)) - - def test_op_strict_different_dim_names_none(self): - for member in self.members_dim_names: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertTrue(lmetadata.__eq__(rmetadata)) - self.assertTrue(rmetadata.__eq__(lmetadata)) - - -class Test___lt__(tests.IrisTest): - def setUp(self): - self.cls = MeshMetadata - self.one = self.cls(1, 1, 1, 1, 1, 1, 1, 1, 1) - self.two = self.cls(1, 1, 1, 2, 1, 1, 1, 1, 1) - self.none = self.cls(1, 1, 1, None, 1, 1, 1, 1, 1) - self.attributes = self.cls(1, 1, 1, 1, 10, 1, 1, 1, 1) - - def test__ascending_lt(self): - result = self.one < self.two - self.assertTrue(result) - - def test__descending_lt(self): - result = self.two < self.one - self.assertFalse(result) - - def test__none_rhs_operand(self): - result = self.one < self.none - self.assertFalse(result) - - def test__none_lhs_operand(self): - result = self.none < self.one - self.assertTrue(result) - - def test__ignore_attributes(self): - result = self.one < self.attributes - self.assertFalse(result) - result = self.attributes < self.one - self.assertFalse(result) - - -class Test_combine(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - topology_dimension=sentinel.topology_dimension, - node_dimension=sentinel.node_dimension, - edge_dimension=sentinel.edge_dimension, - face_dimension=sentinel.face_dimension, - ) - self.dummy = sentinel.dummy - self.cls = MeshMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.combine.__doc__, self.cls.combine.__doc__ - ) - - def test_lenient_service(self): - qualname_combine = _qualname(self.cls.combine) - self.assertIn(qualname_combine, _LENIENT) - self.assertTrue(_LENIENT[qualname_combine]) - self.assertTrue(_LENIENT[self.cls.combine]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "combine", return_value=return_value - ) as mocker: - result = self.none.combine(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - expected = self.values - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = right.copy() - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["units"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - expected = self.values.copy() - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected["long_name"] = None - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual(expected, lmetadata.combine(rmetadata)._asdict()) - self.assertEqual(expected, rmetadata.combine(lmetadata)._asdict()) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - expected = self.values.copy() - expected[member] = None - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - expected, lmetadata.combine(rmetadata)._asdict() - ) - self.assertEqual( - expected, rmetadata.combine(lmetadata)._asdict() - ) - - -class Test_difference(tests.IrisTest): - def setUp(self): - self.values = dict( - standard_name=sentinel.standard_name, - long_name=sentinel.long_name, - var_name=sentinel.var_name, - units=sentinel.units, - attributes=sentinel.attributes, - topology_dimension=sentinel.topology_dimension, - node_dimension=sentinel.node_dimension, - edge_dimension=sentinel.edge_dimension, - face_dimension=sentinel.face_dimension, - ) - self.dummy = sentinel.dummy - self.cls = MeshMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual( - BaseMetadata.difference.__doc__, self.cls.difference.__doc__ - ) - - def test_lenient_service(self): - qualname_difference = _qualname(self.cls.difference) - self.assertIn(qualname_difference, _LENIENT) - self.assertTrue(_LENIENT[qualname_difference]) - self.assertTrue(_LENIENT[self.cls.difference]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "difference", return_value=return_value - ) as mocker: - result = self.none.difference(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - def test_op_lenient_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_none(self): - lmetadata = self.cls(**self.values) - right = self.values.copy() - right["var_name"] = None - rmetadata = self.cls(**right) - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_lenient_same_members_none(self): - for member in self.cls._members: - lmetadata = self.cls(**self.values) - member_value = getattr(lmetadata, member) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (member_value, None) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = (None, member_value) - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["units"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["units"] = (left["units"], right["units"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["units"] = lexpected["units"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=True): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_lenient_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=True - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_same(self): - lmetadata = self.cls(**self.values) - rmetadata = self.cls(**self.values) - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertIsNone(lmetadata.difference(rmetadata)) - self.assertIsNone(rmetadata.difference(lmetadata)) - - def test_op_strict_different(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = self.dummy - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_none(self): - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right["long_name"] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected["long_name"] = (left["long_name"], right["long_name"]) - rexpected = deepcopy(self.none)._asdict() - rexpected["long_name"] = lexpected["long_name"][::-1] - - with mock.patch("iris.common.metadata._LENIENT", return_value=False): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - def test_op_strict_different_members_none(self): - for member in self.cls._members: - left = self.values.copy() - lmetadata = self.cls(**left) - right = self.values.copy() - right[member] = None - rmetadata = self.cls(**right) - lexpected = deepcopy(self.none)._asdict() - lexpected[member] = (left[member], right[member]) - rexpected = deepcopy(self.none)._asdict() - rexpected[member] = lexpected[member][::-1] - - with mock.patch( - "iris.common.metadata._LENIENT", return_value=False - ): - self.assertEqual( - lexpected, lmetadata.difference(rmetadata)._asdict() - ) - self.assertEqual( - rexpected, rmetadata.difference(lmetadata)._asdict() - ) - - -class Test_equal(tests.IrisTest): - def setUp(self): - self.cls = MeshMetadata - self.none = self.cls(*(None,) * len(self.cls._fields)) - - def test_wraps_docstring(self): - self.assertEqual(BaseMetadata.equal.__doc__, self.cls.equal.__doc__) - - def test_lenient_service(self): - qualname_equal = _qualname(self.cls.equal) - self.assertIn(qualname_equal, _LENIENT) - self.assertTrue(_LENIENT[qualname_equal]) - self.assertTrue(_LENIENT[self.cls.equal]) - - def test_lenient_default(self): - other = sentinel.other - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=None), kwargs) - - def test_lenient(self): - other = sentinel.other - lenient = sentinel.lenient - return_value = sentinel.return_value - with mock.patch.object( - BaseMetadata, "equal", return_value=return_value - ) as mocker: - result = self.none.equal(other, lenient=lenient) - - self.assertEqual(return_value, result) - self.assertEqual(1, mocker.call_count) - (arg,), kwargs = mocker.call_args - self.assertEqual(other, arg) - self.assertEqual(dict(lenient=lenient), kwargs) - - if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/experimental/ugrid/utils/__init__.py b/lib/iris/tests/unit/experimental/ugrid/utils/__init__.py deleted file mode 100644 index 135d7ee49c..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/utils/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.experimental.ugrid.utils` package.""" diff --git a/lib/iris/tests/unit/experimental/ugrid/utils/test_recombine_submeshes.py b/lib/iris/tests/unit/experimental/ugrid/utils/test_recombine_submeshes.py deleted file mode 100644 index 4face700ad..0000000000 --- a/lib/iris/tests/unit/experimental/ugrid/utils/test_recombine_submeshes.py +++ /dev/null @@ -1,437 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.experimental.ugrid.utils.recombine_submeshes`. - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import dask.array as da -import numpy as np - -from iris.coords import AuxCoord -from iris.cube import CubeList -from iris.experimental.ugrid.utils import recombine_submeshes -from iris.tests.stock.mesh import sample_mesh, sample_mesh_cube - - -def common_test_setup(self, shape_3d=(0, 2), data_chunks=None): - # Construct a basic testcase with all-lazy mesh_cube and submesh_cubes - # full-mesh cube shape is 'shape_3d' - # data_chunks sets chunking of source cube, (else all-1-chunk) - n_outer, n_z = shape_3d - n_mesh = 20 - mesh = sample_mesh(n_nodes=20, n_edges=0, n_faces=n_mesh) - mesh_cube = sample_mesh_cube(n_z=n_z, mesh=mesh) - # Fix index-coord name to the expected default for recombine_submeshes. - mesh_cube.coord("i_mesh_face").rename("i_mesh_index") - if n_outer: - # Crudely merge a set of copies to build an outer dimension. - mesh_cube.add_aux_coord(AuxCoord([0], long_name="outer")) - meshcubes_2d = [] - for i_outer in range(n_outer): - cube = mesh_cube.copy() - cube.coord("outer").points = np.array([i_outer]) - meshcubes_2d.append(cube) - mesh_cube = CubeList(meshcubes_2d).merge_cube() - - if not data_chunks: - data_chunks = mesh_cube.shape[:-1] + (-1,) - mesh_cube.data = da.zeros(mesh_cube.shape, chunks=data_chunks) - - n_regions = 4 # it doesn't divide neatly - region_len = n_mesh // n_regions - i_points = np.arange(n_mesh) - region_inds = [ - np.where((i_points // region_len) == i_region) - for i_region in range(n_regions) - ] - # Disturb slightly to ensure some gaps + some overlaps - region_inds = [list(indarr[0]) for indarr in region_inds] - region_inds[2] = region_inds[2][:-2] # missing points - region_inds[3] += region_inds[1][:2] # duplicates - self.mesh_cube = mesh_cube - self.region_inds = region_inds - self.region_cubes = [mesh_cube[..., inds] for inds in region_inds] - for i_cube, cube in enumerate(self.region_cubes): - for i_z in range(n_z): - # Set data='z' ; don't vary over other dimensions. - cube.data[..., i_z, :] = i_cube + 1000 * i_z + 1 - cube.data = cube.lazy_data() - - # Also construct an array to match the expected result (2d cases only). - # basic layer showing region allocation (large -ve values for missing) - expected = np.array( - [1.0, 1, 1, 1, 1] - + [4, 4] # points in #1 overlapped by #3 - + [2, 2, 2] - + [3, 3, 3] - + [-99999, -99999] # missing points - + [4, 4, 4, 4, 4] - ) - # second layer should be same but +1000. - # NOTE: only correct if shape_3d=None; no current need to generalise this. - expected = np.stack([expected, expected + 1000]) - # convert to masked array with missing points. - expected = np.ma.masked_less(expected, 0) - self.expected_result = expected - - -class TestRecombine__data(tests.IrisTest): - def setUp(self): - common_test_setup(self) - - def test_basic(self): - # Just confirm that all source data is lazy (by default) - for cube in self.region_cubes + [self.mesh_cube]: - self.assertTrue(cube.has_lazy_data()) - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertTrue(result.has_lazy_data()) - self.assertMaskedArrayEqual(result.data, self.expected_result) - - def test_chunking(self): - # Make non-standard testcube with higher dimensions + specific chunking - common_test_setup(self, shape_3d=(10, 3), data_chunks=(3, 2, -1)) - self.assertEqual(self.mesh_cube.lazy_data().chunksize, (3, 2, 20)) - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - # Check that the result chunking matches the input. - self.assertEqual(result.lazy_data().chunksize, (3, 2, 20)) - - def test_single_region(self): - region = self.region_cubes[1] - result = recombine_submeshes(self.mesh_cube, [region]) - # Construct a snapshot of the expected result. - # basic layer showing region allocation (large -ve values for missing) - expected = np.ma.masked_array(np.zeros(self.mesh_cube.shape), True) - inds = region.coord("i_mesh_index").points - expected[..., inds] = region.data - self.assertMaskedArrayEqual(result.data, expected) - - def test_region_overlaps(self): - # generate two identical regions with different values. - region1 = self.region_cubes[2] - region1.data[:] = 101.0 - inds = region1.coord("i_mesh_index").points - region2 = region1.copy() - region2.data[:] = 202.0 - # check that result values all come from the second. - result1 = recombine_submeshes(self.mesh_cube, [region1, region2]) - result1 = result1[..., inds].data - self.assertArrayEqual(result1, 202.0) - # swap the region order, and it should resolve the other way. - result2 = recombine_submeshes(self.mesh_cube, [region2, region1]) - result2 = result2[..., inds].data - self.assertArrayEqual(result2, 101.0) - - def test_missing_points(self): - # check results with and without a specific region included. - region2 = self.region_cubes[2] - inds = region2.coord("i_mesh_index").points - # With all regions, no points in reg1 are masked - result_all = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertTrue(np.all(~result_all[..., inds].data.mask)) - # Without region1, all points in reg1 are masked - regions_not2 = [ - cube for cube in self.region_cubes if cube is not region2 - ] - result_not2 = recombine_submeshes(self.mesh_cube, regions_not2) - self.assertTrue(np.all(result_not2[..., inds].data.mask)) - - def test_transposed(self): - # Check function when mesh-dim is NOT the last dim. - self.mesh_cube.transpose() - self.assertEqual(self.mesh_cube.mesh_dim(), 0) - for cube in self.region_cubes: - cube.transpose() - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertTrue(result.has_lazy_data()) - self.assertEqual(result.mesh_dim(), 0) - self.assertMaskedArrayEqual( - result.data.transpose(), self.expected_result - ) - - def test_dtype(self): - # Check that result dtype comes from submeshes, not mesh_cube. - self.assertEqual(self.mesh_cube.dtype, np.float64) - self.assertTrue( - all(cube.dtype == np.float64 for cube in self.region_cubes) - ) - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertEqual(result.dtype, np.float64) - region_cubes2 = [ - cube.copy(data=cube.lazy_data().astype(np.int16)) - for cube in self.region_cubes - ] - result2 = recombine_submeshes(self.mesh_cube, region_cubes2) - self.assertEqual(result2.dtype, np.int16) - - def test_meshcube_real(self): - # Real data in reference 'mesh_cube' makes no difference. - self.mesh_cube.data - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertTrue(result.has_lazy_data()) - self.assertMaskedArrayEqual(result.data, self.expected_result) - - def test_regions_real(self): - # Real data in submesh cubes makes no difference. - for cube in self.region_cubes: - cube.data - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertTrue(result.has_lazy_data()) - self.assertMaskedArrayEqual(result.data, self.expected_result) - - def test_allinput_real(self): - # Real data in reference AND regions still makes no difference. - self.mesh_cube.data - for cube in self.region_cubes: - cube.data - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertTrue(result.has_lazy_data()) - self.assertMaskedArrayEqual(result.data, self.expected_result) - - def test_meshcube_masking(self): - # Masked points in the reference 'mesh_cube' should make no difference. - # get real data : copy as default is not writeable - data = self.mesh_cube.data.copy() - # mask all - data[:] = np.ma.masked # all masked - # put back - self.mesh_cube.data = data # put back real array - # recast as lazy - self.mesh_cube.data = self.mesh_cube.lazy_data() # remake as lazy - # result should show no difference - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertMaskedArrayEqual(result.data, self.expected_result) - - def test_no_missing_results(self): - # For a result with no missing points, result array is still masked - # get real data : copy as default is not writeable - data = self.mesh_cube.data.copy() - # set all - data[:] = 7.777 - # put back - self.mesh_cube.data = data # put back real array - # recast as lazy - self.mesh_cube.data = self.mesh_cube.lazy_data() # remake as lazy - - # get result including original full-mesh - region_cubes = [self.mesh_cube] + self.region_cubes - result = recombine_submeshes(self.mesh_cube, region_cubes) - result = result.data - # result is as "normal" expected, except at the usually-missing points. - expected = self.expected_result - expected[expected.mask] = 7.777 - self.assertArrayEqual(result, expected) - # the actual result array is still masked, though with no masked points - self.assertIsInstance(result, np.ma.MaskedArray) - self.assertIsInstance(result.mask, np.ndarray) - self.assertArrayEqual(result.mask, False) - - def test_maskeddata(self): - # Check that masked points within regions behave like ordinary values. - # NB use overlap points - # reg[1][0:2] == reg[3][5:7], but points in reg[3] dominate - for cube in self.region_cubes: - cube.data = np.ma.masked_array(cube.data) # ensure masked arrays - self.region_cubes[0].data[:, 0] = np.ma.masked # result-index =5 - self.region_cubes[1].data[:, 0] = np.ma.masked # result-index =5 - self.region_cubes[3].data[:, 6] = np.ma.masked # result-index =6 - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - result = result.data - expected = self.expected_result - expected[:, 0] = np.ma.masked - expected[:, 6] = np.ma.masked - self.assertArrayEqual(result.mask, expected.mask) - - def test_nandata(self): - # Check that NaN points within regions behave like ordinary values. - # Duplicate of previous test, replacing masks with NaNs - self.region_cubes[0].data[:, 0] = np.nan - self.region_cubes[1].data[:, 0] = np.nan - self.region_cubes[3].data[:, 6] = np.nan - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - result = result.data - expected = self.expected_result - expected[:, 0] = np.nan - expected[:, 6] = np.nan - self.assertArrayEqual(np.isnan(result), np.isnan(expected)) - - -class TestRecombine__api(tests.IrisTest): - def setUp(self): - common_test_setup(self) - - def test_fail_no_mesh(self): - self.mesh_cube = self.mesh_cube[..., 0:] - with self.assertRaisesRegex(ValueError, 'mesh_cube.*has no ".mesh"'): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_single_region(self): - # Check that a single region-cube can replace a list. - single_region = self.region_cubes[0] - result1 = recombine_submeshes(self.mesh_cube, single_region) - result2 = recombine_submeshes(self.mesh_cube, [single_region]) - self.assertEqual(result1, result2) - - def test_fail_no_regions(self): - with self.assertRaisesRegex( - ValueError, "'submesh_cubes' must be non-empty" - ): - recombine_submeshes(self.mesh_cube, []) - - def test_fail_dims_mismatch_mesh_regions(self): - self.mesh_cube = self.mesh_cube[0] - with self.assertRaisesRegex( - ValueError, "Submesh cube.*has 2 dimensions, but 'mesh_cube' has 1" - ): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_dims_mismatch_region_regions(self): - self.region_cubes[1] = self.region_cubes[1][1] - with self.assertRaisesRegex( - ValueError, "Submesh cube.*has 1 dimensions, but 'mesh_cube' has 2" - ): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_metdata_mismatch_region_regions(self): - reg_cube = self.region_cubes[1] - modded_cube = reg_cube.copy() - modded_cube.long_name = "qq" - self.region_cubes[1] = modded_cube - msg = ( - 'Submesh cube #2/4, "qq" has metadata.*long_name=qq.*' - "does not match that of the other region_cubes,.*" - "long_name=mesh_phenom" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - # Also check units - modded_cube = reg_cube.copy() - modded_cube.units = "m" - self.region_cubes[1] = modded_cube - msg = ( - "metadata.*units=m.*" - "does not match that of the other region_cubes,.*" - "units=unknown" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - # Also check attributes - modded_cube = reg_cube.copy() - modded_cube.attributes["tag"] = "x" - self.region_cubes[1] = modded_cube - msg = ( - "units=unknown, attributes={'tag': 'x'}, cell_methods=.*" - "does not match that of the other region_cubes,.*" - "units=unknown, cell_methods=" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_dtype_mismatch_region_regions(self): - reg_cube = self.region_cubes[1] - reg_cube.data = reg_cube.data.astype(np.int16) - msg = ( - "Submesh cube #2/4.*has a dtype of int16, " - "which does not match that of the other region_cubes, " - "which is float64" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_dimcoord_sub_no_mesh(self): - self.mesh_cube.remove_coord("level") - msg = ( - 'has a dim-coord "level" for dimension 0, ' - "but 'mesh_cube' has none." - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_dimcoord_mesh_no_sub(self): - self.region_cubes[2].remove_coord("level") - msg = ( - "has no dim-coord for dimension 0, " - "to match the 'mesh_cube' dimension \"level\"" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_dimcoord_mesh_sub_differ(self): - dimco = self.mesh_cube.coord("level") - dimco.points = dimco.points[::-1] - msg = ( - 'has a dim-coord "level" for dimension 0, ' - "which does not match that of 'mesh_cube', \"level\"" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_index_coordname(self): - # Check that we can use different index coord names. - for cube in self.region_cubes: - cube.coord("i_mesh_index").rename("ii") - result = recombine_submeshes( - self.mesh_cube, self.region_cubes, index_coord_name="ii" - ) - self.assertArrayEqual(result.data, self.expected_result) - - def test_fail_bad_indexcoord_name(self): - self.region_cubes[2].coord("i_mesh_index").rename("ii") - msg = ( - 'Submesh cube #3/4, "mesh_phenom" has no "i_mesh_index" coord ' - r"on the mesh dimension \(dimension 1\)." - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_missing_indexcoord(self): - self.region_cubes[1].remove_coord("i_mesh_index") - msg = ( - 'Submesh cube #2/4, "mesh_phenom" has no "i_mesh_index" coord ' - r"on the mesh dimension \(dimension 1\)." - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_no_mesh_indexcoord(self): - # It is ok for the mesh-cube to NOT have an index-coord. - self.mesh_cube.remove_coord("i_mesh_index") - result = recombine_submeshes(self.mesh_cube, self.region_cubes) - self.assertArrayEqual(result.data, self.expected_result) - - def test_fail_indexcoord_mismatch_mesh_region(self): - self.mesh_cube.coord("i_mesh_index").units = "m" - msg = ( - 'Submesh cube #1/4, "mesh_phenom" has an index coord ' - '"i_mesh_index" whose ".metadata" does not match that of ' - "the same name in 'mesh_cube'" - ".*units=1.* != .*units=m" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - def test_fail_indexcoord_mismatch_region_region(self): - self.mesh_cube.remove_coord("i_mesh_index") - self.region_cubes[2].coord("i_mesh_index").attributes["x"] = 3 - msg = ( - 'Submesh cube #3/4, "mesh_phenom" has an index coord ' - '"i_mesh_index" whose ".metadata" does not match ' - "that of the other submesh-cubes" - ".*units=1, attributes={'x': 3}, climatological.*" - " != .*units=1, climatological" - ) - with self.assertRaisesRegex(ValueError, msg): - recombine_submeshes(self.mesh_cube, self.region_cubes) - - -if __name__ == "__main__": - # Make it runnable in its own right. - tests.main() diff --git a/lib/iris/tests/unit/fileformats/__init__.py b/lib/iris/tests/unit/fileformats/__init__.py deleted file mode 100644 index fa31283c87..0000000000 --- a/lib/iris/tests/unit/fileformats/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats` package.""" - -import iris.tests as tests # isort:skip - - -class TestField(tests.IrisTest): - def _test_for_coord( - self, field, convert, coord_predicate, expected_points, expected_bounds - ): - ( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) = convert(field) - - # Check for one and only one matching coordinate. - coords_and_dims = dim_coords_and_dims + aux_coords_and_dims - matching_coords = [ - coord for coord, _ in coords_and_dims if coord_predicate(coord) - ] - self.assertEqual(len(matching_coords), 1, str(matching_coords)) - coord = matching_coords[0] - - # Check points and bounds. - if expected_points is not None: - self.assertArrayEqual(coord.points, expected_points) - - if expected_bounds is None: - self.assertIsNone(coord.bounds) - else: - self.assertArrayEqual(coord.bounds, expected_bounds) - - def assertCoordsAndDimsListsMatch( - self, coords_and_dims_got, coords_and_dims_expected - ): - """ - Check that coords_and_dims lists are equivalent. - - The arguments are lists of pairs of (coordinate, dimensions). - The elements are compared one-to-one, by coordinate name (so the order - of the lists is _not_ significant). - It also checks that the coordinate types (DimCoord/AuxCoord) match. - - """ - - def sorted_by_coordname(list): - return sorted(list, key=lambda item: item[0].name()) - - coords_and_dims_got = sorted_by_coordname(coords_and_dims_got) - coords_and_dims_expected = sorted_by_coordname( - coords_and_dims_expected - ) - self.assertEqual(coords_and_dims_got, coords_and_dims_expected) - # Also check coordinate type equivalences (as Coord.__eq__ does not). - self.assertEqual( - [type(coord) for coord, dims in coords_and_dims_got], - [type(coord) for coord, dims in coords_and_dims_expected], - ) diff --git a/lib/iris/tests/unit/fileformats/abf/__init__.py b/lib/iris/tests/unit/fileformats/abf/__init__.py deleted file mode 100644 index aaddf427c5..0000000000 --- a/lib/iris/tests/unit/fileformats/abf/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.abf` module.""" diff --git a/lib/iris/tests/unit/fileformats/abf/test_ABFField.py b/lib/iris/tests/unit/fileformats/abf/test_ABFField.py deleted file mode 100644 index 98db52d3e9..0000000000 --- a/lib/iris/tests/unit/fileformats/abf/test_ABFField.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.abf.ABFField` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats.abf import ABFField - - -class MethodCounter: - def __init__(self, method_name): - self.method_name = method_name - self.count = 0 - - def __enter__(self): - self.orig_method = getattr(ABFField, self.method_name) - - def new_method(*args, **kwargs): - self.count += 1 - self.orig_method(*args, **kwargs) - - setattr(ABFField, self.method_name, new_method) - return self - - def __exit__(self, exc_type, exc_value, traceback): - setattr(ABFField, self.method_name, self.orig_method) - return False - - -class Test_data(tests.IrisTest): - def test_single_read(self): - path = "0000000000000000jan00000" - field = ABFField(path) - - with mock.patch("iris.fileformats.abf.np.fromfile") as fromfile: - with MethodCounter("__getattr__") as getattr: - with MethodCounter("_read") as read: - field.data - - fromfile.assert_called_once_with(path, dtype=">u1") - self.assertEqual(getattr.count, 1) - self.assertEqual(read.count, 1) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/cf/__init__.py b/lib/iris/tests/unit/fileformats/cf/__init__.py deleted file mode 100644 index 1bff79368b..0000000000 --- a/lib/iris/tests/unit/fileformats/cf/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.cf` module.""" diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py b/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py deleted file mode 100644 index bfc2d586ef..0000000000 --- a/lib/iris/tests/unit/fileformats/cf/test_CFGroup.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.fileformats.cf.CFGroup` class.""" - -from unittest.mock import MagicMock - -from iris.fileformats.cf import ( - CFAuxiliaryCoordinateVariable, - CFCoordinateVariable, - CFDataVariable, - CFGroup, -) - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests - - -class Tests(tests.IrisTest): - # TODO: unit tests for existing functionality pre 2021-03-11. - def setUp(self): - self.cf_group = CFGroup() - - def test_non_data_names(self): - data_var = MagicMock(spec=CFDataVariable, cf_name="data_var") - aux_var = MagicMock( - spec=CFAuxiliaryCoordinateVariable, cf_name="aux_var" - ) - coord_var = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var") - coord_var2 = MagicMock(spec=CFCoordinateVariable, cf_name="coord_var2") - duplicate_name_var = MagicMock( - spec=CFCoordinateVariable, cf_name="aux_var" - ) - - for var in ( - data_var, - aux_var, - coord_var, - coord_var2, - duplicate_name_var, - ): - self.cf_group[var.cf_name] = var - - expected_names = [ - var.cf_name for var in (aux_var, coord_var, coord_var2) - ] - expected = set(expected_names) - self.assertEqual(expected, self.cf_group.non_data_variable_names) diff --git a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py b/lib/iris/tests/unit/fileformats/cf/test_CFReader.py deleted file mode 100644 index 9e5cf9b7a5..0000000000 --- a/lib/iris/tests/unit/fileformats/cf/test_CFReader.py +++ /dev/null @@ -1,371 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the `iris.fileformats.cf.CFReader` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.fileformats.cf import CFReader - - -def netcdf_variable( - name, - dimensions, - dtype, - ancillary_variables=None, - coordinates="", - bounds=None, - climatology=None, - formula_terms=None, - grid_mapping=None, - cell_measures=None, - standard_name=None, -): - """Return a mock NetCDF4 variable.""" - ndim = 0 - if dimensions is not None: - dimensions = dimensions.split() - ndim = len(dimensions) - else: - dimensions = [] - ncvar = mock.Mock( - name=name, - dimensions=dimensions, - ncattrs=mock.Mock(return_value=[]), - ndim=ndim, - dtype=dtype, - ancillary_variables=ancillary_variables, - coordinates=coordinates, - bounds=bounds, - climatology=climatology, - formula_terms=formula_terms, - grid_mapping=grid_mapping, - cell_measures=cell_measures, - standard_name=standard_name, - ) - return ncvar - - -class Test_translate__global_attributes(tests.IrisTest): - def setUp(self): - ncvar = netcdf_variable("ncvar", "height", np.float64) - ncattrs = mock.Mock(return_value=["dimensions"]) - getncattr = mock.Mock(return_value="something something_else") - self.dataset = mock.Mock( - file_format="NetCDF4", - variables={"ncvar": ncvar}, - ncattrs=ncattrs, - getncattr=getncattr, - ) - - def test_create_global_attributes(self): - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - global_attrs = CFReader("dummy").cf_group.global_attributes - self.assertEqual( - global_attrs["dimensions"], "something something_else" - ) - - -class Test_translate__formula_terms(tests.IrisTest): - def setUp(self): - self.delta = netcdf_variable( - "delta", "height", np.float64, bounds="delta_bnds" - ) - self.delta_bnds = netcdf_variable( - "delta_bnds", "height bnds", np.float64 - ) - self.sigma = netcdf_variable( - "sigma", "height", np.float64, bounds="sigma_bnds" - ) - self.sigma_bnds = netcdf_variable( - "sigma_bnds", "height bnds", np.float64 - ) - self.orography = netcdf_variable("orography", "lat lon", np.float64) - formula_terms = "a: delta b: sigma orog: orography" - standard_name = "atmosphere_hybrid_height_coordinate" - self.height = netcdf_variable( - "height", - "height", - np.float64, - formula_terms=formula_terms, - bounds="height_bnds", - standard_name=standard_name, - ) - # Over-specify the formula terms on the bounds variable, - # which will be ignored by the cf loader. - formula_terms = "a: delta_bnds b: sigma_bnds orog: orography" - self.height_bnds = netcdf_variable( - "height_bnds", - "height bnds", - np.float64, - formula_terms=formula_terms, - ) - self.lat = netcdf_variable("lat", "lat", np.float64) - self.lon = netcdf_variable("lon", "lon", np.float64) - # Note that, only lat and lon are explicitly associated as coordinates. - self.temp = netcdf_variable( - "temp", "height lat lon", np.float64, coordinates="lat lon" - ) - - self.variables = dict( - delta=self.delta, - sigma=self.sigma, - orography=self.orography, - height=self.height, - lat=self.lat, - lon=self.lon, - temp=self.temp, - delta_bnds=self.delta_bnds, - sigma_bnds=self.sigma_bnds, - height_bnds=self.height_bnds, - ) - ncattrs = mock.Mock(return_value=[]) - self.dataset = mock.Mock( - file_format="NetCDF4", variables=self.variables, ncattrs=ncattrs - ) - # Restrict the CFReader functionality to only performing translations. - build_patch = mock.patch( - "iris.fileformats.cf.CFReader._build_cf_groups" - ) - reset_patch = mock.patch("iris.fileformats.cf.CFReader._reset") - build_patch.start() - reset_patch.start() - self.addCleanup(build_patch.stop) - self.addCleanup(reset_patch.stop) - - def test_create_formula_terms(self): - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check there is a singular data variable. - group = cf_group.data_variables - self.assertEqual(len(group), 1) - self.assertEqual(list(group.keys()), ["temp"]) - self.assertIs(group["temp"].cf_data, self.temp) - # Check there are three coordinates. - group = cf_group.coordinates - self.assertEqual(len(group), 3) - coordinates = ["height", "lat", "lon"] - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check there are three auxiliary coordinates. - group = cf_group.auxiliary_coordinates - self.assertEqual(len(group), 3) - aux_coordinates = ["delta", "sigma", "orography"] - self.assertEqual(set(group.keys()), set(aux_coordinates)) - for name in aux_coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check all the auxiliary coordinates are formula terms. - formula_terms = cf_group.formula_terms - self.assertEqual(set(group.items()), set(formula_terms.items())) - # Check there are three bounds. - group = cf_group.bounds - self.assertEqual(len(group), 3) - bounds = ["height_bnds", "delta_bnds", "sigma_bnds"] - self.assertEqual(set(group.keys()), set(bounds)) - for name in bounds: - self.assertEqual(group[name].cf_data, getattr(self, name)) - - -class Test_build_cf_groups__formula_terms(tests.IrisTest): - def setUp(self): - self.delta = netcdf_variable( - "delta", "height", np.float64, bounds="delta_bnds" - ) - self.delta_bnds = netcdf_variable( - "delta_bnds", "height bnds", np.float64 - ) - self.sigma = netcdf_variable( - "sigma", "height", np.float64, bounds="sigma_bnds" - ) - self.sigma_bnds = netcdf_variable( - "sigma_bnds", "height bnds", np.float64 - ) - self.orography = netcdf_variable("orography", "lat lon", np.float64) - formula_terms = "a: delta b: sigma orog: orography" - standard_name = "atmosphere_hybrid_height_coordinate" - self.height = netcdf_variable( - "height", - "height", - np.float64, - formula_terms=formula_terms, - bounds="height_bnds", - standard_name=standard_name, - ) - # Over-specify the formula terms on the bounds variable, - # which will be ignored by the cf loader. - formula_terms = "a: delta_bnds b: sigma_bnds orog: orography" - self.height_bnds = netcdf_variable( - "height_bnds", - "height bnds", - np.float64, - formula_terms=formula_terms, - ) - self.lat = netcdf_variable("lat", "lat", np.float64) - self.lon = netcdf_variable("lon", "lon", np.float64) - self.x = netcdf_variable("x", "lat lon", np.float64) - self.y = netcdf_variable("y", "lat lon", np.float64) - # Note that, only lat and lon are explicitly associated as coordinates. - self.temp = netcdf_variable( - "temp", "height lat lon", np.float64, coordinates="x y" - ) - - self.variables = dict( - delta=self.delta, - sigma=self.sigma, - orography=self.orography, - height=self.height, - lat=self.lat, - lon=self.lon, - temp=self.temp, - delta_bnds=self.delta_bnds, - sigma_bnds=self.sigma_bnds, - height_bnds=self.height_bnds, - x=self.x, - y=self.y, - ) - ncattrs = mock.Mock(return_value=[]) - self.dataset = mock.Mock( - file_format="NetCDF4", variables=self.variables, ncattrs=ncattrs - ) - # Restrict the CFReader functionality to only performing translations - # and building first level cf-groups for variables. - patcher = mock.patch("iris.fileformats.cf.CFReader._reset") - patcher.start() - self.addCleanup(patcher.stop) - - def test_associate_formula_terms_with_data_variable(self): - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check the cf-group associated with the data variable. - temp_cf_group = cf_group["temp"].cf_group - # Check the data variable is associated with eight variables. - self.assertEqual(len(temp_cf_group), 8) - # Check there are three coordinates. - group = temp_cf_group.coordinates - self.assertEqual(len(group), 3) - coordinates = ["height", "lat", "lon"] - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check the height coordinate is bounded. - group = group["height"].cf_group - self.assertEqual(len(group.bounds), 1) - self.assertIn("height_bnds", group.bounds) - self.assertIs(group["height_bnds"].cf_data, self.height_bnds) - # Check there are five auxiliary coordinates. - group = temp_cf_group.auxiliary_coordinates - self.assertEqual(len(group), 5) - aux_coordinates = ["delta", "sigma", "orography", "x", "y"] - self.assertEqual(set(group.keys()), set(aux_coordinates)) - for name in aux_coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - # Check all the auxiliary coordinates are formula terms. - formula_terms = cf_group.formula_terms - self.assertTrue( - set(formula_terms.items()).issubset(list(group.items())) - ) - # Check the terms by root. - for name, term in zip(aux_coordinates, ["a", "b", "orog"]): - self.assertEqual( - formula_terms[name].cf_terms_by_root, dict(height=term) - ) - # Check the bounded auxiliary coordinates. - for name, name_bnds in zip( - ["delta", "sigma"], ["delta_bnds", "sigma_bnds"] - ): - aux_coord_group = group[name].cf_group - self.assertEqual(len(aux_coord_group.bounds), 1) - self.assertIn(name_bnds, aux_coord_group.bounds) - self.assertIs( - aux_coord_group[name_bnds].cf_data, - getattr(self, name_bnds), - ) - - def test_promote_reference(self): - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ): - cf_group = CFReader("dummy").cf_group - self.assertEqual(len(cf_group), len(self.variables)) - # Check the number of data variables. - self.assertEqual(len(cf_group.data_variables), 1) - self.assertEqual(list(cf_group.data_variables.keys()), ["temp"]) - # Check the number of promoted variables. - self.assertEqual(len(cf_group.promoted), 1) - self.assertEqual(list(cf_group.promoted.keys()), ["orography"]) - # Check the promoted variable dependencies. - group = cf_group.promoted["orography"].cf_group.coordinates - self.assertEqual(len(group), 2) - coordinates = ("lat", "lon") - self.assertEqual(set(group.keys()), set(coordinates)) - for name in coordinates: - self.assertIs(group[name].cf_data, getattr(self, name)) - - def test_formula_terms_ignore(self): - self.orography.dimensions = ["lat", "wibble"] - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), mock.patch("warnings.warn") as warn: - cf_group = CFReader("dummy").cf_group - group = cf_group.promoted - self.assertEqual(list(group.keys()), ["orography"]) - self.assertIs(group["orography"].cf_data, self.orography) - self.assertEqual(warn.call_count, 1) - - def test_auxiliary_ignore(self): - self.x.dimensions = ["lat", "wibble"] - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), mock.patch("warnings.warn") as warn: - cf_group = CFReader("dummy").cf_group - promoted = ["x", "orography"] - group = cf_group.promoted - self.assertEqual(set(group.keys()), set(promoted)) - for name in promoted: - self.assertIs(group[name].cf_data, getattr(self, name)) - self.assertEqual(warn.call_count, 1) - - def test_promoted_auxiliary_ignore(self): - self.wibble = netcdf_variable("wibble", "lat wibble", np.float64) - self.variables["wibble"] = self.wibble - self.orography.coordinates = "wibble" - with mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper", - return_value=self.dataset, - ), mock.patch("warnings.warn") as warn: - cf_group = CFReader("dummy").cf_group.promoted - promoted = ["wibble", "orography"] - self.assertEqual(set(cf_group.keys()), set(promoted)) - for name in promoted: - self.assertIs(cf_group[name].cf_data, getattr(self, name)) - self.assertEqual(warn.call_count, 2) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/dot/__init__.py b/lib/iris/tests/unit/fileformats/dot/__init__.py deleted file mode 100644 index 0dbc3ad4c6..0000000000 --- a/lib/iris/tests/unit/fileformats/dot/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :mod:`iris.fileformats.dot`.""" diff --git a/lib/iris/tests/unit/fileformats/dot/test__dot_path.py b/lib/iris/tests/unit/fileformats/dot/test__dot_path.py deleted file mode 100644 index 1111e8bc83..0000000000 --- a/lib/iris/tests/unit/fileformats/dot/test__dot_path.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :func:`iris.fileformats.dot._dot_path`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import os.path -import subprocess -from unittest import mock - -from iris.fileformats.dot import _DOT_EXECUTABLE_PATH, _dot_path - - -class Test(tests.IrisTest): - def setUp(self): - # Because _dot_path is triggered by the initial import we - # reset the caching status to allow us to see what happens - # under different circumstances. - self.patch("iris.fileformats.dot._DOT_CHECKED", new=False) - # Also patch the private path variable to the existing value (i.e. no - # change), and restore it after each test: As these tests modify it, - # that can potentially break subsequent 'normal' behaviour. - self.patch( - "iris.fileformats.dot._DOT_EXECUTABLE_PATH", _DOT_EXECUTABLE_PATH - ) - - def test_valid_absolute_path(self): - # Override the configuration value for System.dot_path - real_path = os.path.abspath(__file__) - assert os.path.exists(real_path) and os.path.isabs(real_path) - with mock.patch("iris.config.get_option", return_value=real_path): - result = _dot_path() - self.assertEqual(result, real_path) - - def test_invalid_absolute_path(self): - # Override the configuration value for System.dot_path - dummy_path = "/not_a_real_path" * 10 - assert not os.path.exists(dummy_path) - with mock.patch("iris.config.get_option", return_value=dummy_path): - result = _dot_path() - self.assertIsNone(result) - - def test_valid_relative_path(self): - # Override the configuration value for System.dot_path - dummy_path = "not_a_real_path" * 10 - assert not os.path.exists(dummy_path) - with mock.patch("iris.config.get_option", return_value=dummy_path): - # Pretend we have a valid installation of dot - with mock.patch("subprocess.check_output"): - result = _dot_path() - self.assertEqual(result, dummy_path) - - def test_valid_relative_path_broken_install(self): - # Override the configuration value for System.dot_path - dummy_path = "not_a_real_path" * 10 - assert not os.path.exists(dummy_path) - with mock.patch("iris.config.get_option", return_value=dummy_path): - # Pretend we have a broken installation of dot - error = subprocess.CalledProcessError(-5, "foo", "bar") - with mock.patch("subprocess.check_output", side_effect=error): - result = _dot_path() - self.assertIsNone(result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/ff/__init__.py b/lib/iris/tests/unit/fileformats/ff/__init__.py deleted file mode 100644 index 4d13a18520..0000000000 --- a/lib/iris/tests/unit/fileformats/ff/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.ff` module.""" diff --git a/lib/iris/tests/unit/fileformats/ff/test_ArakawaC.py b/lib/iris/tests/unit/fileformats/ff/test_ArakawaC.py deleted file mode 100644 index d37b854405..0000000000 --- a/lib/iris/tests/unit/fileformats/ff/test_ArakawaC.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.fileformat.ff.ArakawaC`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats._ff import ArakawaC - - -class Test__x_vectors(tests.IrisTest): - def _test(self, column, horiz_grid_type, xp, xu): - reals = np.arange(6) + 100 - grid = ArakawaC(column, None, reals, horiz_grid_type) - result_xp, result_xu = grid._x_vectors() - self.assertArrayEqual(result_xp, xp) - self.assertArrayEqual(result_xu, xu) - - def test_none(self): - self._test(column=None, horiz_grid_type=None, xp=None, xu=None) - - def test_1d(self): - self._test( - column=np.array([[0], [1], [2], [3]]), - horiz_grid_type=None, - xp=np.array([0, 1, 2, 3]), - xu=None, - ) - - def test_2d_no_wrap(self): - self._test( - column=np.array([[0, 0], [1, 10], [2, 20], [3, 30]]), - horiz_grid_type=1, - xp=np.array([0, 1, 2, 3]), - xu=np.array([0, 10, 20, 30]), - ) - - def test_2d_with_wrap(self): - self._test( - column=np.array([[0, 0], [1, 10], [2, 20], [3, 30]]), - horiz_grid_type=0, - xp=np.array([0, 1, 2, 3]), - xu=np.array([0, 10, 20]), - ) - - -class Test_regular_x(tests.IrisTest): - def _test(self, subgrid, bzx, bdx): - grid = ArakawaC(None, None, [4.0, None, None, -5.0, None, None], None) - result_bzx, result_bdx = grid.regular_x(subgrid) - self.assertEqual(result_bzx, bzx) - self.assertEqual(result_bdx, bdx) - - def test_theta_subgrid(self): - self._test(1, -9.0, 4.0) - - def test_u_subgrid(self): - self._test(11, -7.0, 4.0) - - -class Test_regular_y(tests.IrisTest): - def _test(self, v_offset, subgrid, bzy, bdy): - grid = ArakawaC(None, None, [None, 4.0, 45.0, None, None, None], None) - grid._v_offset = v_offset - result_bzy, result_bdy = grid.regular_y(subgrid) - self.assertEqual(result_bzy, bzy) - self.assertEqual(result_bdy, bdy) - - def test_theta_subgrid_NewDynamics(self): - self._test(0.5, 1, 41.0, 4.0) - - def test_v_subgrid_NewDynamics(self): - self._test(0.5, 11, 43.0, 4.0) - - def test_theta_subgrid_ENDGame(self): - self._test(-0.5, 1, 41.0, 4.0) - - def test_v_subgrid_ENDGame(self): - self._test(-0.5, 11, 39.0, 4.0) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/ff/test_ENDGame.py b/lib/iris/tests/unit/fileformats/ff/test_ENDGame.py deleted file mode 100644 index 696dacd672..0000000000 --- a/lib/iris/tests/unit/fileformats/ff/test_ENDGame.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.fileformat.ff.ENDGame`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats._ff import ENDGame - - -class Test(tests.IrisTest): - def test_class_attributes(self): - reals = np.arange(6) + 100 - grid = ENDGame(None, None, reals, None) - self.assertEqual(grid._v_offset, -0.5) - - -class Test__y_vectors(tests.IrisTest): - def _test(self, row, yp, yv): - reals = np.arange(6) + 100 - grid = ENDGame(None, row, reals, None) - result_yp, result_yv = grid._y_vectors() - self.assertArrayEqual(result_yp, yp) - self.assertArrayEqual(result_yv, yv) - - def test_none(self): - self._test(row=None, yp=None, yv=None) - - def test_1d(self): - self._test( - row=np.array([[0], [1], [2], [3]]), yp=np.array([0, 1, 2]), yv=None - ) - - def test_2d(self): - self._test( - row=np.array([[0, 0], [1, 10], [2, 20], [3, 30]]), - yp=np.array([0, 1, 2]), - yv=np.array([0, 10, 20, 30]), - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/ff/test_FF2PP.py b/lib/iris/tests/unit/fileformats/ff/test_FF2PP.py deleted file mode 100644 index cec4f53bc3..0000000000 --- a/lib/iris/tests/unit/fileformats/ff/test_FF2PP.py +++ /dev/null @@ -1,600 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :class:`iris.fileformat.ff.FF2PP` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import collections -import contextlib -from unittest import mock - -import numpy as np - -from iris.exceptions import NotYetImplementedError -import iris.fileformats._ff as ff -from iris.fileformats._ff import FF2PP -import iris.fileformats.pp as pp - -# PP-field: LBPACK N1 values. -_UNPACKED = 0 -_WGDOS = 1 -_CRAY = 2 - -# PP-field: LBUSER(1) values. -_REAL = 1 -_INTEGER = 2 - - -_DummyField = collections.namedtuple( - "_DummyField", "lbext lblrec lbnrec raw_lbpack " "lbuser boundary_packing" -) -_DummyFieldWithSize = collections.namedtuple( - "_DummyFieldWithSize", - "lbext lblrec lbnrec raw_lbpack " "lbuser boundary_packing " "lbnpt lbrow", -) -_DummyBoundaryPacking = collections.namedtuple( - "_DummyBoundaryPacking", "x_halo y_halo rim_width" -) - - -class Test____iter__(tests.IrisTest): - @mock.patch("iris.fileformats._ff.FFHeader") - def test_call_structure(self, _FFHeader): - # Check that the iter method calls the two necessary utility - # functions - extract_result = mock.Mock() - interpret_patch = mock.patch( - "iris.fileformats.pp._interpret_fields", - autospec=True, - return_value=iter([]), - ) - extract_patch = mock.patch( - "iris.fileformats._ff.FF2PP._extract_field", - autospec=True, - return_value=extract_result, - ) - - FF2PP_instance = ff.FF2PP("mock") - with interpret_patch as interpret, extract_patch as extract: - list(iter(FF2PP_instance)) - - interpret.assert_called_once_with(extract_result) - extract.assert_called_once_with(FF2PP_instance) - - -class Test__extract_field__LBC_format(tests.IrisTest): - @contextlib.contextmanager - def mock_for_extract_field(self, fields, x=None, y=None): - """ - A context manager to ensure FF2PP._extract_field gets a field - instance looking like the next one in the "fields" iterable from - the "make_pp_field" call. - - """ - with mock.patch("iris.fileformats._ff.FFHeader"): - ff2pp = ff.FF2PP("mock") - ff2pp._ff_header.lookup_table = [0, 0, len(fields)] - # Fake level constants, with shape specifying just one model-level. - ff2pp._ff_header.level_dependent_constants = np.zeros(1) - grid = mock.Mock() - grid.vectors = mock.Mock(return_value=(x, y)) - ff2pp._ff_header.grid = mock.Mock(return_value=grid) - - open_func = "builtins.open" - with mock.patch( - "iris.fileformats._ff._parse_binary_stream", return_value=[0] - ), mock.patch(open_func), mock.patch( - "struct.unpack_from", return_value=[4] - ), mock.patch( - "iris.fileformats.pp.make_pp_field", side_effect=fields - ), mock.patch( - "iris.fileformats._ff.FF2PP._payload", return_value=(0, 0) - ): - yield ff2pp - - def _mock_lbc(self, **kwargs): - """Return a Mock object representing an LBC field.""" - # Default kwargs for a valid LBC field mapping just 1 model-level. - field_kwargs = dict(lbtim=0, lblev=7777, lbvc=0, lbhem=101) - # Apply provided args (replacing any defaults if specified). - field_kwargs.update(kwargs) - # Return a mock with just those properties pre-defined. - return mock.Mock(**field_kwargs) - - def test_LBC_header(self): - bzx, bzy = -10, 15 - # stash m01s00i001 - lbuser = [None, None, 121416, 1, None, None, 1] - field = self._mock_lbc( - lbegin=0, - lbrow=10, - lbnpt=12, - bdx=1, - bdy=1, - bzx=bzx, - bzy=bzy, - lbuser=lbuser, - ) - with self.mock_for_extract_field([field]) as ff2pp: - ff2pp._ff_header.dataset_type = 5 - result = list(ff2pp._extract_field()) - - self.assertEqual([field], result) - self.assertEqual(field.lbrow, 10 + 14 * 2) - self.assertEqual(field.lbnpt, 12 + 16 * 2) - - name_mapping_dict = dict( - rim_width=slice(4, 6), y_halo=slice(2, 4), x_halo=slice(0, 2) - ) - boundary_packing = pp.SplittableInt(121416, name_mapping_dict) - - self.assertEqual(field.boundary_packing, boundary_packing) - self.assertEqual(field.bzy, bzy - boundary_packing.y_halo * field.bdy) - self.assertEqual(field.bzx, bzx - boundary_packing.x_halo * field.bdx) - - def check_non_trivial_coordinate_warning(self, field): - field.lbegin = 0 - field.lbrow = 10 - field.lbnpt = 12 - # stash m01s31i020 - field.lbuser = [None, None, 121416, 20, None, None, 1] - orig_bdx, orig_bdy = field.bdx, field.bdy - - x = np.array([1, 2, 6]) - y = np.array([1, 2, 6]) - with self.mock_for_extract_field([field], x, y) as ff2pp: - ff2pp._ff_header.dataset_type = 5 - with mock.patch("warnings.warn") as warn: - list(ff2pp._extract_field()) - - # Check the values are unchanged. - self.assertEqual(field.bdy, orig_bdy) - self.assertEqual(field.bdx, orig_bdx) - - # Check a warning was raised with a suitable message. - warn_error_tmplt = "Unexpected warning message: {}" - non_trivial_coord_warn_msg = warn.call_args[0][0] - msg = ( - "The x or y coordinates of your boundary condition field may " - "be incorrect, not having taken into account the boundary " - "size." - ) - self.assertTrue( - non_trivial_coord_warn_msg.startswith(msg), - warn_error_tmplt.format(non_trivial_coord_warn_msg), - ) - - def test_LBC_header_non_trivial_coords_both(self): - # Check a warning is raised when both bdx and bdy are bad. - field = self._mock_lbc(bdx=0, bdy=0, bzx=10, bzy=10) - self.check_non_trivial_coordinate_warning(field) - - field.bdy = field.bdx = field.bmdi - self.check_non_trivial_coordinate_warning(field) - - def test_LBC_header_non_trivial_coords_x(self): - # Check a warning is raised when bdx is bad. - field = self._mock_lbc(bdx=0, bdy=10, bzx=10, bzy=10) - self.check_non_trivial_coordinate_warning(field) - - field.bdx = field.bmdi - self.check_non_trivial_coordinate_warning(field) - - def test_LBC_header_non_trivial_coords_y(self): - # Check a warning is raised when bdy is bad. - field = self._mock_lbc(bdx=10, bdy=0, bzx=10, bzy=10) - self.check_non_trivial_coordinate_warning(field) - - field.bdy = field.bmdi - self.check_non_trivial_coordinate_warning(field) - - def test_negative_bdy(self): - # Check a warning is raised when bdy is negative, - # we don't yet know what "north" means in this case. - field = self._mock_lbc( - bdx=10, - bdy=-10, - bzx=10, - bzy=10, - lbegin=0, - lbuser=[0, 0, 121416, 0, None, None, 0], - lbrow=10, - lbnpt=12, - ) - with self.mock_for_extract_field([field]) as ff2pp: - ff2pp._ff_header.dataset_type = 5 - with mock.patch("warnings.warn") as warn: - list(ff2pp._extract_field()) - msg = "The LBC has a bdy less than 0." - self.assertTrue( - warn.call_args[0][0].startswith(msg), - "Northwards bdy warning not correctly raised.", - ) - - -class Test__payload(tests.IrisTest): - def setUp(self): - # Create a mock LBC type PPField. - self.mock_field = mock.Mock() - field = self.mock_field - field.raw_lbpack = _UNPACKED - field.lbuser = [_REAL] - field.lblrec = 777 - field.lbext = 222 - field.lbnrec = 50 - field.boundary_packing = None - - def _test( - self, mock_field, expected_depth, expected_dtype, word_depth=None - ): - with mock.patch("iris.fileformats._ff.FFHeader", return_value=None): - kwargs = {} - if word_depth is not None: - kwargs["word_depth"] = word_depth - ff2pp = FF2PP("dummy_filename", **kwargs) - data_depth, data_dtype = ff2pp._payload(mock_field) - self.assertEqual(data_depth, expected_depth) - self.assertEqual(data_dtype, expected_dtype) - - def test_unpacked_real(self): - mock_field = _DummyField( - lbext=0, - lblrec=100, - lbnrec=-1, - raw_lbpack=_UNPACKED, - lbuser=[_REAL], - boundary_packing=None, - ) - self._test(mock_field, 800, ">f8") - - def test_unpacked_real_ext(self): - mock_field = _DummyField( - lbext=5, - lblrec=100, - lbnrec=-1, - raw_lbpack=_UNPACKED, - lbuser=[_REAL], - boundary_packing=None, - ) - self._test(mock_field, 760, ">f8") - - def test_unpacked_integer(self): - mock_field = _DummyField( - lbext=0, - lblrec=200, - lbnrec=-1, - raw_lbpack=_UNPACKED, - lbuser=[_INTEGER], - boundary_packing=None, - ) - self._test(mock_field, 1600, ">i8") - - def test_unpacked_integer_ext(self): - mock_field = _DummyField( - lbext=10, - lblrec=200, - lbnrec=-1, - raw_lbpack=_UNPACKED, - lbuser=[_INTEGER], - boundary_packing=None, - ) - self._test(mock_field, 1520, ">i8") - - def test_unpacked_real_ext_different_word_depth(self): - mock_field = _DummyField( - lbext=5, - lblrec=100, - lbnrec=-1, - raw_lbpack=_UNPACKED, - lbuser=[_REAL], - boundary_packing=None, - ) - self._test(mock_field, 380, ">f4", word_depth=4) - - def test_wgdos_real(self): - mock_field = _DummyField( - lbext=0, - lblrec=-1, - lbnrec=100, - raw_lbpack=_WGDOS, - lbuser=[_REAL], - boundary_packing=None, - ) - self._test(mock_field, 800, ">f4") - - def test_wgdos_real_ext(self): - mock_field = _DummyField( - lbext=5, - lblrec=-1, - lbnrec=100, - raw_lbpack=_WGDOS, - lbuser=[_REAL], - boundary_packing=None, - ) - self._test(mock_field, 800, ">f4") - - def test_wgdos_integer(self): - mock_field = _DummyField( - lbext=0, - lblrec=-1, - lbnrec=200, - raw_lbpack=_WGDOS, - lbuser=[_INTEGER], - boundary_packing=None, - ) - self._test(mock_field, 1600, ">i4") - - def test_wgdos_integer_ext(self): - mock_field = _DummyField( - lbext=10, - lblrec=-1, - lbnrec=200, - raw_lbpack=_WGDOS, - lbuser=[_INTEGER], - boundary_packing=None, - ) - self._test(mock_field, 1600, ">i4") - - def test_cray_real(self): - mock_field = _DummyField( - lbext=0, - lblrec=100, - lbnrec=-1, - raw_lbpack=_CRAY, - lbuser=[_REAL], - boundary_packing=None, - ) - self._test(mock_field, 400, ">f4") - - def test_cray_real_ext(self): - mock_field = _DummyField( - lbext=5, - lblrec=100, - lbnrec=-1, - raw_lbpack=_CRAY, - lbuser=[_REAL], - boundary_packing=None, - ) - self._test(mock_field, 380, ">f4") - - def test_cray_integer(self): - mock_field = _DummyField( - lbext=0, - lblrec=200, - lbnrec=-1, - raw_lbpack=_CRAY, - lbuser=[_INTEGER], - boundary_packing=None, - ) - self._test(mock_field, 800, ">i4") - - def test_cray_integer_ext(self): - mock_field = _DummyField( - lbext=10, - lblrec=200, - lbnrec=-1, - raw_lbpack=_CRAY, - lbuser=[_INTEGER], - boundary_packing=None, - ) - self._test(mock_field, 760, ">i4") - - def test_lbpack_unsupported(self): - mock_field = _DummyField( - lbext=10, - lblrec=200, - lbnrec=-1, - raw_lbpack=1239, - lbuser=[_INTEGER], - boundary_packing=None, - ) - with self.assertRaisesRegex( - NotYetImplementedError, - "PP fields with LBPACK of 1239 are not supported.", - ): - self._test(mock_field, None, None) - - def test_lbc_unpacked(self): - boundary_packing = _DummyBoundaryPacking( - x_halo=11, y_halo=7, rim_width=3 - ) - mock_field = _DummyFieldWithSize( - lbext=10, - lblrec=200, - lbnrec=-1, - raw_lbpack=_UNPACKED, - lbuser=[_REAL], - boundary_packing=boundary_packing, - lbnpt=47, - lbrow=34, - ) - self._test(mock_field, ((47 * 34) - (19 * 14)) * 8, ">f8") - - def test_lbc_wgdos_unsupported(self): - mock_field = _DummyField( - lbext=5, - lblrec=-1, - lbnrec=100, - raw_lbpack=_WGDOS, - lbuser=[_REAL], - # Anything not None will do here. - boundary_packing=0, - ) - with self.assertRaisesRegex( - ValueError, "packed LBC data is not supported" - ): - self._test(mock_field, None, None) - - def test_lbc_cray(self): - boundary_packing = _DummyBoundaryPacking( - x_halo=11, y_halo=7, rim_width=3 - ) - mock_field = _DummyFieldWithSize( - lbext=10, - lblrec=200, - lbnrec=-1, - raw_lbpack=_CRAY, - lbuser=[_REAL], - boundary_packing=boundary_packing, - lbnpt=47, - lbrow=34, - ) - self._test(mock_field, ((47 * 34) - (19 * 14)) * 4, ">f4") - - -class Test__det_border(tests.IrisTest): - def setUp(self): - _FFH_patch = mock.patch("iris.fileformats._ff.FFHeader") - _FFH_patch.start() - self.addCleanup(_FFH_patch.stop) - - def test_unequal_spacing_eitherside(self): - # Ensure that we do not interpret the case where there is not the same - # spacing on the lower edge as the upper edge. - ff2pp = FF2PP("dummy") - field_x = np.array([1, 2, 10]) - - msg = ( - "The x or y coordinates of your boundary condition field may " - "be incorrect, not having taken into account the boundary " - "size." - ) - - with mock.patch("warnings.warn") as warn: - result = ff2pp._det_border(field_x, None) - warn.assert_called_with(msg) - self.assertIs(result, field_x) - - def test_increasing_field_values(self): - # Field where its values a increasing. - ff2pp = FF2PP("dummy") - field_x = np.array([1, 2, 3]) - com = np.array([0, 1, 2, 3, 4]) - result = ff2pp._det_border(field_x, 1) - self.assertArrayEqual(result, com) - - def test_decreasing_field_values(self): - # Field where its values a decreasing. - ff2pp = FF2PP("dummy") - field_x = np.array([3, 2, 1]) - com = np.array([4, 3, 2, 1, 0]) - result = ff2pp._det_border(field_x, 1) - self.assertArrayEqual(result, com) - - -class Test__adjust_field_for_lbc(tests.IrisTest): - def setUp(self): - # Patch FFHeader to produce a mock header instead of opening a file. - self.mock_ff_header = mock.Mock() - self.mock_ff_header.dataset_type = 5 - self.mock_ff = self.patch( - "iris.fileformats._ff.FFHeader", return_value=self.mock_ff_header - ) - - # Create a mock LBC type PPField. - self.mock_field = mock.Mock() - field = self.mock_field - field.lbtim = 0 - field.lblev = 7777 - field.lbvc = 0 - field.lbnpt = 1001 - field.lbrow = 2001 - field.lbuser = (None, None, 80504) - field.lbpack = pp.SplittableInt(0) - field.boundary_packing = None - field.bdx = 1.0 - field.bzx = 0.0 - field.bdy = 1.0 - field.bzy = 0.0 - - def test__basic(self): - ff2pp = FF2PP("dummy_filename") - field = self.mock_field - ff2pp._adjust_field_for_lbc(field) - self.assertEqual(field.lbtim, 11) - self.assertEqual(field.lbvc, 65) - self.assertEqual(field.boundary_packing.rim_width, 8) - self.assertEqual(field.boundary_packing.y_halo, 5) - self.assertEqual(field.boundary_packing.x_halo, 4) - self.assertEqual(field.lbnpt, 1009) - self.assertEqual(field.lbrow, 2011) - - def test__bad_lbtim(self): - self.mock_field.lbtim = 717 - ff2pp = FF2PP("dummy_filename") - with self.assertRaisesRegex( - ValueError, "LBTIM of 717, expected only 0 or 11" - ): - ff2pp._adjust_field_for_lbc(self.mock_field) - - def test__bad_lbvc(self): - self.mock_field.lbvc = 312 - ff2pp = FF2PP("dummy_filename") - with self.assertRaisesRegex( - ValueError, "LBVC of 312, expected only 0 or 65" - ): - ff2pp._adjust_field_for_lbc(self.mock_field) - - -class Test__fields_over_all_levels(tests.IrisTest): - def setUp(self): - # Patch FFHeader to produce a mock header instead of opening a file. - self.mock_ff_header = mock.Mock() - self.mock_ff_header.dataset_type = 5 - - # Fake the level constants to look like 3 model levels. - self.n_all_levels = 3 - self.mock_ff_header.level_dependent_constants = np.zeros( - (self.n_all_levels) - ) - self.mock_ff = self.patch( - "iris.fileformats._ff.FFHeader", return_value=self.mock_ff_header - ) - - # Create a simple mock for a test field. - self.mock_field = mock.Mock() - field = self.mock_field - field.lbhem = 103 - self.original_lblev = mock.sentinel.untouched_lbev - field.lblev = self.original_lblev - - def _check_expected_levels(self, results, n_levels): - if n_levels == 0: - self.assertEqual(len(results), 1) - self.assertEqual(results[0].lblev, self.original_lblev) - else: - self.assertEqual(len(results), n_levels) - self.assertEqual( - [fld.lblev for fld in results], list(range(n_levels)) - ) - - def test__is_lbc(self): - ff2pp = FF2PP("dummy_filename") - field = self.mock_field - results = list(ff2pp._fields_over_all_levels(field)) - self._check_expected_levels(results, 3) - - def test__lbhem_too_small(self): - ff2pp = FF2PP("dummy_filename") - field = self.mock_field - field.lbhem = 100 - with self.assertRaisesRegex(ValueError, "hence >= 101"): - _ = list(ff2pp._fields_over_all_levels(field)) - - def test__lbhem_too_large(self): - ff2pp = FF2PP("dummy_filename") - field = self.mock_field - field.lbhem = 105 - with self.assertRaisesRegex( - ValueError, "more than the total number of levels in the file = 3" - ): - _ = list(ff2pp._fields_over_all_levels(field)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/ff/test_FFHeader.py b/lib/iris/tests/unit/fileformats/ff/test_FFHeader.py deleted file mode 100644 index 6a65397086..0000000000 --- a/lib/iris/tests/unit/fileformats/ff/test_FFHeader.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.fileformat.ff.FFHeader`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import collections -from unittest import mock - -import numpy as np - -from iris.fileformats._ff import FFHeader - -MyGrid = collections.namedtuple("MyGrid", "column row real horiz_grid_type") - - -class Test_grid(tests.IrisTest): - def _header(self, grid_staggering): - with mock.patch.object( - FFHeader, "__init__", mock.Mock(return_value=None) - ): - header = FFHeader() - header.grid_staggering = grid_staggering - header.column_dependent_constants = mock.sentinel.column - header.row_dependent_constants = mock.sentinel.row - header.real_constants = mock.sentinel.real - header.horiz_grid_type = mock.sentinel.horiz_grid_type - return header - - def _test_grid_staggering(self, grid_staggering): - header = self._header(grid_staggering) - with mock.patch.dict( - FFHeader.GRID_STAGGERING_CLASS, {grid_staggering: MyGrid} - ): - grid = header.grid() - self.assertIsInstance(grid, MyGrid) - self.assertIs(grid.column, mock.sentinel.column) - self.assertIs(grid.row, mock.sentinel.row) - self.assertIs(grid.real, mock.sentinel.real) - self.assertIs(grid.horiz_grid_type, mock.sentinel.horiz_grid_type) - - def test_new_dynamics(self): - self._test_grid_staggering(3) - - def test_end_game(self): - self._test_grid_staggering(6) - - def test_unknown(self): - header = self._header(0) - with mock.patch( - "iris.fileformats._ff.NewDynamics", - mock.Mock(return_value=mock.sentinel.grid), - ): - with mock.patch("warnings.warn") as warn: - grid = header.grid() - warn.assert_called_with( - "Staggered grid type: 0 not currently" - " interpreted, assuming standard C-grid" - ) - self.assertIs(grid, mock.sentinel.grid) - - -@tests.skip_data -class Test_integer_constants(tests.IrisTest): - def test_read_ints(self): - test_file_path = tests.get_data_path(("FF", "structured", "small")) - ff_header = FFHeader(test_file_path) - self.assertEqual(ff_header.integer_constants.dtype, np.dtype(">i8")) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/ff/test_Grid.py b/lib/iris/tests/unit/fileformats/ff/test_Grid.py deleted file mode 100644 index b20c85b9a8..0000000000 --- a/lib/iris/tests/unit/fileformats/ff/test_Grid.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.fileformat.ff.Grid`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats._ff import Grid - - -class Test___init__(tests.IrisTest): - def test_attributes(self): - # Ensure the constructor initialises all the grid's attributes - # correctly, including unpacking values from the REAL constants. - reals = ( - mock.sentinel.ew, - mock.sentinel.ns, - mock.sentinel.first_lat, - mock.sentinel.first_lon, - mock.sentinel.pole_lat, - mock.sentinel.pole_lon, - ) - grid = Grid( - mock.sentinel.column, - mock.sentinel.row, - reals, - mock.sentinel.horiz_grid_type, - ) - self.assertIs(grid.column_dependent_constants, mock.sentinel.column) - self.assertIs(grid.row_dependent_constants, mock.sentinel.row) - self.assertIs(grid.ew_spacing, mock.sentinel.ew) - self.assertIs(grid.ns_spacing, mock.sentinel.ns) - self.assertIs(grid.first_lat, mock.sentinel.first_lat) - self.assertIs(grid.first_lon, mock.sentinel.first_lon) - self.assertIs(grid.pole_lat, mock.sentinel.pole_lat) - self.assertIs(grid.pole_lon, mock.sentinel.pole_lon) - self.assertIs(grid.horiz_grid_type, mock.sentinel.horiz_grid_type) - - -class Test_vectors(tests.IrisTest): - def setUp(self): - self.xp = mock.sentinel.xp - self.xu = mock.sentinel.xu - self.yp = mock.sentinel.yp - self.yv = mock.sentinel.yv - - def _test_subgrid_vectors(self, subgrid, expected): - grid = Grid(None, None, (None,) * 6, None) - grid._x_vectors = mock.Mock(return_value=(self.xp, self.xu)) - grid._y_vectors = mock.Mock(return_value=(self.yp, self.yv)) - result = grid.vectors(subgrid) - self.assertEqual(result, expected) - - def test_1(self): - # Data on atmospheric theta points. - self._test_subgrid_vectors(1, (self.xp, self.yp)) - - def test_2(self): - # Data on atmospheric theta points, values over land only. - self._test_subgrid_vectors(2, (self.xp, self.yp)) - - def test_3(self): - # Data on atmospheric theta points, values over sea only. - self._test_subgrid_vectors(3, (self.xp, self.yp)) - - def test_4(self): - # Data on atmospheric zonal theta points. - self._test_subgrid_vectors(4, (self.xp, self.yp)) - - def test_5(self): - # Data on atmospheric meridional theta points. - self._test_subgrid_vectors(5, (self.xp, self.yp)) - - def test_11(self): - # Data on atmospheric uv points. - self._test_subgrid_vectors(11, (self.xu, self.yv)) - - def test_18(self): - # Data on atmospheric u points on the 'c' grid. - self._test_subgrid_vectors(18, (self.xu, self.yp)) - - def test_19(self): - # Data on atmospheric v points on the 'c' grid. - self._test_subgrid_vectors(19, (self.xp, self.yv)) - - def test_26(self): - # Lateral boundary data at atmospheric theta points. - self._test_subgrid_vectors(26, (self.xp, self.yp)) - - def test_27(self): - # Lateral boundary data at atmospheric u points. - self._test_subgrid_vectors(27, (self.xu, self.yp)) - - def test_28(self): - # Lateral boundary data at atmospheric v points. - self._test_subgrid_vectors(28, (self.xp, self.yv)) - - def test_29(self): - # Orography field for atmospheric LBCs. - self._test_subgrid_vectors(29, (self.xp, self.yp)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/ff/test_NewDynamics.py b/lib/iris/tests/unit/fileformats/ff/test_NewDynamics.py deleted file mode 100644 index 5f0d64da71..0000000000 --- a/lib/iris/tests/unit/fileformats/ff/test_NewDynamics.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.fileformat.ff.NewDynamics`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats._ff import NewDynamics - - -class Test(tests.IrisTest): - def test_class_attributes(self): - reals = np.arange(6) + 100 - grid = NewDynamics(None, None, reals, None) - self.assertEqual(grid._v_offset, 0.5) - - -class Test__y_vectors(tests.IrisTest): - def _test(self, row, yp, yv): - reals = np.arange(6) + 100 - grid = NewDynamics(None, row, reals, None) - result_yp, result_yv = grid._y_vectors() - self.assertArrayEqual(result_yp, yp) - self.assertArrayEqual(result_yv, yv) - - def test_none(self): - self._test(row=None, yp=None, yv=None) - - def test_1d(self): - self._test( - row=np.array([[0], [1], [2], [3]]), - yp=np.array([0, 1, 2, 3]), - yv=None, - ) - - def test_2d(self): - self._test( - row=np.array([[0, 0], [1, 10], [2, 20], [3, 30]]), - yp=np.array([0, 1, 2, 3]), - yv=np.array([0, 10, 20]), - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/name_loaders/__init__.py b/lib/iris/tests/unit/fileformats/name_loaders/__init__.py deleted file mode 100644 index 751801a176..0000000000 --- a/lib/iris/tests/unit/fileformats/name_loaders/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.name_loaders` package.""" diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py b/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py deleted file mode 100644 index ded635984c..0000000000 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__build_cell_methods.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.fileformats.name_loaders._build_cell_methods`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris.coords -from iris.fileformats.name_loaders import _build_cell_methods - - -class Tests(tests.IrisTest): - def test_nameII_average(self): - av_or_int = ["something average ob bla"] - coord_name = "foo" - res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("mean", "foo")]) - - def test_nameIII_averaged(self): - av_or_int = ["something averaged ob bla"] - coord_name = "bar" - res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("mean", "bar")]) - - def test_nameII_integral(self): - av_or_int = ["something integral ob bla"] - coord_name = "ensemble" - res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("sum", "ensemble")]) - - def test_nameIII_integrated(self): - av_or_int = ["something integrated ob bla"] - coord_name = "time" - res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [iris.coords.CellMethod("sum", "time")]) - - def test_no_averaging(self): - av_or_int = [ - "No foo averaging", - "No bar averaging", - "no", - "", - "no averaging", - "no anything at all averaging", - ] - coord_name = "time" - res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual(res, [None] * len(av_or_int)) - - def test_nameII_mixed(self): - av_or_int = [ - "something integral ob bla", - "no averaging", - "other average", - ] - coord_name = "ensemble" - res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual( - res, - [ - iris.coords.CellMethod("sum", "ensemble"), - None, - iris.coords.CellMethod("mean", "ensemble"), - ], - ) - - def test_nameIII_mixed(self): - av_or_int = [ - "something integrated ob bla", - "no averaging", - "other averaged", - ] - coord_name = "ensemble" - res = _build_cell_methods(av_or_int, coord_name) - self.assertEqual( - res, - [ - iris.coords.CellMethod("sum", "ensemble"), - None, - iris.coords.CellMethod("mean", "ensemble"), - ], - ) - - def test_unrecognised(self): - unrecognised_heading = "bla else" - av_or_int = [ - "something average", - unrecognised_heading, - "something integral", - ] - coord_name = "foo" - with mock.patch("warnings.warn") as warn: - _ = _build_cell_methods(av_or_int, coord_name) - expected_msg = ( - "Unknown {} statistic: {!r}. Unable to " - "create cell method.".format(coord_name, unrecognised_heading) - ) - warn.assert_called_with(expected_msg) - - def test_unrecognised_similar_to_no_averaging(self): - unrecognised_headings = [ - "not averaging", - "this is not a valid no", - "nope", - "no daveraging", - "no averagingg", - "no something", - "noaveraging", - ] - for unrecognised_heading in unrecognised_headings: - av_or_int = [ - "something average", - unrecognised_heading, - "something integral", - ] - coord_name = "foo" - with mock.patch("warnings.warn") as warn: - _ = _build_cell_methods(av_or_int, coord_name) - expected_msg = ( - "Unknown {} statistic: {!r}. Unable to " - "create cell method.".format(coord_name, unrecognised_heading) - ) - warn.assert_called_with(expected_msg) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py b/lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py deleted file mode 100644 index 5954823c54..0000000000 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__build_lat_lon_for_NAME_timeseries.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.analysis.name_loaders._build_lat_lon_for_NAME_timeseries`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.fileformats.name_loaders import ( - NAMECoord, - _build_lat_lon_for_NAME_timeseries, -) - - -class TestCellMethods(tests.IrisTest): - def test_float(self): - column_headings = { - "X": ["X = -.100 Lat-Long", "X = -1.600 Lat-Long"], - "Y": ["Y = 52.450 Lat-Long", "Y = 51. Lat-Long"], - } - lat, lon = _build_lat_lon_for_NAME_timeseries(column_headings) - self.assertIsInstance(lat, NAMECoord) - self.assertIsInstance(lon, NAMECoord) - self.assertEqual(lat.name, "latitude") - self.assertEqual(lon.name, "longitude") - self.assertIsNone(lat.dimension) - self.assertIsNone(lon.dimension) - self.assertArrayEqual(lat.values, [52.45, 51.0]) - self.assertArrayEqual(lon.values, [-0.1, -1.6]) - - def test_int(self): - column_headings = { - "X": ["X = -1 Lat-Long", "X = -2 Lat-Long"], - "Y": ["Y = 52 Lat-Long", "Y = 51 Lat-Long"], - } - lat, lon = _build_lat_lon_for_NAME_timeseries(column_headings) - self.assertIsInstance(lat, NAMECoord) - self.assertIsInstance(lon, NAMECoord) - self.assertEqual(lat.name, "latitude") - self.assertEqual(lon.name, "longitude") - self.assertIsNone(lat.dimension) - self.assertIsNone(lon.dimension) - self.assertArrayEqual(lat.values, [52.0, 51.0]) - self.assertArrayEqual(lon.values, [-1.0, -2.0]) - self.assertIsInstance(lat.values[0], float) - self.assertIsInstance(lon.values[0], float) diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py b/lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py deleted file mode 100644 index c4cbde8c14..0000000000 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__calc_integration_period.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.fileformats.name_loaders.__calc_integration_period`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import datetime - -from iris.fileformats.name_loaders import _calc_integration_period - - -class Test(tests.IrisTest): - def test_30_min_av(self): - time_avgs = [" 30min average"] - result = _calc_integration_period(time_avgs) - expected = [datetime.timedelta(0, (30 * 60))] - self.assertEqual(result, expected) - - def test_30_min_av_rspace(self): - time_avgs = [" 30min average "] - result = _calc_integration_period(time_avgs) - expected = [datetime.timedelta(0, (30 * 60))] - self.assertEqual(result, expected) - - def test_30_min_av_lstrip(self): - time_avgs = [" 30min average".lstrip()] - result = _calc_integration_period(time_avgs) - expected = [datetime.timedelta(0, (30 * 60))] - self.assertEqual(result, expected) - - def test_3_hour_av(self): - time_avgs = [" 3hr 0min average"] - result = _calc_integration_period(time_avgs) - expected = [datetime.timedelta(0, (3 * 60 * 60))] - self.assertEqual(result, expected) - - def test_3_hour_int(self): - time_avgs = [" 3hr 0min integral"] - result = _calc_integration_period(time_avgs) - expected = [datetime.timedelta(0, (3 * 60 * 60))] - self.assertEqual(result, expected) - - def test_12_hour_av(self): - time_avgs = [" 12hr 0min average"] - result = _calc_integration_period(time_avgs) - expected = [datetime.timedelta(0, (12 * 60 * 60))] - self.assertEqual(result, expected) - - def test_5_day_av(self): - time_avgs = [" 5day 0hr 0min integral"] - result = _calc_integration_period(time_avgs) - expected = [datetime.timedelta(0, (5 * 24 * 60 * 60))] - self.assertEqual(result, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py b/lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py deleted file mode 100644 index 7ce66c3fef..0000000000 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__cf_height_from_name.py +++ /dev/null @@ -1,317 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.analysis.name_loaders._cf_height_from_name` -function. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.coords import AuxCoord -from iris.fileformats.name_loaders import _cf_height_from_name - - -class TestAll(tests.IrisTest): - def _default_coord(self, data): - # This private method returns a coordinate with values expected - # when no interpretation is made of the field header string. - return AuxCoord( - units="no-unit", - points=data, - bounds=None, - standard_name=None, - long_name="z", - attributes={"positive": "up"}, - ) - - -class TestAll_NAMEII(TestAll): - # NAMEII formats are defined by bounds, not points - def test_bounded_height_above_ground(self): - data = "From 0 - 100m agl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=np.array([0.0, 100.0]), - standard_name="height", - long_name="height above ground level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_bounded_flight_level(self): - data = "From FL0 - FL100" - res = _cf_height_from_name(data) - com = AuxCoord( - units="unknown", - points=50.0, - bounds=np.array([0.0, 100.0]), - standard_name=None, - long_name="flight_level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_bounded_height_above_sea_level(self): - data = "From 0 - 100m asl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=np.array([0.0, 100.0]), - standard_name="altitude", - long_name="altitude above sea level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_malformed_height_above_ground(self): - # Parse height above ground level with additional stuff on the end of - # the string (agl). - data = "From 0 - 100m agl and stuff" - res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) - - def test_malformed_height_above_sea_level(self): - # Parse height above ground level with additional stuff on the end of - # the string (agl). - data = "From 0 - 100m asl and stuff" - res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) - - def test_malformed_flight_level(self): - # Parse height above ground level with additional stuff on the end of - # the string (agl). - data = "From FL0 - FL100 and stuff" - res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) - - def test_float_bounded_height_above_ground(self): - # Parse height above ground level when its a float. - data = "From 0.0 - 100.0m agl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=np.array([0.0, 100.0]), - standard_name="height", - long_name="height above ground level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_float_bounded_height_flight_level(self): - # Parse height above ground level, as a float (agl). - data = "From FL0.0 - FL100.0" - res = _cf_height_from_name(data) - com = AuxCoord( - units="unknown", - points=50.0, - bounds=np.array([0.0, 100.0]), - standard_name=None, - long_name="flight_level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_float_bounded_height_above_sea_level(self): - # Parse height above ground level as a float (agl). - data = "From 0.0 - 100.0m asl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=np.array([0.0, 100.0]), - standard_name="altitude", - long_name="altitude above sea level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_no_match(self): - # Parse height information when there is no match. - # No interpretation, just returns default values. - data = "Vertical integral" - res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) - - def test_pressure(self): - # Parse air_pressure string. - data = "From 0 - 100 Pa" - res = _cf_height_from_name(data) - com = AuxCoord( - units="Pa", - points=50.0, - bounds=np.array([0.0, 100.0]), - standard_name="air_pressure", - long_name=None, - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - -class TestAll_NAMEIII(TestAll): - # NAMEIII formats are defined by points, not bounds. - def test_height_above_ground(self): - data = "Z = 50.00000 m agl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=None, - standard_name="height", - long_name="height above ground level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_height_flight_level(self): - data = "Z = 50.00000 FL" - res = _cf_height_from_name(data) - com = AuxCoord( - units="unknown", - points=50.0, - bounds=None, - standard_name=None, - long_name="flight_level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_height_above_sea_level(self): - data = "Z = 50.00000 m asl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=None, - standard_name="altitude", - long_name="altitude above sea level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_malformed_height_above_ground(self): - # Parse height above ground level, with additonal stuff at the string - # end (agl). - data = "Z = 50.00000 m agl and stuff" - res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) - - def test_malformed_height_above_sea_level(self): - # Parse height above ground level, with additional stuff at string - # end (agl). - data = "Z = 50.00000 m asl and stuff" - res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) - - def test_malformed_flight_level(self): - # Parse height above ground level (agl), with additional stuff at - # string end. - data = "Z = 50.00000 FL and stuff" - res = _cf_height_from_name(data) - com = self._default_coord(data) - self.assertEqual(com, res) - - def test_integer_height_above_ground(self): - # Parse height above ground level when its an integer. - data = "Z = 50 m agl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=None, - standard_name="height", - long_name="height above ground level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_integer_height_flight_level(self): - # Parse flight level when its an integer. - data = "Z = 50 FL" - res = _cf_height_from_name(data) - com = AuxCoord( - units="unknown", - points=50.0, - bounds=None, - standard_name=None, - long_name="flight_level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_integer_height_above_sea_level(self): - # Parse height above sea level (asl) when its an integer. - data = "Z = 50 m asl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=50.0, - bounds=None, - standard_name="altitude", - long_name="altitude above sea level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_enotation_height_above_ground(self): - # Parse height above ground expressed in scientific notation - data = "Z = 0.0000000E+00 m agl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=0.0, - bounds=None, - standard_name="height", - long_name="height above ground level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_enotation_height_above_sea_level(self): - # Parse height above sea level expressed in scientific notation - data = "Z = 0.0000000E+00 m asl" - res = _cf_height_from_name(data) - com = AuxCoord( - units="m", - points=0.0, - bounds=None, - standard_name="altitude", - long_name="altitude above sea level", - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - def test_pressure(self): - # Parse pressure. - data = "Z = 50.00000 Pa" - res = _cf_height_from_name(data) - com = AuxCoord( - units="Pa", - points=50.0, - bounds=None, - standard_name="air_pressure", - long_name=None, - attributes={"positive": "up"}, - ) - self.assertEqual(com, res) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py b/lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py deleted file mode 100644 index d50a7fdad1..0000000000 --- a/lib/iris/tests/unit/fileformats/name_loaders/test__generate_cubes.py +++ /dev/null @@ -1,168 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.analysis.name_loaders._generate_cubes`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from datetime import datetime, timedelta -from unittest import mock - -import numpy as np - -from iris.fileformats.name_loaders import NAMECoord, _generate_cubes - - -class TestCellMethods(tests.IrisTest): - def test_cell_methods(self): - header = mock.MagicMock() - column_headings = { - "Species": [1, 2, 3], - "Quantity": [4, 5, 6], - "Units": ["m", "m", "m"], - "Z": [1, 2, 3], - } - coords = mock.MagicMock() - data_arrays = [mock.Mock(), mock.Mock()] - cell_methods = ["cell_method_1", "cell_method_2"] - - self.patch("iris.fileformats.name_loaders._cf_height_from_name") - self.patch("iris.cube.Cube") - cubes = list( - _generate_cubes( - header, column_headings, coords, data_arrays, cell_methods - ) - ) - - cubes[0].assert_has_calls([mock.call.add_cell_method("cell_method_1")]) - cubes[1].assert_has_calls([mock.call.add_cell_method("cell_method_2")]) - - -class TestCircularLongitudes(tests.IrisTest): - def _simulate_with_coords(self, names, values, dimensions): - header = mock.MagicMock() - column_headings = { - "Species": [1, 2, 3], - "Quantity": [4, 5, 6], - "Units": ["m", "m", "m"], - "Z": [1, 2, 3], - } - coords = [ - NAMECoord(name, dim, vals) - for name, vals, dim in zip(names, values, dimensions) - ] - data_arrays = [mock.Mock()] - - self.patch("iris.fileformats.name_loaders._cf_height_from_name") - self.patch("iris.cube.Cube") - cubes = list( - _generate_cubes(header, column_headings, coords, data_arrays) - ) - return cubes - - def test_non_circular(self): - results = self._simulate_with_coords( - names=["longitude"], values=[[1, 7, 23]], dimensions=[0] - ) - self.assertEqual(len(results), 1) - add_coord_calls = results[0].add_dim_coord.call_args_list - self.assertEqual(len(add_coord_calls), 1) - coord = add_coord_calls[0][0][0] - self.assertEqual(coord.circular, False) - - def test_circular(self): - results = self._simulate_with_coords( - names=["longitude"], - values=[[5.0, 95.0, 185.0, 275.0]], - dimensions=[0], - ) - self.assertEqual(len(results), 1) - add_coord_calls = results[0].add_dim_coord.call_args_list - self.assertEqual(len(add_coord_calls), 1) - coord = add_coord_calls[0][0][0] - self.assertEqual(coord.circular, True) - - def test_lat_lon_byname(self): - results = self._simulate_with_coords( - names=["longitude", "latitude"], - values=[[5.0, 95.0, 185.0, 275.0], [5.0, 95.0, 185.0, 275.0]], - dimensions=[0, 1], - ) - self.assertEqual(len(results), 1) - add_coord_calls = results[0].add_dim_coord.call_args_list - self.assertEqual(len(add_coord_calls), 2) - lon_coord = add_coord_calls[0][0][0] - lat_coord = add_coord_calls[1][0][0] - self.assertEqual(lon_coord.circular, True) - self.assertEqual(lat_coord.circular, False) - - -class TestTimeCoord(tests.IrisTest): - def _simulate_with_coords(self, names, values, dimensions): - header = mock.MagicMock() - column_headings = { - "Species": [1, 2, 3], - "Quantity": [4, 5, 6], - "Units": ["m", "m", "m"], - "Av or Int period": [timedelta(hours=24)], - } - coords = [ - NAMECoord(name, dim, np.array(vals)) - for name, vals, dim in zip(names, values, dimensions) - ] - data_arrays = [mock.Mock()] - - self.patch("iris.fileformats.name_loaders._cf_height_from_name") - self.patch("iris.cube.Cube") - cubes = list( - _generate_cubes(header, column_headings, coords, data_arrays) - ) - return cubes - - def test_time_dim(self): - results = self._simulate_with_coords( - names=["longitude", "latitude", "time"], - values=[ - [10, 20], - [30, 40], - [datetime(2015, 6, 7), datetime(2015, 6, 8)], - ], - dimensions=[0, 1, 2], - ) - self.assertEqual(len(results), 1) - result = results[0] - dim_coord_calls = result.add_dim_coord.call_args_list - self.assertEqual(len(dim_coord_calls), 3) # lon, lat, time - t_coord = dim_coord_calls[2][0][0] - self.assertEqual(t_coord.standard_name, "time") - self.assertArrayEqual(t_coord.points, [398232, 398256]) - self.assertArrayEqual(t_coord.bounds[0], [398208, 398232]) - self.assertArrayEqual(t_coord.bounds[-1], [398232, 398256]) - - def test_time_scalar(self): - results = self._simulate_with_coords( - names=["longitude", "latitude", "time"], - values=[[10, 20], [30, 40], [datetime(2015, 6, 7)]], - dimensions=[0, 1, None], - ) - self.assertEqual(len(results), 1) - result = results[0] - dim_coord_calls = result.add_dim_coord.call_args_list - self.assertEqual(len(dim_coord_calls), 2) - aux_coord_calls = result.add_aux_coord.call_args_list - self.assertEqual(len(aux_coord_calls), 1) - t_coord = aux_coord_calls[0][0][0] - self.assertEqual(t_coord.standard_name, "time") - self.assertArrayEqual(t_coord.points, [398232]) - self.assertArrayEqual(t_coord.bounds, [[398208, 398232]]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/__init__.py deleted file mode 100644 index 2ea22c420b..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the module -:mod:`iris.fileformats.netcdf._nc_load_rules` . - -""" diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py deleted file mode 100644 index 399a987f11..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/__init__.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the module :mod:`iris.fileformats._nc_load_rules.actions`. - -""" -from pathlib import Path -import shutil -import tempfile -import warnings - -import iris.fileformats._nc_load_rules.engine -from iris.fileformats.cf import CFReader -import iris.fileformats.netcdf -from iris.fileformats.netcdf.loader import _load_cube -from iris.tests.stock.netcdf import ncgen_from_cdl - -""" -Notes on testing method. - -IN cf : "def _load_cube(engine, cf, cf_var, filename)" -WHERE: - - engine is a :class:`iris.fileformats._nc_load_rules.engine.Engine` - - cf is a :class:`iris.fileformats.cf.CFReader` - - cf_var is a :class:`iris.fileformats.cf.CFDataVariable` - -As it's hard to construct a suitable CFReader from scratch, it would seem -simpler (for now) to use an ACTUAL FILE. -Likewise, the easiest approach to that is with CDL and "ncgen". -For this, we just use 'tests.stock.netcdf.ncgen_from_cdl'. -""" - - -class Mixin__nc_load_actions: - """ - Class to make testcases for rules or actions code, and check results. - - Defines standard setUpClass/tearDownClass methods, to create a temporary - directory for intermediate files. - NOTE: owing to peculiarities of unittest, these must be explicitly called - from a setUpClass/tearDownClass within the 'final' inheritor, i.e. the - actual Test_XXX class which also inherits unittest.TestCase. - - Testcases are manufactured by the '_make_testcase_cdl' method. - The 'run_testcase' method takes the '_make_testcase_cdl' kwargs and makes - a result cube (by: producing cdl, converting to netcdf, and loading the - 'phenom' variable only). - Likewise, a generalised 'check_result' method will be used to perform result - checking. - Both '_make_testcase_cdl' and 'check_result' are not defined here : They - are to be variously implemented by the inheritors. - - """ - - # "global" test setting : whether to output various debug info - debug = False - - @classmethod - def setUpClass(cls): - # Create a temp directory for temp files. - cls.temp_dirpath = Path(tempfile.mkdtemp()) - - @classmethod - def tearDownClass(cls): - # Destroy a temp directory for temp files. - shutil.rmtree(cls.temp_dirpath) - - def load_cube_from_cdl(self, cdl_string, cdl_path, nc_path): - """ - Load the 'phenom' data variable in a CDL testcase, as a cube. - - Using ncgen, CFReader and the _load_cube call. - - """ - # Write the CDL to a file. - ncgen_from_cdl(cdl_string, cdl_path, nc_path) - - # Simulate the inner part of the file reading process. - cf = CFReader(nc_path) - - with cf: - # Grab a data variable : FOR NOW always grab the 'phenom' variable. - cf_var = cf.cf_group.data_variables["phenom"] - - engine = iris.fileformats.netcdf.loader._actions_engine() - - # If debug enabled, switch on the activation summary debug output. - # Use 'patch' so it is restored after the test. - self.patch("iris.fileformats.netcdf.loader.DEBUG", self.debug) - - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - message="Ignoring a datum in netCDF load for consistency with existing " - "behaviour. In a future version of Iris, this datum will be " - "applied. To apply the datum when loading, use the " - "iris.FUTURE.datum_support flag.", - category=FutureWarning, - ) - # Call the main translation function to load a single cube. - # _load_cube establishes per-cube facts, activates rules and - # produces an actual cube. - cube = _load_cube(engine, cf, cf_var, nc_path) - - # Also Record, on the cubes, which hybrid coord elements were identified - # by the rules operation. - # Unlike the other translations, _load_cube does *not* convert this - # information into actual cube elements. That is instead done by - # `iris.fileformats.netcdf._load_aux_factory`. - # For rules testing, it is anyway more convenient to deal with the raw - # data, as each factory type has different validity requirements to - # build it, and none of that is relevant to the rules operation. - cube._formula_type_name = engine.requires.get("formula_type") - cube._formula_terms_byname = engine.requires.get("formula_terms") - - # Always returns a single cube. - return cube - - def run_testcase(self, warning_regex=None, **testcase_kwargs): - """ - Run a testcase with chosen options, returning a test cube. - - The kwargs apply to the '_make_testcase_cdl' method. - - """ - cdl_path = str(self.temp_dirpath / "test.cdl") - nc_path = cdl_path.replace(".cdl", ".nc") - - cdl_string = self._make_testcase_cdl(**testcase_kwargs) - if self.debug: - print("CDL file content:") - print(cdl_string) - print("------\n") - - if warning_regex is None: - context = self.assertNoWarningsRegexp() - else: - context = self.assertWarnsRegex(UserWarning, warning_regex) - with context: - cube = self.load_cube_from_cdl(cdl_string, cdl_path, nc_path) - - if self.debug: - print("\nCube:") - print(cube) - print("") - return cube - - def _make_testcase_cdl(self, **kwargs): - """Make a testcase CDL string.""" - # Override for specific uses... - raise NotImplementedError() - - def check_result(self, cube, **kwargs): - """Test a result cube.""" - # Override for specific uses... - raise NotImplementedError() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py deleted file mode 100644 index a367e7709c..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__grid_mappings.py +++ /dev/null @@ -1,884 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the engine.activate() call within the -`iris.fileformats.netcdf._load_cube` function. - -Here, *specifically* testcases relating to grid-mappings and dim-coords. - -""" -import iris.tests as tests # isort: skip - -import iris.coord_systems as ics -import iris.fileformats._nc_load_rules.helpers as hh -from iris.tests.unit.fileformats.nc_load_rules.actions import ( - Mixin__nc_load_actions, -) - - -class Mixin__grid_mapping(Mixin__nc_load_actions): - # Testcase support routines for testing translation of grid-mappings - def _make_testcase_cdl( - self, - latitude_units=None, - gridmapvar_name=None, - gridmapvar_mappropertyname=None, - mapping_missingradius=False, - mapping_type_name=None, - mapping_scalefactor=None, - yco_values=None, - xco_name=None, - yco_name=None, - xco_units=None, - yco_units=None, - xco_is_dim=True, - yco_is_dim=True, - ): - """ - Create a CDL string for a testcase. - - This is the "master" routine for creating all our testcases. - Kwarg options modify a simple default testcase with a latlon grid. - The routine handles the various testcase options and their possible - interactions. This includes knowing what extra changes are required - to support different grid-mapping types (for example). - - """ - # The grid-mapping options are standard-latlon, rotated, or non-latlon. - # This affects names+units of the X and Y coords. - # We don't have an option to *not* include a grid-mapping variable, but - # we can mimic a missing grid-mapping by changing the varname from that - # which the data-variable refers to, with "gridmapvar_name=xxx". - # Likewise, an invalid (unrecognised) grid-mapping can be mimicked by - # selecting an unkown 'grid_mapping_name' property, with - # "gridmapvar_mappropertyname=xxx". - if mapping_type_name is None: - # Default grid-mapping and coords are standard lat-lon. - mapping_type_name = hh.CF_GRID_MAPPING_LAT_LON - xco_name_default = hh.CF_VALUE_STD_NAME_LON - yco_name_default = hh.CF_VALUE_STD_NAME_LAT - xco_units_default = "degrees_east" - # Special kwarg overrides some of the values. - if latitude_units is None: - yco_units_default = "degrees_north" - else: - # Override the latitude units (to invalidate). - yco_units_default = latitude_units - - elif mapping_type_name == hh.CF_GRID_MAPPING_ROTATED_LAT_LON: - # Rotated lat-lon coordinates. - xco_name_default = hh.CF_VALUE_STD_NAME_GRID_LON - yco_name_default = hh.CF_VALUE_STD_NAME_GRID_LAT - xco_units_default = "degrees" - yco_units_default = "degrees" - - else: - # General non-latlon coordinates - # Exactly which depends on the grid_mapping name. - xco_name_default = hh.CF_VALUE_STD_NAME_PROJ_X - yco_name_default = hh.CF_VALUE_STD_NAME_PROJ_Y - xco_units_default = "m" - yco_units_default = "m" - - # Options can override coord (standard) names and units. - if xco_name is None: - xco_name = xco_name_default - if yco_name is None: - yco_name = yco_name_default - if xco_units is None: - xco_units = xco_units_default - if yco_units is None: - yco_units = yco_units_default - - phenom_auxcoord_names = [] - if xco_is_dim: - # xdim has same name as xco, making xco a dim-coord - xdim_name = "xco" - else: - # use alternate dim-name, and put xco on the 'coords' list - # This makes the X coord an aux-coord - xdim_name = "xdim_altname" - phenom_auxcoord_names.append("xco") - if yco_is_dim: - # ydim has same name as yco, making yco a dim-coord - ydim_name = "yco" # This makes the Y coord a dim-coord - else: - # use alternate dim-name, and put yco on the 'coords' list - # This makes the Y coord an aux-coord - ydim_name = "ydim_altname" - phenom_auxcoord_names.append("yco") - - # Build a 'phenom:coords' string if needed. - if phenom_auxcoord_names: - phenom_coords_string = " ".join(phenom_auxcoord_names) - phenom_coords_string = f""" - phenom:coordinates = "{phenom_coords_string}" ; -""" - else: - phenom_coords_string = "" - - grid_mapping_name = "grid" - # Options can override the gridvar name and properties. - g_varname = gridmapvar_name - g_mapname = gridmapvar_mappropertyname - if g_varname is None: - g_varname = grid_mapping_name - if g_mapname is None: - # If you change this, it is no longer a valid grid-mapping var. - g_mapname = "grid_mapping_name" - - # Omit the earth radius, if requested. - if mapping_missingradius: - g_radius_string = "" - else: - g_radius_string = f"{g_varname}:earth_radius = 6.e6 ;" - g_string = f""" - int {g_varname} ; - {g_varname}:{g_mapname} = "{mapping_type_name}"; - {g_radius_string} - """ - - # Add a specified scale-factor, if requested. - if mapping_scalefactor is not None: - # Add a specific scale-factor term to the grid mapping. - sfapo_name = hh.CF_ATTR_GRID_SCALE_FACTOR_AT_PROJ_ORIGIN - g_string += f""" - {g_varname}:{sfapo_name} = {mapping_scalefactor} ; - """ - - # - # Add various additional (minimal) required properties for different - # grid mapping types. - # - - # Those which require 'latitude of projection origin' - if mapping_type_name in ( - hh.CF_GRID_MAPPING_TRANSVERSE, - hh.CF_GRID_MAPPING_STEREO, - hh.CF_GRID_MAPPING_GEOSTATIONARY, - hh.CF_GRID_MAPPING_VERTICAL, - ): - latpo_name = hh.CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN - g_string += f""" - {g_varname}:{latpo_name} = 0.0 ; - """ - # Those which require 'longitude of projection origin' - if mapping_type_name in ( - hh.CF_GRID_MAPPING_STEREO, - hh.CF_GRID_MAPPING_GEOSTATIONARY, - hh.CF_GRID_MAPPING_VERTICAL, - ): - lonpo_name = hh.CF_ATTR_GRID_LON_OF_PROJ_ORIGIN - g_string += f""" - {g_varname}:{lonpo_name} = 0.0 ; - """ - # Those which require 'longitude of central meridian' - if mapping_type_name in (hh.CF_GRID_MAPPING_TRANSVERSE,): - latcm_name = hh.CF_ATTR_GRID_LON_OF_CENT_MERIDIAN - g_string += f""" - {g_varname}:{latcm_name} = 0.0 ; - """ - # Those which require 'perspective point height' - if mapping_type_name in ( - hh.CF_GRID_MAPPING_VERTICAL, - hh.CF_GRID_MAPPING_GEOSTATIONARY, - ): - pph_name = hh.CF_ATTR_GRID_PERSPECTIVE_HEIGHT - g_string += f""" - {g_varname}:{pph_name} = 600000.0 ; - """ - # Those which require 'sweep angle axis' - if mapping_type_name in (hh.CF_GRID_MAPPING_GEOSTATIONARY,): - saa_name = hh.CF_ATTR_GRID_SWEEP_ANGLE_AXIS - g_string += f""" - {g_varname}:{saa_name} = "y" ; - """ - # Polar stereo needs a special 'latitude of projection origin', a - # 'straight_vertical_longitude_from_pole' and a `standard_parallel` or - # `scale_factor_at_projection_origin` so treat it specially - if mapping_type_name in (hh.CF_GRID_MAPPING_POLAR,): - latpo_name = hh.CF_ATTR_GRID_LAT_OF_PROJ_ORIGIN - g_string += f""" - {g_varname}:{latpo_name} = 90.0 ; - """ - svl_name = hh.CF_ATTR_GRID_STRAIGHT_VERT_LON - g_string += f""" - {g_varname}:{svl_name} = 0.0 ; - """ - stanpar_name = hh.CF_ATTR_GRID_STANDARD_PARALLEL - g_string += f""" - {g_varname}:{stanpar_name} = 1.0 ; - """ - - # y-coord values - if yco_values is None: - yco_values = [10.0, 20.0] - yco_value_strings = [str(val) for val in yco_values] - yco_values_string = ", ".join(yco_value_strings) - - # Construct the total CDL string - cdl_string = f""" - netcdf test {{ - dimensions: - {ydim_name} = 2 ; - {xdim_name} = 3 ; - variables: - double phenom({ydim_name}, {xdim_name}) ; - phenom:standard_name = "air_temperature" ; - phenom:units = "K" ; - phenom:grid_mapping = "grid" ; -{phenom_coords_string} - double yco({ydim_name}) ; - yco:axis = "Y" ; - yco:units = "{yco_units}" ; - yco:standard_name = "{yco_name}" ; - double xco({xdim_name}) ; - xco:axis = "X" ; - xco:units = "{xco_units}" ; - xco:standard_name = "{xco_name}" ; - {g_string} - data: - yco = {yco_values_string} ; - xco = 100., 110., 120. ; - }} - """ - return cdl_string - - def check_result( - self, - cube, - cube_cstype=None, - cube_no_cs=False, - cube_no_xycoords=False, - xco_no_cs=False, # N.B. no effect if cube_no_cs is True - yco_no_cs=False, # N.B. no effect if cube_no_cs is True - xco_is_aux=False, - yco_is_aux=False, - xco_stdname=True, - yco_stdname=True, - ): - """ - Check key properties of a result cube. - - Various options control the expected things which are tested. - """ - self.assertEqual(cube.standard_name, "air_temperature") - self.assertEqual(cube.var_name, "phenom") - - x_coords = cube.coords(dimensions=(1,)) - y_coords = cube.coords(dimensions=(0,)) - expected_dim_coords = [] - expected_aux_coords = [] - if yco_is_aux: - expected_aux_coords += y_coords - else: - expected_dim_coords += y_coords - if xco_is_aux: - expected_aux_coords += x_coords - else: - expected_dim_coords += x_coords - - self.assertEqual( - set(expected_dim_coords), set(cube.coords(dim_coords=True)) - ) - if cube_no_xycoords: - self.assertEqual(expected_dim_coords, []) - x_coord = None - y_coord = None - else: - self.assertEqual(len(x_coords), 1) - (x_coord,) = x_coords - self.assertEqual(len(y_coords), 1) - (y_coord,) = y_coords - - self.assertEqual( - set(expected_aux_coords), set(cube.coords(dim_coords=False)) - ) - - if x_coord: - if xco_stdname is None: - # no check - pass - elif xco_stdname is True: - self.assertIsNotNone(x_coord.standard_name) - elif xco_stdname is False: - self.assertIsNone(x_coord.standard_name) - else: - self.assertEqual(x_coord.standard_name, xco_stdname) - - if y_coord: - if yco_stdname is None: - # no check - pass - if yco_stdname is True: - self.assertIsNotNone(y_coord.standard_name) - elif yco_stdname is False: - self.assertIsNone(y_coord.standard_name) - else: - self.assertEqual(y_coord.standard_name, yco_stdname) - - cube_cs = cube.coord_system() - if cube_no_xycoords: - yco_cs = None - xco_cs = None - else: - yco_cs = y_coord.coord_system - xco_cs = x_coord.coord_system - if cube_no_cs: - self.assertIsNone(cube_cs) - self.assertIsNone(yco_cs) - self.assertIsNone(xco_cs) - else: - if cube_cstype is not None: - self.assertIsInstance(cube_cs, cube_cstype) - if xco_no_cs: - self.assertIsNone(xco_cs) - else: - self.assertEqual(xco_cs, cube_cs) - if yco_no_cs: - self.assertIsNone(yco_cs) - else: - self.assertEqual(yco_cs, cube_cs) - - -class Test__grid_mapping(Mixin__grid_mapping, tests.IrisTest): - # Various testcases for translation of grid-mappings - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def test_basic_latlon(self): - # A basic reference example with a lat-long grid. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_provides_coordinate_(latitude) - # 004 : fc_provides_coordinate_(longitude) - # 005 : fc_build_coordinate_(latitude) - # 006 : fc_build_coordinate_(longitude) - # Notes: - # * grid-mapping identified : regular latlon - # * dim-coords identified : lat+lon - # * coords built : standard latlon (with latlon coord-system) - result = self.run_testcase() - self.check_result(result) - - def test_missing_latlon_radius(self): - # Lat-long with a missing earth-radius causes an error. - # One of very few cases where activation may encounter an error. - # N.B. doesn't really test rules-activation, but maybe worth doing. - # (no rules trigger) - with self.assertRaisesRegex(ValueError, "No ellipsoid"): - self.run_testcase(mapping_missingradius=True) - - def test_bad_gridmapping_nameproperty(self): - # Fix the 'grid' var so it does not register as a grid-mapping. - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping --FAILED(no grid-mapping attr) - # 003 : fc_provides_coordinate_(latitude) - # 004 : fc_provides_coordinate_(longitude) - # 005 : fc_build_coordinate_(latitude)(no-cs) - # 006 : fc_build_coordinate_(longitude)(no-cs) - # Notes: - # * grid-mapping identified : NONE (thus, no coord-system) - # * dim-coords identified : lat+lon - # * coords built : lat+lon coords, with NO coord-system - result = self.run_testcase(gridmapvar_mappropertyname="mappy") - self.check_result(result, cube_no_cs=True) - - def test_latlon_bad_gridmapping_varname(self): - # rename the grid-mapping variable so it is effectively 'missing' - # (I.E. the var named in "data-variable:grid_mapping" does not exist). - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(latitude) - # 003 : fc_provides_coordinate_(longitude) - # 004 : fc_build_coordinate_(latitude)(no-cs) - # 005 : fc_build_coordinate_(longitude)(no-cs) - # Notes: - # * behaviours all the same as 'test_bad_gridmapping_nameproperty' - warning = "Missing.*grid mapping variable 'grid'" - result = self.run_testcase( - warning_regex=warning, gridmapvar_name="grid_2" - ) - self.check_result(result, cube_no_cs=True) - - def test_latlon_bad_latlon_unit(self): - # Check with bad latitude units : 'degrees' in place of 'degrees_north'. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_default_coordinate_(provide-phase) - # 004 : fc_provides_coordinate_(longitude) - # 005 : fc_build_coordinate_(miscellaneous) - # 006 : fc_build_coordinate_(longitude) - # Notes: - # * grid-mapping identified : regular latlon - # * dim-coords identified : - # x is regular longitude dim-coord - # y is 'default' coord ==> builds as an 'extra' dim-coord - # * coords built : - # x(lon) is regular latlon with coord-system - # y(lat) is a dim-coord, but with NO coord-system - # * additional : - # "fc_provides_coordinate_latitude" did not trigger, - # because it is not a valid latitude coordinate. - result = self.run_testcase(latitude_units="degrees") - self.check_result(result, yco_no_cs=True) - - def test_mapping_rotated(self): - # Test with rotated-latlon grid-mapping - # Distinct from both regular-latlon and non-latlon cases, as the - # coordinate standard names and units are different. - # ('_make_testcase_cdl' and 'check_result' know how to handle that). - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(rotated_latitude_longitude) - # 003 : fc_provides_coordinate_(rotated_latitude) - # 004 : fc_provides_coordinate_(rotated_longitude) - # 005 : fc_build_coordinate_(rotated_latitude)(rotated) - # 006 : fc_build_coordinate_(rotated_longitude)(rotated) - # Notes: - # * grid-mapping identified : rotated lat-lon - # * dim-coords identified : lat+lon - # * coords built: lat+lon coords ROTATED, with coord-system - # - "rotated" means that they have a different name + units - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_ROTATED_LAT_LON - ) - self.check_result(result, cube_cstype=ics.RotatedGeogCS) - - # - # All non-latlon coordinate systems ... - # These all have projection-x/y coordinates with units of metres. - # They all work the same way. - # NOTE: various mapping types *require* certain addtional properties - # - without which an error will occur during translation. - # - run_testcase/_make_testcase_cdl know how to provide these - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_() - # 003 : fc_provides_coordinate_(projection_y) - # 004 : fc_provides_coordinate_(projection_x) - # 005 : fc_build_coordinate_(projection_y) - # 006 : fc_build_coordinate_(projection_x) - # Notes: - # * grid-mapping identified : - # * dim-coords identified : projection__coordinate - # * coords built : projection__coordinate, with coord-system - def test_mapping_albers(self): - result = self.run_testcase(mapping_type_name=hh.CF_GRID_MAPPING_ALBERS) - self.check_result(result, cube_cstype=ics.AlbersEqualArea) - - def test_mapping_geostationary(self): - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_GEOSTATIONARY - ) - self.check_result(result, cube_cstype=ics.Geostationary) - - def test_mapping_lambert_azimuthal(self): - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_LAMBERT_AZIMUTHAL - ) - self.check_result(result, cube_cstype=ics.LambertAzimuthalEqualArea) - - def test_mapping_lambert_conformal(self): - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_LAMBERT_CONFORMAL - ) - self.check_result(result, cube_cstype=ics.LambertConformal) - - def test_mapping_mercator(self): - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_MERCATOR - ) - self.check_result(result, cube_cstype=ics.Mercator) - - def test_mapping_stereographic(self): - result = self.run_testcase(mapping_type_name=hh.CF_GRID_MAPPING_STEREO) - self.check_result(result, cube_cstype=ics.Stereographic) - - def test_mapping_polar_stereographic(self): - result = self.run_testcase(mapping_type_name=hh.CF_GRID_MAPPING_POLAR) - self.check_result(result, cube_cstype=ics.PolarStereographic) - - def test_mapping_transverse_mercator(self): - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_TRANSVERSE - ) - self.check_result(result, cube_cstype=ics.TransverseMercator) - - def test_mapping_vertical_perspective(self): - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_VERTICAL - ) - self.check_result(result, cube_cstype=ics.VerticalPerspective) - - def test_mapping_unsupported(self): - # Use azimuthal, which is a real thing but we don't yet support it. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping --FAILED(unhandled type azimuthal_equidistant) - # 003 : fc_provides_coordinate_(projection_y) - # 004 : fc_provides_coordinate_(projection_x) - # 005 : fc_build_coordinate_(projection_y)(FAILED projected coord with non-projected cs) - # 006 : fc_build_coordinate_(projection_x)(FAILED projected coord with non-projected cs) - # Notes: - # * NO grid-mapping is identified (or coord-system built) - # * There is no warning for this : it fails silently. - # TODO: perhaps there _should_ be a warning in such cases ? - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_AZIMUTHAL - ) - self.check_result(result, cube_no_cs=True, cube_no_xycoords=True) - - def test_mapping_undefined(self): - # Use a random, unknown "mapping type". - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping --FAILED(unhandled type unknown) - # 003 : fc_provides_coordinate_(projection_y) - # 004 : fc_provides_coordinate_(projection_x) - # 005 : fc_build_coordinate_(projection_y)(FAILED projected coord with non-projected cs) - # 006 : fc_build_coordinate_(projection_x)(FAILED projected coord with non-projected cs) - # Notes: - # * There is no warning for this : it fails silently. - # TODO: perhaps there _should_ be a warning in such cases ? - result = self.run_testcase(mapping_type_name="unknown") - self.check_result(result, cube_no_cs=True, cube_no_xycoords=True) - - # - # Cases where names(+units) of coords don't match the grid-mapping type. - # Effectively, there are 9 possibilities for (latlon/rotated/projected) - # coords mismatched to (latlon/rotated/projected/missing) coord-systems. - # - # N.B. the results are not all the same : - # - # 1. when a coord and the grid-mapping have the same 'type', - # i.e. plain-latlon, rotated-latlon or non-latlon, then dim-coords are - # built with a coord-system (as seen previously). - # 2. when there is no grid-mapping, we can build coords of any type, - # but with no coord-system. - # 3. when one of (coord + grid-mapping) is plain-latlon or rotated-latlon, - # and the other is non-latlon (i.e. any other type), - # then we build coords *without* a coord-system - # 4. when one of (coord + grid-mapping) is plain-latlon, and the other is - # rotated-latlon, we don't build coords at all. - # TODO: it's not clear why this needs to behave differently from case - # (3.) : possibly, these two should be made consistent. - # - # TODO: *all* these 'mismatch' cases should probably generate warnings, - # except for plain-latlon coords with no grid-mapping. - # At present, we _only_ warn when an expected grid-mapping is absent. - # - - def test_mapping__mismatch__latlon_coords_rotated_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(rotated_latitude_longitude) - # 003 : fc_provides_coordinate_(latitude) - # 004 : fc_provides_coordinate_(longitude) - # 005 : fc_build_coordinate_(latitude)(FAILED : latlon coord with rotated cs) - # 006 : fc_build_coordinate_(longitude)(FAILED : latlon coord with rotated cs) - # Notes: - # * coords built : NONE (see above) - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_ROTATED_LAT_LON, - xco_name="longitude", - xco_units="degrees_east", - yco_name="latitude", - yco_units="degrees_north", - ) - self.check_result(result, cube_no_cs=True, cube_no_xycoords=True) - - def test_mapping__mismatch__latlon_coords_nonll_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(albers_conical_equal_area) - # 003 : fc_provides_coordinate_(latitude) - # 004 : fc_provides_coordinate_(longitude) - # 005 : fc_build_coordinate_(latitude)(no-cs : discarded projected cs) - # 006 : fc_build_coordinate_(longitude)(no-cs : discarded projected cs) - # Notes: - # * coords built : lat + lon, with no coord-system (see above) - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_ALBERS, - xco_name="longitude", - xco_units="degrees_east", - yco_name="latitude", - yco_units="degrees_north", - ) - self.check_result(result, cube_no_cs=True) - - def test_mapping__mismatch__latlon_coords_missing_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(latitude) - # 003 : fc_provides_coordinate_(longitude) - # 004 : fc_build_coordinate_(latitude)(no-cs) - # 005 : fc_build_coordinate_(longitude)(no-cs) - # Notes: - # * coords built : lat + lon, with no coord-system (see above) - warning = "Missing.*grid mapping variable 'grid'" - result = self.run_testcase( - warning_regex=warning, - gridmapvar_name="moved", - xco_name="longitude", - xco_units="degrees_east", - yco_name="latitude", - yco_units="degrees_north", - ) - self.check_result(result, cube_no_cs=True) - - def test_mapping__mismatch__rotated_coords_latlon_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_provides_coordinate_(rotated_latitude) - # 004 : fc_provides_coordinate_(rotated_longitude) - # 005 : fc_build_coordinate_(rotated_latitude)(FAILED rotated coord with latlon cs) - # 006 : fc_build_coordinate_(rotated_longitude)(FAILED rotated coord with latlon cs) - # Notes: - # * coords built : NONE (see above) - result = self.run_testcase( - xco_name="grid_longitude", - xco_units="degrees", - yco_name="grid_latitude", - yco_units="degrees", - ) - self.check_result(result, cube_no_cs=True, cube_no_xycoords=True) - - def test_mapping__mismatch__rotated_coords_nonll_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(albers_conical_equal_area) - # 003 : fc_provides_coordinate_(rotated_latitude) - # 004 : fc_provides_coordinate_(rotated_longitude) - # 005 : fc_build_coordinate_(rotated_latitude)(rotated no-cs : discarded projected cs) - # 006 : fc_build_coordinate_(rotated_longitude)(rotated no-cs : discarded projected cs) - # Notes: - # * coords built : rotated-lat + lon, with no coord-system (see above) - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_ALBERS, - xco_name="grid_longitude", - xco_units="degrees", - yco_name="grid_latitude", - yco_units="degrees", - ) - self.check_result(result, cube_no_cs=True) - - def test_mapping__mismatch__rotated_coords_missing_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(rotated_latitude) - # 003 : fc_provides_coordinate_(rotated_longitude) - # 004 : fc_build_coordinate_(rotated_latitude)(rotated no-cs) - # 005 : fc_build_coordinate_(rotated_longitude)(rotated no-cs) - # Notes: - # * coords built : rotated lat + lon, with no coord-system (see above) - warning = "Missing.*grid mapping variable 'grid'" - result = self.run_testcase( - warning_regex=warning, - gridmapvar_name="moved", - xco_name="grid_longitude", - xco_units="degrees", - yco_name="grid_latitude", - yco_units="degrees", - ) - self.check_result(result, cube_no_cs=True) - - def test_mapping__mismatch__nonll_coords_latlon_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_default_coordinate_(provide-phase) - # 004 : fc_default_coordinate_(provide-phase) - # 005 : fc_build_coordinate_(miscellaneous) - # 006 : fc_build_coordinate_(miscellaneous) - # Notes: - # * coords built : projection x + y, with no coord-system (see above) - # * the coords build as "default" type : they have no standard-name - result = self.run_testcase( - xco_name="projection_x", - xco_units="m", - yco_name="projection_y", - yco_units="m", - ) - self.check_result( - result, cube_no_cs=True, xco_stdname=False, yco_stdname=False - ) - - def test_mapping__mismatch__nonll_coords_rotated_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(rotated_latitude_longitude) - # 003 : fc_default_coordinate_(provide-phase) - # 004 : fc_default_coordinate_(provide-phase) - # 005 : fc_build_coordinate_(miscellaneous) - # 006 : fc_build_coordinate_(miscellaneous) - # Notes: - # * as previous case '__mismatch__nonll_' - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_ROTATED_LAT_LON, - xco_name="projection_x", - xco_units="m", - yco_name="projection_y", - yco_units="m", - ) - self.check_result( - result, cube_no_cs=True, xco_stdname=False, yco_stdname=False - ) - - def test_mapping__mismatch__nonll_coords_missing_system(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_default_coordinate_(provide-phase) - # 003 : fc_default_coordinate_(provide-phase) - # 004 : fc_build_coordinate_(miscellaneous) - # 005 : fc_build_coordinate_(miscellaneous) - # Notes: - # * effectively, just like previous 2 cases - warning = "Missing.*grid mapping variable 'grid'" - result = self.run_testcase( - warning_regex=warning, - gridmapvar_name="moved", - xco_name="projection_x", - xco_units="m", - yco_name="projection_y", - yco_units="m", - ) - self.check_result( - result, cube_no_cs=True, xco_stdname=False, yco_stdname=False - ) - - -class Test__aux_latlons(Mixin__grid_mapping, tests.IrisTest): - # Testcases for translating auxiliary latitude+longitude variables - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def test_aux_lon(self): - # Change the name of xdim, and put xco on the coords list. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_provides_coordinate_(latitude) - # 004 : fc_build_coordinate_(latitude) - # 005 : fc_build_auxiliary_coordinate_longitude - result = self.run_testcase(xco_is_dim=False) - self.check_result(result, xco_is_aux=True, xco_no_cs=True) - - def test_aux_lat(self): - # As previous, but with the Y coord. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_provides_coordinate_(longitude) - # 004 : fc_build_coordinate_(longitude) - # 005 : fc_build_auxiliary_coordinate_latitude - result = self.run_testcase(yco_is_dim=False) - self.check_result(result, yco_is_aux=True, yco_no_cs=True) - - def test_aux_lat_and_lon(self): - # Make *both* X and Y coords into aux-coords. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_build_auxiliary_coordinate_longitude - # 004 : fc_build_auxiliary_coordinate_latitude - # Notes: - # * a grid-mapping is recognised, but discarded, as in this case - # there are no dim-coords to reference it. - result = self.run_testcase(xco_is_dim=False, yco_is_dim=False) - self.check_result( - result, xco_is_aux=True, yco_is_aux=True, cube_no_cs=True - ) - - def test_aux_lon_rotated(self): - # Rotated-style lat + lon coords, X is an aux-coord. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(rotated_latitude_longitude) - # 003 : fc_provides_coordinate_(rotated_latitude) - # 004 : fc_build_coordinate_(rotated_latitude)(rotated) - # 005 : fc_build_auxiliary_coordinate_longitude_rotated - # Notes: - # * as the plain-latlon case 'test_aux_lon'. - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_ROTATED_LAT_LON, - xco_is_dim=False, - ) - self.check_result(result, xco_is_aux=True, xco_no_cs=True) - - def test_aux_lat_rotated(self): - # Rotated-style lat + lon coords, Y is an aux-coord. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(rotated_latitude_longitude) - # 003 : fc_provides_coordinate_(rotated_longitude) - # 004 : fc_build_coordinate_(rotated_longitude)(rotated) - # 005 : fc_build_auxiliary_coordinate_latitude_rotated - # Notes: - # * as the plain-latlon case 'test_aux_lat'. - result = self.run_testcase( - mapping_type_name=hh.CF_GRID_MAPPING_ROTATED_LAT_LON, - yco_is_dim=False, - ) - self.check_result(result, yco_is_aux=True, yco_no_cs=True) - - -class Test__nondimcoords(Mixin__grid_mapping, tests.IrisTest): - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def test_nondim_lats(self): - # Fix a coord's values so it cannot be a dim-coord. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_grid_mapping_(latitude_longitude) - # 003 : fc_provides_coordinate_(latitude) - # 004 : fc_provides_coordinate_(longitude) - # 005 : fc_build_coordinate_(latitude) - # 006 : fc_build_coordinate_(longitude) - # Notes: - # * in terms of rule triggering, this is not distinct from the - # "normal" case : but latitude is now created as an aux-coord. - warning = "must be.* monotonic" - result = self.run_testcase( - warning_regex=warning, yco_values=[0.0, 0.0] - ) - self.check_result(result, yco_is_aux=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py deleted file mode 100644 index d962fc2758..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__hybrid_formulae.py +++ /dev/null @@ -1,313 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the engine.activate() call within the -`iris.fileformats.netcdf._load_cube` function. - -Test rules activation relating to hybrid vertical coordinates. - -""" -import iris.tests as tests # isort: skip - -import iris.fileformats._nc_load_rules.helpers as hh -from iris.tests.unit.fileformats.nc_load_rules.actions import ( - Mixin__nc_load_actions, -) - - -class Test__formulae_tests(Mixin__nc_load_actions, tests.IrisTest): - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def _make_testcase_cdl( - self, formula_root_name=None, term_names=None, extra_formula_type=None - ): - """Construct a testcase CDL for data with hybrid vertical coords.""" - if formula_root_name is None: - formula_root_name = "atmosphere_hybrid_height_coordinate" - if term_names is None: - term_names = hh.CF_COORD_VERTICAL.get(formula_root_name) - if term_names is None: - # unsupported type : just make something up - term_names = ["term1"] - - # Arrange to create additional term variables for an 'extra' hybrid - # formula, if requested. - if extra_formula_type is None: - term_names_extra = [] - phenom_coord_names = ["vert"] # always include the root variable - else: - phenom_coord_names = ["vert", "vert_2"] # two formula coords - term_names_extra = hh.CF_COORD_VERTICAL.get(extra_formula_type) - - # Build strings to define term variables. - formula_term_strings = [] - extra_formula_term_strings = [] - terms_string = "" - for term_name in term_names + term_names_extra: - term_varname = "v_" + term_name - # Include in the phenom coordinates list. - phenom_coord_names.append(term_varname) - term_string = f"{term_name}: {term_varname}" - if term_name in term_names: - # Include in the 'main' terms list. - formula_term_strings.append(term_string) - else: - # Include in the 'extra' terms list. - extra_formula_term_strings.append(term_string) - terms_string += f""" - double {term_varname}(h) ; - {term_varname}:long_name = "{term_name}_long_name" ; - {term_varname}:units = "m" ; -""" - - # Construct the reference strings. - phenom_coords_string = " ".join(phenom_coord_names) - formula_terms_string = " ".join(formula_term_strings) - extra_formula_terms_string = " ".join(extra_formula_term_strings) - - # Construct the 'extra' hybrid coord if requested. - if extra_formula_type is None: - extra_formula_string = "" - else: - # Create the lines to add an 'extra' formula. - # For now, put this on the same dim : makes no difference. - extra_formula_string = f""" - double vert_2(h) ; - vert_2:standard_name = "{extra_formula_type}" ; - vert_2:units = "m" ; - vert_2:formula_terms = "{extra_formula_terms_string}" ; -""" - - # Create the main result string. - cdl_str = f""" -netcdf test {{ -dimensions: - h = 2 ; -variables: - double phenom(h) ; - phenom:standard_name = "air_temperature" ; - phenom:units = "K" ; - phenom:coordinates = "{phenom_coords_string}" ; - double vert(h) ; - vert:standard_name = "{formula_root_name}" ; - vert:long_name = "hybrid_vertical" ; - vert:units = "m" ; - vert:formula_terms = "{formula_terms_string}" ; -{terms_string} -{extra_formula_string} -}} -""" - return cdl_str - - def check_result(self, cube, factory_type="_auto", formula_terms="_auto"): - """Check the result of a cube load with a hybrid vertical coord.""" - if factory_type == "_auto": - # replace with our 'default', which is hybrid-height. - # N.B. 'None' is different: it means expect *no* factory. - factory_type = "atmosphere_hybrid_height_coordinate" - self.assertEqual(cube._formula_type_name, factory_type) - - if formula_terms == "_auto": - # Set default terms-expected, according to the expected factory - # type. - if factory_type is None: - # If no factory, expect no identified terms. - formula_terms = [] - else: - # Expect the correct ones defined for the factory type. - formula_terms = hh.CF_COORD_VERTICAL[factory_type] - - # Compare the formula_terms list with the 'expected' ones. - # N.B. first make the 'expected' list lower case, as the lists in - # hh.CF_COORD_VERTICAL include uppercase, but rules outputs don't. - formula_terms = [term.lower() for term in formula_terms] - - # N.B. the terms dictionary can be missing, if there were none - actual_terms = cube._formula_terms_byname or {} - self.assertEqual(sorted(formula_terms), sorted(actual_terms.keys())) - - # Check that there is an aux-coord of the expected name for each term - for var_name in actual_terms.values(): - coords = cube.coords(var_name=var_name, dim_coords=False) - self.assertEqual(len(coords), 1) - - # - # Actual testcase routines - # - - def test_basic_hybridheight(self): - # Rules Triggered: - # 001 : fc_default - # 002 : fc_build_auxiliary_coordinate - # 003 : fc_build_auxiliary_coordinate - # 004 : fc_build_auxiliary_coordinate - # 005 : fc_build_auxiliary_coordinate - # 008 : fc_formula_type_atmosphere_hybrid_height_coordinate - # 009 : fc_formula_term(a) - # 010 : fc_formula_term(b) - # 011 : fc_formula_term(orog) - result = self.run_testcase() - self.check_result(result) - - def test_missing_term(self): - # Check behaviour when a term is missing. - # For the test, omit "orography", which is common in practice. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_build_auxiliary_coordinate - # 003 : fc_build_auxiliary_coordinate - # 004 : fc_build_auxiliary_coordinate - # 007 : fc_formula_type_atmosphere_hybrid_height_coordinate - # 008 : fc_formula_term(a) - # 009 : fc_formula_term(b) - result = self.run_testcase( - term_names=["a", "b"] # missing the 'orog' term - ) - self.check_result(result, formula_terms=["a", "b"]) - - def test_no_terms(self): - # Check behaviour when *all* terms are missing. - # N.B. for any _actual_ type, this is probably invalid and would fail? - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_build_auxiliary_coordinate - result = self.run_testcase( - formula_root_name="atmosphere_hybrid_height_coordinate", - term_names=[], - ) - # This does *not* trigger - # 'fc_formula_type_atmosphere_hybrid_height_coordinate' - # This is because, within the 'assert_case_specific_facts' routine, - # formula_roots are only recognised by scanning the identified - # formula_terms. - self.check_result(result, factory_type=None) - - def test_unrecognised_verticaltype(self): - # Set the root variable name to something NOT a recognised hybrid type. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_build_auxiliary_coordinate - # 003 : fc_build_auxiliary_coordinate - # 004 : fc_build_auxiliary_coordinate - # 007 : fc_formula_type(FAILED - unrecognised formula type = 'unknown') - # 008 : fc_formula_term(a) - # 009 : fc_formula_term(b) - result = self.run_testcase( - formula_root_name="unknown", - term_names=["a", "b"], - warning_regex="Ignored formula of unrecognised type: 'unknown'.", - ) - # Check that it picks up the terms, but *not* the factory root coord, - # which is simply discarded. - self.check_result(result, factory_type=None, formula_terms=["a", "b"]) - - def test_two_formulae(self): - # Construct an example with TWO hybrid coords. - # This is not errored, but we don't correctly support it. - # - # NOTE: the original Pyke implementation does not detect this problem - # By design, the new mechanism does + will raise a warning. - warning = ( - "Omitting factories for some hybrid coordinates.*" - "multiple hybrid coordinates.* not supported" - ) - - extra_type = "ocean_sigma_coordinate" - result = self.run_testcase( - extra_formula_type=extra_type, warning_regex=warning - ) - # NOTE: FOR NOW, check expected behaviour : only one factory will be - # built, but there are coordinates (terms) for both types. - # TODO: this is a bug and needs fixing : translation should handle - # multiple hybrid coordinates in a sensible way. - self.check_result( - result, - factory_type=extra_type, - formula_terms=["a", "b", "depth", "eta", "orog", "sigma"], - ) - - def test_atmosphere_sigma_coordinate(self): - hybrid_type = "atmosphere_sigma_coordinate" - term_names = hh.CF_COORD_VERTICAL[hybrid_type] - result = self.run_testcase( - formula_root_name=hybrid_type, term_names=term_names - ) - self.check_result( - result, factory_type=hybrid_type, formula_terms=term_names - ) - - def test_atmosphere_hybrid_sigma_pressure_coordinate(self): - hybrid_type = "atmosphere_hybrid_sigma_pressure_coordinate" - term_names = hh.CF_COORD_VERTICAL[hybrid_type] - result = self.run_testcase( - formula_root_name=hybrid_type, term_names=term_names - ) - self.check_result( - result, factory_type=hybrid_type, formula_terms=term_names - ) - - def test_ocean_sigma_z_coordinate(self): - hybrid_type = "ocean_sigma_z_coordinate" - term_names = hh.CF_COORD_VERTICAL[hybrid_type] - result = self.run_testcase( - formula_root_name=hybrid_type, term_names=term_names - ) - self.check_result( - result, factory_type=hybrid_type, formula_terms=term_names - ) - - def test_ocean_sigma_coordinate(self): - hybrid_type = "ocean_sigma_coordinate" - term_names = hh.CF_COORD_VERTICAL[hybrid_type] - result = self.run_testcase( - formula_root_name=hybrid_type, term_names=term_names - ) - self.check_result( - result, factory_type=hybrid_type, formula_terms=term_names - ) - - def test_ocean_s_coordinate(self): - hybrid_type = "ocean_s_coordinate" - term_names = hh.CF_COORD_VERTICAL[hybrid_type] - result = self.run_testcase( - formula_root_name=hybrid_type, term_names=term_names - ) - self.check_result( - result, factory_type=hybrid_type, formula_terms=term_names - ) - - def test_ocean_s_coordinate_g1(self): - hybrid_type = "ocean_s_coordinate_g1" - term_names = hh.CF_COORD_VERTICAL[hybrid_type] - result = self.run_testcase( - formula_root_name=hybrid_type, term_names=term_names - ) - self.check_result( - result, factory_type=hybrid_type, formula_terms=term_names - ) - - def test_ocean_s_coordinate_g2(self): - hybrid_type = "ocean_s_coordinate_g2" - term_names = hh.CF_COORD_VERTICAL[hybrid_type] - result = self.run_testcase( - formula_root_name=hybrid_type, term_names=term_names - ) - self.check_result( - result, factory_type=hybrid_type, formula_terms=term_names - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__latlon_dimcoords.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__latlon_dimcoords.py deleted file mode 100644 index dfa862c4d1..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__latlon_dimcoords.py +++ /dev/null @@ -1,337 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the engine.activate() call within the -`iris.fileformats.netcdf._load_cube` function. - -Tests for rules behaviour in identifying latitude/longitude dim-coords, both -rotated and non-rotated. - -""" -import iris.tests as tests # isort: skip - -from iris.coord_systems import GeogCS, RotatedGeogCS -from iris.tests.unit.fileformats.nc_load_rules.actions import ( - Mixin__nc_load_actions, -) - - -class Mixin_latlon_dimcoords(Mixin__nc_load_actions): - # Tests for the recognition and construction of latitude/longitude coords. - - # Control to test either longitude or latitude coords. - # Set by inheritor classes, which are actual TestCases. - lat_1_or_lon_0 = None - - def setUp(self): - super().setUp() - # Generate some useful settings : just to generalise operation over - # both latitude and longitude. - islat = self.lat_1_or_lon_0 - assert islat in (0, 1) - self.unrotated_name = "latitude" if islat else "longitude" - self.rotated_name = "grid_latitude" if islat else "grid_longitude" - self.unrotated_units = "degrees_north" if islat else "degrees_east" - # Note: there are many alternative valid forms for the rotated units, - # but we are not testing that here. - self.rotated_units = "degrees" # NB this one is actually constant - self.axis = "y" if islat else "x" - - def _make_testcase_cdl( - self, - standard_name=None, - long_name=None, - var_name=None, - units=None, - axis=None, - grid_mapping=None, - ): - # Inner routine called by 'run_testcase' (in Mixin__nc_load_actions), - # to generate CDL which is then translated into a testfile and loaded. - if var_name is None: - # Can't have *no* var-name - # N.B. it is also the name of the dimension. - var_name = "dim" - - def attribute_str(name, value): - if value is None or value == "": - result = "" - else: - result = f'{var_name}:{name} = "{value}" ;' - - return result - - standard_name_str = attribute_str("standard_name", standard_name) - long_name_str = attribute_str("long_name", long_name) - units_str = attribute_str("units", units) - axis_str = attribute_str("axis", axis) - if grid_mapping: - grid_mapping_str = 'phenom:grid_mapping = "crs" ;' - else: - grid_mapping_str = "" - - assert grid_mapping in (None, "latlon", "rotated") - if grid_mapping is None: - crs_str = "" - elif grid_mapping == "latlon": - crs_str = """ - int crs ; - crs:grid_mapping_name = "latitude_longitude" ; - crs:semi_major_axis = 6371000.0 ; - crs:inverse_flattening = 1000. ; -""" - elif grid_mapping == "rotated": - crs_str = """ - int crs ; - crs:grid_mapping_name = "rotated_latitude_longitude" ; - crs:grid_north_pole_latitude = 32.5 ; - crs:grid_north_pole_longitude = 170. ; -""" - - cdl_string = f""" -netcdf test {{ - dimensions: - {var_name} = 2 ; - variables: - double {var_name}({var_name}) ; - {standard_name_str} - {units_str} - {long_name_str} - {axis_str} - double phenom({var_name}) ; - phenom:standard_name = "air_temperature" ; - phenom:units = "K" ; - {grid_mapping_str} - {crs_str} - data: - {var_name} = 0., 1. ; -}} -""" - return cdl_string - - def check_result( - self, - cube, - standard_name, - long_name, - units, - crs=None, - context_message="", - ): - # Check the existence, standard-name, long-name, units and coord-system - # of the resulting coord. Also that it is always a dim-coord. - # NOTE: there is no "axis" arg, as this information does *not* appear - # as a separate property (or attribute) of the resulting coord. - # However, whether the file variable has an axis attribute *does* - # affect the results here, in some cases. - coords = cube.coords() - # There should be one and only one coord. - self.assertEqual(1, len(coords)) - # It should also be a dim-coord - self.assertEqual(1, len(cube.coords(dim_coords=True))) - (coord,) = coords - if self.debug: - print("") - print("DEBUG : result coord =", coord) - print("") - - coord_stdname, coord_longname, coord_units, coord_crs = [ - getattr(coord, name) - for name in ("standard_name", "long_name", "units", "coord_system") - ] - self.assertEqual(standard_name, coord_stdname, context_message) - self.assertEqual(long_name, coord_longname, context_message) - self.assertEqual(units, coord_units, context_message) - assert crs in (None, "latlon", "rotated") - if crs is None: - self.assertEqual(None, coord_crs, context_message) - elif crs == "latlon": - self.assertIsInstance(coord_crs, GeogCS, context_message) - elif crs == "rotated": - self.assertIsInstance(coord_crs, RotatedGeogCS, context_message) - - # - # Testcase routines - # - # NOTE: all these testcases have been verified against the older behaviour - # in v3.0.4, based on Pyke rules. - # - - def test_minimal(self): - # Nothing but a var-name --> unrecognised dim-coord. - result = self.run_testcase() - self.check_result(result, None, None, "unknown") - - def test_fullinfo_unrotated(self): - # Check behaviour with all normal info elements for 'unrotated' case. - # Includes a grid-mapping, but no axis (should not be needed). - result = self.run_testcase( - standard_name=self.unrotated_name, - units=self.unrotated_units, - grid_mapping="latlon", - ) - self.check_result( - result, self.unrotated_name, None, "degrees", "latlon" - ) - - def test_fullinfo_rotated(self): - # Check behaviour with all normal info elements for 'rotated' case. - # Includes a grid-mapping, but no axis (should not be needed). - result = self.run_testcase( - standard_name=self.rotated_name, - units=self.rotated_units, - grid_mapping="rotated", - ) - self.check_result( - result, self.rotated_name, None, "degrees", "rotated" - ) - - def test_axis(self): - # A suitable axis --> unrotated lat/lon coord, but unknown units. - result = self.run_testcase(axis=self.axis) - self.check_result(result, self.unrotated_name, None, "unknown") - - def test_units_unrotated(self): - # With a unit like 'degrees_east', we automatically identify this as a - # latlon coord, *and* convert units to plain 'degrees' on loading. - result = self.run_testcase(units=self.unrotated_units) - self.check_result(result, self.unrotated_name, None, "degrees") - - def test_units_rotated(self): - # With no info except a "degrees" unit, we **don't** identify a latlon, - # i.e. we do not set the standard-name - result = self.run_testcase(units="degrees") - self.check_result(result, None, None, "degrees") - - def test_units_unrotated_gridmapping(self): - # With an unrotated unit *AND* a suitable grid-mapping, we identify a - # rotated latlon coordinate + assign it the coord-system. - result = self.run_testcase( - units=self.unrotated_units, grid_mapping="latlon" - ) - self.check_result( - result, self.unrotated_name, None, "degrees", "latlon" - ) - - def test_units_rotated_gridmapping_noname(self): - # Rotated units and grid-mapping, but *without* the expected name. - # Does not translate, no coord-system (i.e. grid-mapping is discarded). - result = self.run_testcase( - units="degrees", - grid_mapping="rotated", - ) - self.check_result(result, None, None, "degrees", None) - - def test_units_rotated_gridmapping_withname(self): - # With a "degrees" unit, a rotated grid-mapping *AND* a suitable - # standard-name, it recognises a rotated dimcoord. - result = self.run_testcase( - standard_name=self.rotated_name, - units="degrees", - grid_mapping="rotated", - ) - self.check_result( - result, self.rotated_name, None, "degrees", "rotated" - ) - - def test_units_rotated_gridmapping_varname(self): - # Same but with var-name containing the standard-name : in this case we - # get NO COORDINATE-SYSTEM (which is a bit weird). - result = self.run_testcase( - var_name=self.rotated_name, - units="degrees", - grid_mapping="rotated", - ) - self.check_result(result, self.rotated_name, None, "degrees", None) - - def test_varname_unrotated(self): - # With a recognised name in the var-name, we set standard-name. - # But units are left undetermined. - result = self.run_testcase(var_name=self.unrotated_name) - self.check_result(result, self.unrotated_name, None, "unknown") - - def test_varname_rotated(self): - # With a *rotated* name in the var-name, we set standard-name. - # But units are left undetermined. - result = self.run_testcase(var_name=self.rotated_name) - self.check_result(result, self.rotated_name, None, "unknown") - - def test_varname_unrotated_units_rotated(self): - # With a "degrees" unit and a suitable var-name, we do identify - # (= set standard-name). - # N.B. this accepts "degrees" as a generic term, and so does *not* - # interpret it as a rotated coordinate. - result = self.run_testcase( - var_name=self.unrotated_name, units="degrees" - ) - self.check_result(result, self.unrotated_name, None, "degrees") - - def test_longname(self): - # A recognised form in long-name is *not* translated into standard-name. - result = self.run_testcase(long_name=self.unrotated_name) - self.check_result(result, None, self.unrotated_name, "unknown") - - def test_stdname_unrotated(self): - # Only an (unrotated) standard name : units is not specified - result = self.run_testcase(standard_name=self.unrotated_name) - self.check_result(result, self.unrotated_name, None, None) - - def test_stdname_rotated(self): - # Only a (rotated) standard name : units is not specified - result = self.run_testcase(standard_name=self.rotated_name) - self.check_result(result, self.rotated_name, None, None) - - def test_stdname_unrotated_gridmapping(self): - # An unrotated standard-name and grid-mapping, translates into a - # coordinate system. - result = self.run_testcase( - standard_name=self.unrotated_name, grid_mapping="latlon" - ) - self.check_result( - result, self.unrotated_name, None, "unknown", "latlon" - ) - - def test_stdname_rotated_gridmapping(self): - # An *rotated* standard-name and grid-mapping, translates into a - # coordinate system. - result = self.run_testcase( - standard_name=self.rotated_name, grid_mapping="rotated" - ) - self.check_result(result, self.rotated_name, None, None, "rotated") - - -class Test__longitude_coords(Mixin_latlon_dimcoords, tests.IrisTest): - lat_1_or_lon_0 = 0 - - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def setUp(self): - super().setUp() - - -class Test__latitude_coords(Mixin_latlon_dimcoords, tests.IrisTest): - lat_1_or_lon_0 = 1 - - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def setUp(self): - super().setUp() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py deleted file mode 100644 index ffe00c8c19..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__miscellaneous.py +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the engine.activate() call within the -`iris.fileformats.netcdf._load_cube` function. - -Tests for rules activation relating to some isolated aspects : - * UKMO um-specific metadata - * label coordinates - * cell measures - * ancillary variables - -""" -import iris.tests as tests # isort: skip - -from iris.coords import AncillaryVariable, AuxCoord, CellMeasure -from iris.fileformats.pp import STASH -from iris.tests.unit.fileformats.nc_load_rules.actions import ( - Mixin__nc_load_actions, -) - - -class Test__ukmo_attributes(Mixin__nc_load_actions, tests.IrisTest): - # Tests for handling of the special UM-specific data-var attributes. - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def _make_testcase_cdl(self, **add_attrs): - phenom_attrs_string = "" - for key, value in add_attrs.items(): - phenom_attrs_string += f""" - phenom:{key} = "{value}" ; -""" - - cdl_string = f""" -netcdf test {{ - dimensions: - xdim = 2 ; - variables: - double phenom(xdim) ; - phenom:standard_name = "air_temperature" ; - phenom:units = "K" ; -{phenom_attrs_string} -}} -""" - return cdl_string - - def check_result(self, cube, stashcode=None, processflags=None): - cube_stashattr = cube.attributes.get("STASH") - cube_processflags = cube.attributes.get("ukmo__process_flags") - - if stashcode is not None: - self.assertIsInstance(cube_stashattr, STASH) - self.assertEqual(str(stashcode), str(cube_stashattr)) - else: - self.assertIsNone(cube_stashattr) - - if processflags is not None: - self.assertIsInstance(cube_processflags, tuple) - self.assertEqual(set(cube_processflags), set(processflags)) - else: - self.assertIsNone(cube_processflags) - - # - # Testcase routines - # - stashcode = "m01s02i034" # Just one valid STASH msi string for testing - - def test_stash(self): - cube = self.run_testcase(um_stash_source=self.stashcode) - self.check_result(cube, stashcode=self.stashcode) - - def test_stash_altname(self): - cube = self.run_testcase(ukmo__um_stash_source=self.stashcode) - self.check_result(cube, stashcode=self.stashcode) - - def test_stash_empty(self): - value = "" - cube = self.run_testcase(ukmo__um_stash_source=value) - self.assertNotIn("STASH", cube.attributes) - self.assertEqual(cube.attributes["ukmo__um_stash_source"], value) - - def test_stash_invalid(self): - value = "XXX" - cube = self.run_testcase(ukmo__um_stash_source="XXX") - self.assertNotIn("STASH", cube.attributes) - self.assertEqual(cube.attributes["ukmo__um_stash_source"], value) - - def test_processflags_single(self): - cube = self.run_testcase(ukmo__process_flags="this") - self.check_result(cube, processflags=["this"]) - - def test_processflags_multi_with_underscores(self): - flags_testinput = "this that_1 the_other_one x" - flags_expectresult = ["this", "that 1", "the other one", "x"] - cube = self.run_testcase(ukmo__process_flags=flags_testinput) - self.check_result(cube, processflags=flags_expectresult) - - def test_processflags_empty(self): - cube = self.run_testcase(ukmo__process_flags="") - expected_result = [""] # May seem odd, but that's what it does. - self.check_result(cube, processflags=expected_result) - - -class Test__labels_cellmeasures_ancils(Mixin__nc_load_actions, tests.IrisTest): - # Tests for some simple rules that translate facts directly into cube data, - # with no alternative actions, complications or failure modes to test. - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def _make_testcase_cdl( - self, - include_label=False, - include_cellmeasure=False, - include_ancil=False, - ): - phenom_extra_attrs_string = "" - extra_vars_string = "" - - if include_label: - phenom_extra_attrs_string += """ - phenom:coordinates = "v_label" ; -""" - extra_vars_string += """ - char v_label(xdim, strdim) ; - v_label:long_name = "string data" ; -""" - - if include_cellmeasure: - # One simple case : a valid link + a variable definition. - phenom_extra_attrs_string += """ - phenom:cell_measures = "area: v_cellm" ; -""" - extra_vars_string += """ - double v_cellm(xdim) ; - v_cellm:long_name = "cell areas" ; -""" - - if include_ancil: - # One simple case : a valid link + a variable definition. - phenom_extra_attrs_string += """ - phenom:ancillary_variables = "v_ancil" ; -""" - extra_vars_string += """ - double v_ancil(xdim) ; - v_ancil:long_name = "ancillary values" ; -""" - cdl_string = f""" - netcdf test {{ - dimensions: - xdim = 2 ; - strdim = 5 ; - variables: - double phenom(xdim) ; - phenom:standard_name = "air_temperature" ; - phenom:units = "K" ; -{phenom_extra_attrs_string} -{extra_vars_string} - }} - """ - return cdl_string - - def check_result( - self, - cube, - expect_label=False, - expect_cellmeasure=False, - expect_ancil=False, - ): - label_coords = cube.coords(var_name="v_label") - if expect_label: - self.assertEqual(len(label_coords), 1) - (coord,) = label_coords - self.assertIsInstance(coord, AuxCoord) - self.assertEqual(coord.dtype.kind, "U") - else: - self.assertEqual(len(label_coords), 0) - - cell_measures = cube.cell_measures() - if expect_cellmeasure: - self.assertEqual(len(cell_measures), 1) - (cellm,) = cell_measures - self.assertIsInstance(cellm, CellMeasure) - else: - self.assertEqual(len(cell_measures), 0) - - ancils = cube.ancillary_variables() - if expect_ancil: - self.assertEqual(len(ancils), 1) - (ancil,) = ancils - self.assertIsInstance(ancil, AncillaryVariable) - else: - self.assertEqual(len(ancils), 0) - - def test_label(self): - cube = self.run_testcase(include_label=True) - self.check_result(cube, expect_label=True) - - def test_ancil(self): - cube = self.run_testcase(include_ancil=True) - self.check_result(cube, expect_ancil=True) - - def test_cellmeasure(self): - cube = self.run_testcase(include_cellmeasure=True) - self.check_result(cube, expect_cellmeasure=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py b/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py deleted file mode 100644 index 59ffa30684..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/actions/test__time_coords.py +++ /dev/null @@ -1,462 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the engine.activate() call within the -`iris.fileformats.netcdf._load_cube` function. - -Tests for rules activation relating to 'time' and 'time_period' coords. - -""" -import iris.tests as tests # isort: skip - -from iris.coords import AuxCoord, DimCoord -from iris.tests.unit.fileformats.nc_load_rules.actions import ( - Mixin__nc_load_actions, -) - - -class Opts(dict): - # A dict-like thing which provides '.' access in place of indexing. - def __init__(self, **kwargs): - # Init like a dict - super().__init__(**kwargs) - # Alias contents "self['key']", as properties "self.key" - # See: https://stackoverflow.com/a/14620633/2615050 - self.__dict__ = self - - -# Per-coord options settings for testcase definitions. -_COORD_OPTIONS_TEMPLATE = { - "which": "", # set to "something" - "stdname": "_auto_which", # default = time / time_period - "varname": "_as_which", # default = time / period - "dimname": "_as_which", - "in_phenomvar_dims": True, - "in_phenomvar_coords": False, # set for an aux-coord - "values_all_zero": False, # set to block CFDimensionVariable identity - "units": "_auto_which", # specific to time/period -} - - -class Mixin__timecoords__common(Mixin__nc_load_actions): - def _make_testcase_cdl( - self, - phenom_dims="_auto", # =get from time+period opts - phenom_coords="_auto", # =get from time+period opts - time_opts=None, - period_opts=None, - timedim_name="time", - perioddim_name="period", - ): - opt_t = None - opt_p = None - if time_opts is not None: - # Convert a non-null kwarg into an options dict for 'time' options - opt_t = Opts(**_COORD_OPTIONS_TEMPLATE) - opt_t.update(which="time", **time_opts) - if period_opts is not None: - # Convert a non-null kwarg into an options dict for 'period' options - opt_p = Opts(**_COORD_OPTIONS_TEMPLATE) - opt_p.update(which="period", **period_opts) - - # Define the 'standard' dimensions which we will create - # NB we don't necessarily *use* either of these - dims_and_lens = {timedim_name: 2, perioddim_name: 3} - dims_string = "\n".join( - [ - f" {name} = {length} ;" - for name, length in dims_and_lens.items() - ] - ) - - phenom_auto_dims = [] - phenom_auto_coords = [] - coord_variables_string = "" - data_string = "" - for opt in (opt_t, opt_p): - # Handle computed defaults and common info for both coord options. - if opt: - if opt.which not in ("time", "period"): - raise ValueError(f"unrecognised opt.which={opt.which}") - - # Do computed defaults. - if opt.stdname == "_auto_which": - if opt.which == "time": - opt.stdname = "time" - else: - assert opt.which == "period" - opt.stdname = "forecast_period" - if opt.varname == "_as_which": - opt.varname = opt.which - if opt.dimname == "_as_which": - opt.dimname = opt.which - if opt.units == "_auto_which": - if opt.which == "time": - opt.units = "hours since 2000-01-01" - else: - assert opt.which == "period" - opt.units = "hours" - - # Build 'auto' lists of phenom dims and (aux) coordinates. - if opt.in_phenomvar_dims: - phenom_auto_dims.append(opt.dimname) - if opt.in_phenomvar_coords: - phenom_auto_coords.append(opt.varname) - - # Add a definition of the coord variable. - coord_variables_string += f""" - double {opt.varname}({opt.dimname}) ; - {opt.varname}:standard_name = "{opt.stdname}" ; - {opt.varname}:units = "{opt.units}" ; -""" - # NOTE: we don't bother with an 'axis' property. - # We can probe the behaviour we need without that, because we - # are *not* testing the cf.py categorisation code, or the - # helper "build_xxx" routines. - - # Define coord-var data values (so it can be a dimension). - varname = opt.varname - if opt.values_all_zero: - # Use 'values_all_zero' to prevent a dim-var from - # identifying as a CFDimensionCoordinate (as it is - # non-monotonic). - dim_vals = [0.0] * dims_and_lens[opt.dimname] - else: - # "otherwise", assign an ascending sequence. - dim_vals = range(dims_and_lens[opt.dimname]) - dimvals_string = ", ".join(f"{val:0.1f}" for val in dim_vals) - data_string += f"\n {varname} = {dimvals_string} ;" - - if phenom_dims == "_auto": - phenom_dims = phenom_auto_dims - if not phenom_dims: - phenom_dims_string = "" - else: - phenom_dims_string = ", ".join(phenom_dims) - - if phenom_coords == "_auto": - phenom_coords = phenom_auto_coords - if not phenom_coords: - phenom_coords_string = "" - else: - phenom_coords_string = " ".join(phenom_coords) - phenom_coords_string = ( - " " - f'phenom:coordinates = "{phenom_coords_string}" ; ' - ) - - # Create a testcase with time dims + coords. - cdl_string = f""" -netcdf test {{ - dimensions: -{dims_string} - variables: - double phenom({phenom_dims_string}) ; - phenom:standard_name = "air_temperature" ; - phenom:units = "K" ; -{phenom_coords_string} - -{coord_variables_string} - data: -{data_string} -}} -""" - return cdl_string - - def check_result(self, cube, time_is="dim", period_is="missing"): - """ - Check presence of expected dim/aux-coords in the result cube. - - Both of 'time_is' and 'period_is' can take values 'dim', 'aux' or - 'missing'. - - """ - options = ("dim", "aux", "missing") - msg = f'Invalid "{{name}}" = {{opt}} : Not one of {options!r}.' - if time_is not in options: - raise ValueError(msg.format(name="time_is", opt=time_is)) - if period_is not in options: - raise ValueError(msg.format(name="period_is", opt=period_is)) - - # Get the facts we want to check - time_name = "time" - period_name = "forecast_period" - time_dimcos = cube.coords(time_name, dim_coords=True) - time_auxcos = cube.coords(time_name, dim_coords=False) - period_dimcos = cube.coords(period_name, dim_coords=True) - period_auxcos = cube.coords(period_name, dim_coords=False) - - if time_is == "dim": - self.assertEqual(len(time_dimcos), 1) - self.assertEqual(len(time_auxcos), 0) - elif time_is == "aux": - self.assertEqual(len(time_dimcos), 0) - self.assertEqual(len(time_auxcos), 1) - else: - self.assertEqual(len(time_dimcos), 0) - self.assertEqual(len(time_auxcos), 0) - - if period_is == "dim": - self.assertEqual(len(period_dimcos), 1) - self.assertEqual(len(period_auxcos), 0) - elif period_is == "aux": - self.assertEqual(len(period_dimcos), 0) - self.assertEqual(len(period_auxcos), 1) - else: - self.assertEqual(len(period_dimcos), 0) - self.assertEqual(len(period_auxcos), 0) - - # Also check expected built Coord types. - if time_is == "dim": - self.assertIsInstance(time_dimcos[0], DimCoord) - elif time_is == "aux": - self.assertIsInstance(time_auxcos[0], AuxCoord) - - if period_is == "dim": - self.assertIsInstance(period_dimcos[0], DimCoord) - elif period_is == "aux": - self.assertIsInstance(period_auxcos[0], AuxCoord) - - -class Mixin__singlecoord__tests(Mixin__timecoords__common): - # Coordinate tests to be run for both 'time' and 'period' coordinate vars. - # Set (in inheritors) to select time/period testing. - which = None - - def run_testcase(self, coord_dim_name=None, **opts): - """ - Specialise 'run_testcase' for single-coord 'time' or 'period' testing. - """ - which = self.which - assert which in ("time", "period") - - # Separate the 'Opt' keywords from "others" : others are passed - # directly to the parent routine, whereas 'Opt' ones are passed to - # 'time_opts' / 'period_opts' keys accordingly. - general_opts = {} - for key, value in list(opts.items()): - if key not in _COORD_OPTIONS_TEMPLATE.keys(): - del opts[key] - general_opts[key] = value - - if coord_dim_name is not None: - # Translate this into one of timedim_name/perioddim_name - general_opts[f"{which}dim_name"] = coord_dim_name - - period_opts = None - time_opts = None - if which == "time": - time_opts = opts - else: - period_opts = opts - - result = super().run_testcase( - time_opts=time_opts, period_opts=period_opts, **general_opts - ) - - return result - - def check_result(self, cube, coord_is="dim"): - """ - Specialise 'check_result' for single-coord 'time' or 'period' testing. - """ - # Pass generic 'coord_is' option to parent as time/period options. - which = self.which - assert which in ("time", "period") - - if which == "time": - time_is = coord_is - period_is = "missing" - else: - period_is = coord_is - time_is = "missing" - - super().check_result(cube, time_is=time_is, period_is=period_is) - - # - # Generic single-coordinate testcases. - # ( these are repeated for both 'time' and 'time_period' ) - # - - def test_dimension(self): - # Coord is a normal dimension --> dimcoord - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(time[[_period]]) - # 003 : fc_build_coordinate_(time[[_period]]) - result = self.run_testcase() - self.check_result(result, "dim") - - def test_dimension_in_phenom_coords(self): - # Dimension coord also present in phenom:coords. - # Strictly wrong but a common error in datafiles : must tolerate. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(time[[_period]]) - # 003 : fc_build_coordinate_(time[[_period]]) - result = self.run_testcase(in_phenomvar_coords=True) - self.check_result(result, "dim") - - def test_dim_nonmonotonic(self): - # Coord has all-zero values, which prevents it being a dimcoord. - # The rule has a special way of treating it as an aux coord - # -- even though it doesn't appear in the phenom coords. - # ( Done by the build_coord routine, so not really a rules issue). - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(time[[_period]]) - # 003 : fc_build_coordinate_(time[[_period]]) - msg = "Failed to create.* dimension coordinate" - result = self.run_testcase(values_all_zero=True, warning_regex=msg) - self.check_result(result, "aux") - - def test_dim_fails_typeident(self): - # Provide a coord variable, identified as a CFDimensionCoordinate by - # cf.py, but with the "wrong" units for a time or period coord. - # This causes it to fail both 'is_time' and 'is_period' tests and so, - # within the 'action_provides_coordinate' routine, does not trigger as - # a 'provides_coord_(time[[_period]])' rule, but instead as a - # 'default_coordinate_(provide-phase)'. - # As a result, it is built as a 'miscellaneous' dim-coord. - # N.B. this makes *no* practical difference, because a 'misc' dim - # coord is still a dim coord (albeit one with incorrect units). - # N.B.#2 that is different from lat/lon coords, where the coord-specific - # 'build' rules have the extra effect of setting a fixed standard-name. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_default_coordinate_(provide-phase) - # 003 : fc_build_coordinate_(miscellaneous) - result = self.run_testcase(units="1") - self.check_result(result, "dim") - - def test_aux(self): - # time/period is installed as an auxiliary coord. - # For this, rename both DIMENSIONS, so that the generated coords are - # not actually CF coordinates. - # For a valid case, we must *also* have a ref in phenom:coordinates - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_build_auxiliary_coordinate_time[[_period]] - result = self.run_testcase( - coord_dim_name="dim_renamed", - dimname="dim_renamed", - in_phenomvar_coords=True, - ) - self.check_result(result, "aux") - - def test_aux_not_in_phenom_coords(self): - # time/period is installed as an auxiliary coord, - # but we DIDN'T list it in phenom:coords -- otherwise as previous. - # Should have no result at all. - # - # Rules Triggered: - # 001 : fc_default - result = self.run_testcase( - coord_dim_name="dim_renamed", - dimname="dim_renamed", - in_phenomvar_coords=False, - ) # "should" be True for an aux-coord - self.check_result(result, "missing") - - def test_aux_fails_typeident(self): - # We provide a non-dimension coord variable, identified as a - # CFAuxiliaryCoordinate by cf.py, but we also give it "wrong" units, - # unsuitable for a time or period coord. - # Because it fails both 'is_time' and 'is_period' tests, it then does - # not trigger 'fc_build_auxiliary_coordinate_time[[_period]]'. - # As in the above testcase 'test_dim_fails_typeident', the routine - # 'action_build_auxiliary_coordinate' therefore builds this as a - # 'miscellaneous' rather than a specific coord type (time or period). - # However, also as in that other case, this makes absolutely no - # practical difference -- unlike for latitude or longitutude coords, - # where it may affect the standard-name. - # - # Rules Triggered: - # 001 : fc_default - # 002 : fc_build_auxiliary_coordinate - result = self.run_testcase( - coord_dim_name="dim_renamed", - dimname="dim_renamed", - in_phenomvar_coords=True, - units="1", - ) - self.check_result(result, "aux") - - -class Test__time(Mixin__singlecoord__tests, tests.IrisTest): - # Run 'time' coord tests - which = "time" - - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - -class Test__period(Mixin__singlecoord__tests, tests.IrisTest): - # Run 'time_period' coord tests - which = "period" - - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - -class Test__dualcoord(Mixin__timecoords__common, tests.IrisTest): - # Coordinate tests for a combination of 'time' and 'time_period'. - # Not strictly necessary, as handling is independent, but a handy check - # on typical usage. - @classmethod - def setUpClass(cls): - super().setUpClass() - - @classmethod - def tearDownClass(cls): - super().tearDownClass() - - def test_time_and_period(self): - # Test case with both 'time' and 'period', with separate dims. - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(time) - # 003 : fc_provides_coordinate_(time_period) - # 004 : fc_build_coordinate_(time) - # 005 : fc_build_coordinate_(time_period) - result = self.run_testcase(time_opts={}, period_opts={}) - self.check_result(result, time_is="dim", period_is="dim") - - def test_time_dim_period_aux(self): - # Test case with both 'time' and 'period' sharing a dim. - # Rules Triggered: - # 001 : fc_default - # 002 : fc_provides_coordinate_(time) - # 003 : fc_build_coordinate_(time) - # 004 : fc_build_auxiliary_coordinate_time_period - result = self.run_testcase( - time_opts={}, - period_opts=dict( - dimname="time", - in_phenomvar_dims=False, - in_phenomvar_coords=True, - ), - ) - self.check_result(result, time_is="dim", period_is="aux") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/engine/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/engine/__init__.py deleted file mode 100644 index e6508bea85..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/engine/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the module -:mod:`iris.fileformats.netcdf._nc_load_rules.engine` . - -""" diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/engine/test_engine.py b/lib/iris/tests/unit/fileformats/nc_load_rules/engine/test_engine.py deleted file mode 100644 index df5fbd4922..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/engine/test_engine.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :mod:`iris.fileformats._nc_load_rules.engine` module. - -""" -from unittest import mock - -from iris.fileformats._nc_load_rules.engine import Engine, FactEntity -import iris.tests as tests - - -class Test_Engine(tests.IrisTest): - def setUp(self): - self.empty_engine = Engine() - engine = Engine() - engine.add_fact("this", ("that", "other")) - self.nonempty_engine = engine - - def test__init(self): - # Check that init creates an empty Engine. - engine = Engine() - self.assertIsInstance(engine, Engine) - self.assertIsInstance(engine.facts, FactEntity) - self.assertEqual(list(engine.facts.entity_lists.keys()), []) - - def test_reset(self): - # Check that calling reset() causes a non-empty engine to be emptied. - engine = self.nonempty_engine - fact_names = list(engine.facts.entity_lists.keys()) - self.assertNotEqual(len(fact_names), 0) - engine.reset() - fact_names = list(engine.facts.entity_lists.keys()) - self.assertEqual(len(fact_names), 0) - - def test_activate(self): - # Check that calling engine.activate() --> actions.run_actions(engine) - engine = self.empty_engine - target = "iris.fileformats._nc_load_rules.engine.run_actions" - run_call = self.patch(target) - engine.activate() - self.assertEqual(run_call.call_args_list, [mock.call(engine)]) - - def test_add_case_specific_fact__newname(self): - # Adding a new fact to a new fact-name records as expected. - engine = self.nonempty_engine - engine.add_case_specific_fact("new_fact", ("a1", "a2")) - self.assertEqual(engine.fact_list("new_fact"), [("a1", "a2")]) - - def test_add_case_specific_fact__existingname(self): - # Adding a new fact to an existing fact-name records as expected. - engine = self.nonempty_engine - name = "this" - self.assertEqual(engine.fact_list(name), [("that", "other")]) - engine.add_case_specific_fact(name, ("yetanother",)) - self.assertEqual( - engine.fact_list(name), [("that", "other"), ("yetanother",)] - ) - - def test_add_case_specific_fact__emptyargs(self): - # Check that empty args work ok, and will create a new fact. - engine = self.empty_engine - engine.add_case_specific_fact("new_fact", ()) - self.assertIn("new_fact", engine.facts.entity_lists) - self.assertEqual(engine.fact_list("new_fact"), [()]) - - def test_add_fact(self): - # Check that 'add_fact' is equivalent to (short for) a call to - # 'add_case_specific_fact'. - engine = self.empty_engine - target = ( - "iris.fileformats._nc_load_rules.engine.Engine" - ".add_case_specific_fact" - ) - acsf_call = self.patch(target) - engine.add_fact("extra", ()) - self.assertEqual(acsf_call.call_count, 1) - self.assertEqual( - acsf_call.call_args_list, - [mock.call(fact_name="extra", fact_arglist=())], - ) - - def test_get_kb(self): - # Check that this stub just returns the facts database. - engine = self.nonempty_engine - kb = engine.get_kb() - self.assertIsInstance(kb, FactEntity) - self.assertIs(kb, engine.facts) - - def test_fact_list__existing(self): - self.assertEqual( - self.nonempty_engine.fact_list("this"), [("that", "other")] - ) - - def test_fact_list__nonexisting(self): - self.assertEqual(self.empty_engine.fact_list("odd-unknown"), []) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/__init__.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/__init__.py deleted file mode 100644 index 69a536b9ae..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the module -:mod:`iris.fileformats.netcdf._nc_load_rules.helpers` . - -""" diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_albers_equal_area_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_albers_equal_area_coordinate_system.py deleted file mode 100644 index c040d43ca0..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_albers_equal_area_coordinate_system.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_albers_equal_area_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import AlbersEqualArea -from iris.fileformats._nc_load_rules.helpers import ( - build_albers_equal_area_coordinate_system, -) - - -class TestBuildAlbersEqualAreaCoordinateSystem(tests.IrisTest): - def _test(self, inverse_flattening=False, no_optionals=False): - if no_optionals: - # Most properties are optional for this system. - gridvar_props = {} - # Setup all the expected default values - test_lat = 0 - test_lon = 0 - test_easting = 0 - test_northing = 0 - test_parallels = (20, 50) - else: - # Choose test values and setup corresponding named properties. - test_lat = -35 - test_lon = 175 - test_easting = -100 - test_northing = 200 - test_parallels = (-27, 3) - gridvar_props = dict( - latitude_of_projection_origin=test_lat, - longitude_of_central_meridian=test_lon, - false_easting=test_easting, - false_northing=test_northing, - standard_parallel=test_parallels, - ) - - # Add ellipsoid args. - gridvar_props["semi_major_axis"] = 6377563.396 - if inverse_flattening: - gridvar_props["inverse_flattening"] = 299.3249646 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, inverse_flattening=299.3249646 - ) - else: - gridvar_props["semi_minor_axis"] = 6356256.909 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, 6356256.909 - ) - - cf_grid_var = mock.Mock(spec=[], **gridvar_props) - - cs = build_albers_equal_area_coordinate_system(None, cf_grid_var) - - expected = AlbersEqualArea( - latitude_of_projection_origin=test_lat, - longitude_of_central_meridian=test_lon, - false_easting=test_easting, - false_northing=test_northing, - standard_parallels=test_parallels, - ellipsoid=expected_ellipsoid, - ) - - self.assertEqual(cs, expected) - - def test_basic(self): - self._test() - - def test_inverse_flattening(self): - # Check when inverse_flattening is provided instead of semi_minor_axis. - self._test(inverse_flattening=True) - - def test_no_optionals(self): - # Check defaults, when all optional attributes are absent. - self._test(no_optionals=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py deleted file mode 100644 index b057a41a3e..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_ancil_var.py +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.build_ancil_var`. - -""" - -from unittest import mock - -import numpy as np -import pytest - -from iris.exceptions import CannotAddError -from iris.fileformats._nc_load_rules.helpers import build_ancil_var - - -@pytest.fixture -def mock_engine(): - return mock.Mock( - cube=mock.Mock(), - cf_var=mock.Mock(dimensions=("foo", "bar")), - filename="DUMMY", - cube_parts=dict(ancillary_variables=[]), - ) - - -@pytest.fixture -def mock_cf_av_var(monkeypatch): - data = np.arange(6) - output = mock.Mock( - dimensions=("foo",), - scale_factor=1, - add_offset=0, - cf_name="wibble", - cf_data=mock.MagicMock(chunking=mock.Mock(return_value=None), spec=[]), - standard_name=None, - long_name="wibble", - units="m2", - shape=data.shape, - dtype=data.dtype, - __getitem__=lambda self, key: data[key], - ) - - # Create patch for deferred loading that prevents attempted - # file access. This assumes that output is defined in the test case. - def patched__getitem__(proxy_self, keys): - if proxy_self.variable_name == output.cf_name: - return output[keys] - raise RuntimeError() - - monkeypatch.setattr( - "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", - patched__getitem__, - ) - - return output - - -def test_not_added(monkeypatch, mock_engine, mock_cf_av_var): - # Confirm that the ancillary variable will be skipped if a CannotAddError - # is raised when attempting to add. - def mock_add_ancillary_variable(_, __): - raise CannotAddError("foo") - - with monkeypatch.context() as m: - m.setattr( - mock_engine.cube, - "add_ancillary_variable", - mock_add_ancillary_variable, - ) - with pytest.warns(match="ancillary variable not added to Cube: foo"): - build_ancil_var(mock_engine, mock_cf_av_var) - - assert mock_engine.cube_parts["ancillary_variables"] == [] diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py deleted file mode 100644 index 13622b72e2..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_auxiliary_coordinate.py +++ /dev/null @@ -1,330 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_auxilliary_coordinate`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np -import pytest - -from iris.coords import AuxCoord -from iris.exceptions import CannotAddError -from iris.fileformats._nc_load_rules.helpers import build_auxiliary_coordinate -from iris.fileformats.cf import CFVariable - - -class TestBoundsVertexDim(tests.IrisTest): - # Lookup for various tests (which change the dimension order). - dim_names_lens = { - "foo": 2, - "bar": 3, - "nv": 4, - # 'x' and 'y' used as aliases for 'foo' and 'bar' - "x": 2, - "y": 3, - } - - def setUp(self): - # Create coordinate cf variables and pyke engine. - dimension_names = ("foo", "bar") - points, cf_data = self._make_array_and_cf_data(dimension_names) - self.cf_coord_var = mock.Mock( - spec=CFVariable, - dimensions=dimension_names, - cf_name="wibble", - cf_data=cf_data, - standard_name=None, - long_name="wibble", - units="m", - shape=points.shape, - dtype=points.dtype, - __getitem__=lambda self, key: points[key], - ) - - expected_bounds, _ = self._make_array_and_cf_data( - dimension_names=("foo", "bar", "nv") - ) - self.expected_coord = AuxCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=expected_bounds, - ) - - self.engine = mock.Mock( - cube=mock.Mock(), - cf_var=mock.Mock(dimensions=("foo", "bar"), cf_data=cf_data), - filename="DUMMY", - cube_parts=dict(coordinates=[]), - ) - - # Patch the deferred loading that prevents attempted file access. - # This assumes that self.cf_bounds_var is defined in the test case. - def patched__getitem__(proxy_self, keys): - for var in (self.cf_coord_var, self.cf_bounds_var): - if proxy_self.variable_name == var.cf_name: - return var[keys] - raise RuntimeError() - - self.patch( - "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", - new=patched__getitem__, - ) - - # Patch the helper function that retrieves the bounds cf variable, - # and a False flag for climatological. - # This avoids the need for setting up further mocking of cf objects. - def _get_per_test_bounds_var(_coord_unused): - # Return the 'cf_bounds_var' created by the current test. - return (self.cf_bounds_var, False) - - self.patch( - "iris.fileformats._nc_load_rules.helpers.get_cf_bounds_var", - new=_get_per_test_bounds_var, - ) - - @classmethod - def _make_array_and_cf_data(cls, dimension_names): - shape = tuple(cls.dim_names_lens[name] for name in dimension_names) - cf_data = mock.MagicMock(_FillValue=None, spec=[]) - cf_data.chunking = mock.MagicMock(return_value=shape) - return np.zeros(shape), cf_data - - def _make_cf_bounds_var(self, dimension_names): - # Create the bounds cf variable. - bounds, cf_data = self._make_array_and_cf_data(dimension_names) - cf_bounds_var = mock.Mock( - spec=CFVariable, - dimensions=dimension_names, - cf_name="wibble_bnds", - cf_data=cf_data, - shape=bounds.shape, - dtype=bounds.dtype, - __getitem__=lambda self, key: bounds[key], - ) - - return bounds, cf_bounds_var - - def _check_case(self, dimension_names): - bounds, self.cf_bounds_var = self._make_cf_bounds_var( - dimension_names=dimension_names - ) - - # Asserts must lie within context manager because of deferred loading. - build_auxiliary_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_aux_coord.assert_called_with( - self.expected_coord, [0, 1] - ) - - # Test that engine.cube_parts container is correctly populated. - expected_list = [(self.expected_coord, self.cf_coord_var.cf_name)] - self.assertEqual(self.engine.cube_parts["coordinates"], expected_list) - - def test_fastest_varying_vertex_dim(self): - # The usual order. - self._check_case(dimension_names=("foo", "bar", "nv")) - - def test_slowest_varying_vertex_dim(self): - # Bounds in the first (slowest varying) dimension. - self._check_case(dimension_names=("nv", "foo", "bar")) - - def test_fastest_with_different_dim_names(self): - # Despite the dimension names ('x', and 'y') differing from the coord's - # which are 'foo' and 'bar' (as permitted by the cf spec), - # this should still work because the vertex dim is the fastest varying. - self._check_case(dimension_names=("x", "y", "nv")) - - -class TestDtype(tests.IrisTest): - def setUp(self): - # Create coordinate cf variables and pyke engine. - points = np.arange(6).reshape(2, 3) - cf_data = mock.MagicMock(_FillValue=None) - cf_data.chunking = mock.MagicMock(return_value=points.shape) - - self.cf_coord_var = mock.Mock( - spec=CFVariable, - dimensions=("foo", "bar"), - cf_name="wibble", - cf_data=cf_data, - standard_name=None, - long_name="wibble", - units="m", - shape=points.shape, - dtype=points.dtype, - __getitem__=lambda self, key: points[key], - ) - - self.engine = mock.Mock( - cube=mock.Mock(), - cf_var=mock.Mock(dimensions=("foo", "bar")), - filename="DUMMY", - cube_parts=dict(coordinates=[]), - ) - - def patched__getitem__(proxy_self, keys): - if proxy_self.variable_name == self.cf_coord_var.cf_name: - return self.cf_coord_var[keys] - raise RuntimeError() - - self.deferred_load_patch = mock.patch( - "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", - new=patched__getitem__, - ) - - def test_scale_factor_add_offset_int(self): - self.cf_coord_var.scale_factor = 3 - self.cf_coord_var.add_offset = 5 - - with self.deferred_load_patch: - build_auxiliary_coordinate(self.engine, self.cf_coord_var) - - coord, _ = self.engine.cube_parts["coordinates"][0] - self.assertEqual(coord.dtype.kind, "i") - - def test_scale_factor_float(self): - self.cf_coord_var.scale_factor = 3.0 - - with self.deferred_load_patch: - build_auxiliary_coordinate(self.engine, self.cf_coord_var) - - coord, _ = self.engine.cube_parts["coordinates"][0] - self.assertEqual(coord.dtype.kind, "f") - - def test_add_offset_float(self): - self.cf_coord_var.add_offset = 5.0 - - with self.deferred_load_patch: - build_auxiliary_coordinate(self.engine, self.cf_coord_var) - - coord, _ = self.engine.cube_parts["coordinates"][0] - self.assertEqual(coord.dtype.kind, "f") - - -class TestCoordConstruction(tests.IrisTest): - def setUp(self): - # Create dummy pyke engine. - self.engine = mock.Mock( - cube=mock.Mock(), - cf_var=mock.Mock(dimensions=("foo", "bar")), - filename="DUMMY", - cube_parts=dict(coordinates=[]), - ) - - points = np.arange(6) - self.cf_coord_var = mock.Mock( - dimensions=("foo",), - scale_factor=1, - add_offset=0, - cf_name="wibble", - cf_data=mock.MagicMock( - chunking=mock.Mock(return_value=None), spec=[] - ), - standard_name=None, - long_name="wibble", - units="days since 1970-01-01", - calendar=None, - shape=points.shape, - dtype=points.dtype, - __getitem__=lambda self, key: points[key], - ) - - bounds = np.arange(12).reshape(6, 2) - self.cf_bounds_var = mock.Mock( - dimensions=("x", "nv"), - scale_factor=1, - add_offset=0, - cf_name="wibble_bnds", - cf_data=mock.MagicMock(chunking=mock.Mock(return_value=None)), - shape=bounds.shape, - dtype=bounds.dtype, - __getitem__=lambda self, key: bounds[key], - ) - self.bounds = bounds - - # Create patch for deferred loading that prevents attempted - # file access. This assumes that self.cf_coord_var and - # self.cf_bounds_var are defined in the test case. - def patched__getitem__(proxy_self, keys): - for var in (self.cf_coord_var, self.cf_bounds_var): - if proxy_self.variable_name == var.cf_name: - return var[keys] - raise RuntimeError() - - self.patch( - "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", - new=patched__getitem__, - ) - - # Patch the helper function that retrieves the bounds cf variable. - # This avoids the need for setting up further mocking of cf objects. - self.use_climatology_bounds = False # Set this when you need to. - - def get_cf_bounds_var(coord_var): - return self.cf_bounds_var, self.use_climatology_bounds - - self.patch( - "iris.fileformats._nc_load_rules.helpers.get_cf_bounds_var", - new=get_cf_bounds_var, - ) - - # test_not_added() has been written in pytest-style, but the rest of - # the class is pending migration. Defining self.monkeypatch (not the - # typical practice in pure pytest) allows this transitional state. - self.monkeypatch = pytest.MonkeyPatch() - - def check_case_aux_coord_construction(self, climatology=False): - # Test a generic auxiliary coordinate, with or without - # a climatological coord. - self.use_climatology_bounds = climatology - - expected_coord = AuxCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=self.bounds, - climatological=climatology, - ) - - build_auxiliary_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_aux_coord.assert_called_with(expected_coord, [0]) - - def test_aux_coord_construction(self): - self.check_case_aux_coord_construction(climatology=False) - - def test_aux_coord_construction__climatology(self): - self.check_case_aux_coord_construction(climatology=True) - - def test_not_added(self): - # Confirm that the coord will be skipped if a CannotAddError is raised - # when attempting to add. - def mock_add_aux_coord(_, __): - raise CannotAddError("foo") - - with self.monkeypatch.context() as m: - m.setattr(self.engine.cube, "add_aux_coord", mock_add_aux_coord) - with pytest.warns(match="coordinate not added to Cube: foo"): - build_auxiliary_coordinate(self.engine, self.cf_coord_var) - - assert self.engine.cube_parts["coordinates"] == [] - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py deleted file mode 100644 index efbb0649c9..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cell_measure.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.build_cell_measure`. - -""" - -from unittest import mock - -import numpy as np -import pytest - -from iris.exceptions import CannotAddError -from iris.fileformats._nc_load_rules.helpers import build_cell_measures - - -@pytest.fixture -def mock_engine(): - return mock.Mock( - cube=mock.Mock(), - cf_var=mock.Mock(dimensions=("foo", "bar")), - filename="DUMMY", - cube_parts=dict(cell_measures=[]), - ) - - -@pytest.fixture -def mock_cf_cm_var(monkeypatch): - data = np.arange(6) - output = mock.Mock( - dimensions=("foo",), - scale_factor=1, - add_offset=0, - cf_name="wibble", - cf_data=mock.MagicMock(chunking=mock.Mock(return_value=None), spec=[]), - standard_name=None, - long_name="wibble", - units="m2", - shape=data.shape, - dtype=data.dtype, - __getitem__=lambda self, key: data[key], - cf_measure="area", - ) - - # Create patch for deferred loading that prevents attempted - # file access. This assumes that output is defined in the test case. - def patched__getitem__(proxy_self, keys): - if proxy_self.variable_name == output.cf_name: - return output[keys] - raise RuntimeError() - - monkeypatch.setattr( - "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", - patched__getitem__, - ) - - return output - - -def test_not_added(monkeypatch, mock_engine, mock_cf_cm_var): - # Confirm that the cell measure will be skipped if a CannotAddError is - # raised when attempting to add. - def mock_add_cell_measure(_, __): - raise CannotAddError("foo") - - with monkeypatch.context() as m: - m.setattr(mock_engine.cube, "add_cell_measure", mock_add_cell_measure) - with pytest.warns(match="cell measure not added to Cube: foo"): - build_cell_measures(mock_engine, mock_cf_cm_var) - - assert mock_engine.cube_parts["cell_measures"] == [] diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cube_metadata.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cube_metadata.py deleted file mode 100644 index a13fa6cca0..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_cube_metadata.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers\ -build_cube_metadata`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.cube import Cube -from iris.fileformats._nc_load_rules.helpers import build_cube_metadata - - -def _make_engine(global_attributes=None, standard_name=None, long_name=None): - if global_attributes is None: - global_attributes = {} - - cf_group = mock.Mock(global_attributes=global_attributes) - - cf_var = mock.MagicMock( - cf_name="wibble", - standard_name=standard_name, - long_name=long_name, - units="m", - dtype=np.float64, - cell_methods=None, - cf_group=cf_group, - ) - - engine = mock.Mock(cube=Cube([23]), cf_var=cf_var) - - return engine - - -class TestInvalidGlobalAttributes(tests.IrisTest): - def test_valid(self): - global_attributes = { - "Conventions": "CF-1.5", - "comment": "Mocked test object", - } - engine = _make_engine(global_attributes) - build_cube_metadata(engine) - expected = global_attributes - self.assertEqual(engine.cube.attributes, expected) - - def test_invalid(self): - global_attributes = { - "Conventions": "CF-1.5", - "comment": "Mocked test object", - "calendar": "standard", - } - engine = _make_engine(global_attributes) - with mock.patch("warnings.warn") as warn: - build_cube_metadata(engine) - # Check for a warning. - self.assertEqual(warn.call_count, 1) - self.assertIn( - "Skipping global attribute 'calendar'", warn.call_args[0][0] - ) - # Check resulting attributes. The invalid entry 'calendar' - # should be filtered out. - global_attributes.pop("calendar") - expected = global_attributes - self.assertEqual(engine.cube.attributes, expected) - - -class TestCubeName(tests.IrisTest): - def check_cube_names(self, inputs, expected): - # Inputs - attributes on the fake CF Variable. - standard_name, long_name = inputs - # Expected - The expected cube attributes. - exp_standard_name, exp_long_name = expected - - engine = _make_engine(standard_name=standard_name, long_name=long_name) - build_cube_metadata(engine) - - # Check the cube's standard name and long name are as expected. - self.assertEqual(engine.cube.standard_name, exp_standard_name) - self.assertEqual(engine.cube.long_name, exp_long_name) - - def test_standard_name_none_long_name_none(self): - inputs = (None, None) - expected = (None, None) - self.check_cube_names(inputs, expected) - - def test_standard_name_none_long_name_set(self): - inputs = (None, "ice_thickness_long_name") - expected = (None, "ice_thickness_long_name") - self.check_cube_names(inputs, expected) - - def test_standard_name_valid_long_name_none(self): - inputs = ("sea_ice_thickness", None) - expected = ("sea_ice_thickness", None) - self.check_cube_names(inputs, expected) - - def test_standard_name_valid_long_name_set(self): - inputs = ("sea_ice_thickness", "ice_thickness_long_name") - expected = ("sea_ice_thickness", "ice_thickness_long_name") - self.check_cube_names(inputs, expected) - - def test_standard_name_invalid_long_name_none(self): - inputs = ("not_a_standard_name", None) - expected = ( - None, - "not_a_standard_name", - ) - self.check_cube_names(inputs, expected) - - def test_standard_name_invalid_long_name_set(self): - inputs = ("not_a_standard_name", "ice_thickness_long_name") - expected = (None, "ice_thickness_long_name") - self.check_cube_names(inputs, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py deleted file mode 100644 index bc13975441..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_dimension_coordinate.py +++ /dev/null @@ -1,567 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_dimension_coordinate`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock -import warnings - -import numpy as np -import pytest - -from iris.coords import AuxCoord, DimCoord -from iris.exceptions import CannotAddError -from iris.fileformats._nc_load_rules.helpers import build_dimension_coordinate - - -class RulesTestMixin: - def setUp(self): - # Create dummy pyke engine. - self.engine = mock.Mock( - cube=mock.Mock(), - cf_var=mock.Mock(dimensions=("foo", "bar")), - filename="DUMMY", - cube_parts=dict(coordinates=[]), - ) - - # Create patch for deferred loading that prevents attempted - # file access. This assumes that self.cf_coord_var and - # self.cf_bounds_var are defined in the test case. - def patched__getitem__(proxy_self, keys): - for var in (self.cf_coord_var, self.cf_bounds_var): - if proxy_self.variable_name == var.cf_name: - return var[keys] - raise RuntimeError() - - self.deferred_load_patch = mock.patch( - "iris.fileformats.netcdf.NetCDFDataProxy.__getitem__", - new=patched__getitem__, - ) - - # Patch the helper function that retrieves the bounds cf variable. - # This avoids the need for setting up further mocking of cf objects. - self.use_climatology_bounds = False # Set this when you need to. - - def get_cf_bounds_var(coord_var): - return self.cf_bounds_var, self.use_climatology_bounds - - self.get_cf_bounds_var_patch = mock.patch( - "iris.fileformats._nc_load_rules.helpers.get_cf_bounds_var", - new=get_cf_bounds_var, - ) - - -class TestCoordConstruction(tests.IrisTest, RulesTestMixin): - def setUp(self): - # Call parent setUp explicitly, because of how unittests work. - RulesTestMixin.setUp(self) - - bounds = np.arange(12).reshape(6, 2) - self.cf_bounds_var = mock.Mock( - dimensions=("x", "nv"), - cf_name="wibble_bnds", - shape=bounds.shape, - __getitem__=lambda self, key: bounds[key], - ) - self.bounds = bounds - - # test_dimcoord_not_added() and test_auxcoord_not_added have been - # written in pytest-style, but the rest of the class is pending - # migration. Defining self.monkeypatch (not the - # typical practice in pure pytest) allows this transitional state. - self.monkeypatch = pytest.MonkeyPatch() - - def _set_cf_coord_var(self, points): - self.cf_coord_var = mock.Mock( - dimensions=("foo",), - cf_name="wibble", - cf_data=mock.Mock(spec=[]), - standard_name=None, - long_name="wibble", - units="days since 1970-01-01", - calendar=None, - shape=points.shape, - dtype=points.dtype, - __getitem__=lambda self, key: points[key], - ) - - def check_case_dim_coord_construction(self, climatology=False): - # Test a generic dimension coordinate, with or without - # a climatological coord. - self.use_climatology_bounds = climatology - self._set_cf_coord_var(np.arange(6)) - - expected_coord = DimCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=self.bounds, - climatological=climatology, - ) - - # Asserts must lie within context manager because of deferred loading. - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_dim_coord.assert_called_with( - expected_coord, [0] - ) - - def test_dim_coord_construction(self): - self.check_case_dim_coord_construction(climatology=False) - - def test_dim_coord_construction__climatology(self): - self.check_case_dim_coord_construction(climatology=True) - - def test_dim_coord_construction_masked_array(self): - self._set_cf_coord_var( - np.ma.array( - np.arange(6), - mask=[True, False, False, False, False, False], - fill_value=-999, - ) - ) - - expected_coord = DimCoord( - np.array([-999, 1, 2, 3, 4, 5]), - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=self.bounds, - ) - - with warnings.catch_warnings(record=True) as w: - # Asserts must lie within context manager because of deferred - # loading. - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_dim_coord.assert_called_with( - expected_coord, [0] - ) - - # Assert warning is raised - assert len(w) == 1 - assert "Gracefully filling" in w[0].message.args[0] - - def test_dim_coord_construction_masked_array_mask_does_nothing(self): - self._set_cf_coord_var( - np.ma.array( - np.arange(6), - mask=False, - ) - ) - - expected_coord = DimCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=self.bounds, - ) - - with warnings.catch_warnings(record=True) as w: - # Asserts must lie within context manager because of deferred - # loading. - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_dim_coord.assert_called_with( - expected_coord, [0] - ) - - # Assert no warning is raised - assert len(w) == 0 - - def test_dim_coord_construction_masked_bounds_mask_does_nothing(self): - self.bounds = np.ma.array(np.arange(12).reshape(6, 2), mask=False) - self._set_cf_coord_var(np.arange(6)) - - expected_coord = DimCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=self.bounds, - ) - - with warnings.catch_warnings(record=True) as w: - # Asserts must lie within context manager because of deferred - # loading. - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_dim_coord.assert_called_with( - expected_coord, [0] - ) - - # Assert no warning is raised - assert len(w) == 0 - - def test_aux_coord_construction(self): - # Use non monotonically increasing coordinates to force aux coord - # construction. - self._set_cf_coord_var(np.array([1, 3, 2, 4, 6, 5])) - - expected_coord = AuxCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=self.bounds, - ) - - warning_patch = mock.patch("warnings.warn") - - # Asserts must lie within context manager because of deferred loading. - with ( - warning_patch - ), self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_aux_coord.assert_called_with( - expected_coord, [0] - ) - self.assertIn( - "creating 'wibble' auxiliary coordinate instead", - warnings.warn.call_args[0][0], - ) - - def test_dimcoord_not_added(self): - # Confirm that the coord will be skipped if a CannotAddError is raised - # when attempting to add. - def mock_add_dim_coord(_, __): - raise CannotAddError("foo") - - with self.monkeypatch.context() as m: - m.setattr(self.engine.cube, "add_dim_coord", mock_add_dim_coord) - - self._set_cf_coord_var(np.arange(6)) - - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - with pytest.warns(match="coordinate not added to Cube: foo"): - build_dimension_coordinate(self.engine, self.cf_coord_var) - - assert self.engine.cube_parts["coordinates"] == [] - - def test_auxcoord_not_added(self): - # Confirm that a gracefully-created auxiliary coord will also be - # skipped if a CannotAddError is raised when attempting to add. - def mock_add_aux_coord(_, __): - raise CannotAddError("foo") - - with self.monkeypatch.context() as m: - m.setattr(self.engine.cube, "add_aux_coord", mock_add_aux_coord) - - self._set_cf_coord_var(np.array([1, 3, 2, 4, 6, 5])) - - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - with pytest.warns(match="coordinate not added to Cube: foo"): - build_dimension_coordinate(self.engine, self.cf_coord_var) - - assert self.engine.cube_parts["coordinates"] == [] - - -class TestBoundsVertexDim(tests.IrisTest, RulesTestMixin): - def setUp(self): - # Call parent setUp explicitly, because of how unittests work. - RulesTestMixin.setUp(self) - # Create test coordinate cf variable. - points = np.arange(6) - self.cf_coord_var = mock.Mock( - dimensions=("foo",), - cf_name="wibble", - standard_name=None, - long_name="wibble", - cf_data=mock.Mock(spec=[]), - units="m", - shape=points.shape, - dtype=points.dtype, - __getitem__=lambda self, key: points[key], - ) - - def test_slowest_varying_vertex_dim(self): - # Create the bounds cf variable. - bounds = np.arange(12).reshape(2, 6) - self.cf_bounds_var = mock.Mock( - dimensions=("nv", "foo"), - cf_name="wibble_bnds", - shape=bounds.shape, - __getitem__=lambda self, key: bounds[key], - ) - - # Expected bounds on the resulting coordinate should be rolled so that - # the vertex dimension is at the end. - expected_bounds = bounds.transpose() - expected_coord = DimCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=expected_bounds, - ) - - # Asserts must lie within context manager because of deferred loading. - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_dim_coord.assert_called_with( - expected_coord, [0] - ) - - # Test that engine.cube_parts container is correctly populated. - expected_list = [(expected_coord, self.cf_coord_var.cf_name)] - self.assertEqual( - self.engine.cube_parts["coordinates"], expected_list - ) - - def test_fastest_varying_vertex_dim(self): - bounds = np.arange(12).reshape(6, 2) - self.cf_bounds_var = mock.Mock( - dimensions=("foo", "nv"), - cf_name="wibble_bnds", - shape=bounds.shape, - __getitem__=lambda self, key: bounds[key], - ) - - expected_coord = DimCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=bounds, - ) - - # Asserts must lie within context manager because of deferred loading. - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_dim_coord.assert_called_with( - expected_coord, [0] - ) - - # Test that engine.cube_parts container is correctly populated. - expected_list = [(expected_coord, self.cf_coord_var.cf_name)] - self.assertEqual( - self.engine.cube_parts["coordinates"], expected_list - ) - - def test_fastest_with_different_dim_names(self): - # Despite the dimension names 'x' differing from the coord's - # which is 'foo' (as permitted by the cf spec), - # this should still work because the vertex dim is the fastest varying. - bounds = np.arange(12).reshape(6, 2) - self.cf_bounds_var = mock.Mock( - dimensions=("x", "nv"), - cf_name="wibble_bnds", - shape=bounds.shape, - __getitem__=lambda self, key: bounds[key], - ) - - expected_coord = DimCoord( - self.cf_coord_var[:], - long_name=self.cf_coord_var.long_name, - var_name=self.cf_coord_var.cf_name, - units=self.cf_coord_var.units, - bounds=bounds, - ) - - # Asserts must lie within context manager because of deferred loading. - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate(self.engine, self.cf_coord_var) - - # Test that expected coord is built and added to cube. - self.engine.cube.add_dim_coord.assert_called_with( - expected_coord, [0] - ) - - # Test that engine.cube_parts container is correctly populated. - expected_list = [(expected_coord, self.cf_coord_var.cf_name)] - self.assertEqual( - self.engine.cube_parts["coordinates"], expected_list - ) - - -class TestCircular(tests.IrisTest, RulesTestMixin): - # Test the rules logic for marking a coordinate "circular". - def setUp(self): - # Call parent setUp explicitly, because of how unittests work. - RulesTestMixin.setUp(self) - self.cf_bounds_var = None - - def _make_vars(self, points, bounds=None, units="degrees"): - points = np.array(points) - self.cf_coord_var = mock.MagicMock( - dimensions=("foo",), - cf_name="wibble", - standard_name=None, - long_name="wibble", - cf_data=mock.Mock(spec=[]), - units=units, - shape=points.shape, - dtype=points.dtype, - __getitem__=lambda self, key: points[key], - ) - if bounds: - bounds = np.array(bounds).reshape(self.cf_coord_var.shape + (2,)) - self.cf_bounds_var = mock.Mock( - dimensions=("x", "nv"), - cf_name="wibble_bnds", - shape=bounds.shape, - __getitem__=lambda self, key: bounds[key], - ) - - def _check_circular(self, circular, *args, **kwargs): - if "coord_name" in kwargs: - coord_name = kwargs.pop("coord_name") - else: - coord_name = "longitude" - self._make_vars(*args, **kwargs) - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate( - self.engine, self.cf_coord_var, coord_name=coord_name - ) - self.assertEqual(self.engine.cube.add_dim_coord.call_count, 1) - coord, dims = self.engine.cube.add_dim_coord.call_args[0] - self.assertEqual(coord.circular, circular) - - def check_circular(self, *args, **kwargs): - self._check_circular(True, *args, **kwargs) - - def check_noncircular(self, *args, **kwargs): - self._check_circular(False, *args, **kwargs) - - def test_single_zero_noncircular(self): - self.check_noncircular([0.0]) - - def test_single_lt_modulus_noncircular(self): - self.check_noncircular([-1.0]) - - def test_single_eq_modulus_circular(self): - self.check_circular([360.0]) - - def test_single_gt_modulus_circular(self): - self.check_circular([361.0]) - - def test_single_bounded_noncircular(self): - self.check_noncircular([180.0], bounds=[90.0, 240.0]) - - def test_single_bounded_circular(self): - self.check_circular([180.0], bounds=[90.0, 450.0]) - - def test_multiple_unbounded_circular(self): - self.check_circular([0.0, 90.0, 180.0, 270.0]) - - def test_non_angle_noncircular(self): - points = [0.0, 90.0, 180.0, 270.0] - self.check_noncircular(points, units="m") - - def test_non_longitude_noncircular(self): - points = [0.0, 90.0, 180.0, 270.0] - self.check_noncircular(points, coord_name="depth") - - def test_multiple_unbounded_irregular_noncircular(self): - self.check_noncircular([0.0, 90.0, 189.999, 270.0]) - - def test_multiple_unbounded_offset_circular(self): - self.check_circular([45.0, 135.0, 225.0, 315.0]) - - def test_multiple_unbounded_shortrange_circular(self): - self.check_circular([0.0, 90.0, 180.0, 269.9999]) - - def test_multiple_bounded_circular(self): - self.check_circular( - [0.0, 120.3, 240.0], - bounds=[[-45.0, 50.0], [100.0, 175.0], [200.0, 315.0]], - ) - - def test_multiple_bounded_noncircular(self): - self.check_noncircular( - [0.0, 120.3, 240.0], - bounds=[[-45.0, 50.0], [100.0, 175.0], [200.0, 355.0]], - ) - - -class TestCircularScalar(tests.IrisTest, RulesTestMixin): - def setUp(self): - RulesTestMixin.setUp(self) - - def _make_vars(self, bounds): - # Create cf vars for the coordinate and its bounds. - # Note that for a scalar the shape of the array from - # the cf var is (), rather than (1,). - points = np.array([0.0]) - self.cf_coord_var = mock.Mock( - dimensions=(), - cf_name="wibble", - standard_name=None, - long_name="wibble", - units="degrees", - cf_data=mock.Mock(spec=[]), - shape=(), - dtype=points.dtype, - __getitem__=lambda self, key: points[key], - ) - - bounds = np.array(bounds) - self.cf_bounds_var = mock.Mock( - dimensions=("bnds"), - cf_name="wibble_bnds", - shape=bounds.shape, - __getitem__=lambda self, key: bounds[key], - ) - - def _assert_circular(self, value): - with self.deferred_load_patch, self.get_cf_bounds_var_patch: - build_dimension_coordinate( - self.engine, self.cf_coord_var, coord_name="longitude" - ) - self.assertEqual(self.engine.cube.add_aux_coord.call_count, 1) - coord, dims = self.engine.cube.add_aux_coord.call_args[0] - self.assertEqual(coord.circular, value) - - def test_two_bounds_noncircular(self): - self._make_vars([0.0, 180.0]) - self._assert_circular(False) - - def test_two_bounds_circular(self): - self._make_vars([0.0, 360.0]) - self._assert_circular(True) - - def test_two_bounds_circular_decreasing(self): - self._make_vars([360.0, 0.0]) - self._assert_circular(True) - - def test_two_bounds_circular_alt(self): - self._make_vars([-180.0, 180.0]) - self._assert_circular(True) - - def test_two_bounds_circular_alt_decreasing(self): - self._make_vars([180.0, -180.0]) - self._assert_circular(True) - - def test_four_bounds(self): - self._make_vars([0.0, 10.0, 20.0, 30.0]) - self._assert_circular(False) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_geostationary_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_geostationary_coordinate_system.py deleted file mode 100644 index 28b3d8ab9a..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_geostationary_coordinate_system.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_geostationary_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import Geostationary -from iris.fileformats._nc_load_rules.helpers import ( - build_geostationary_coordinate_system, -) - - -class TestBuildGeostationaryCoordinateSystem(tests.IrisTest): - def _test( - self, inverse_flattening=False, replace_props=None, remove_props=None - ): - """ - Generic test that can check vertical perspective validity with or - without inverse flattening. - """ - # Make a dictionary of the non-ellipsoid properties to be added to both a test - # coord-system, and a test grid-mapping cf_var. - non_ellipsoid_kwargs = { - "latitude_of_projection_origin": 0.0, - "longitude_of_projection_origin": 2.0, - "perspective_point_height": 2000000.0, - "sweep_angle_axis": "x", - "false_easting": 100.0, - "false_northing": 200.0, - } - - # Make specified adjustments to the non-ellipsoid properties. - if remove_props: - for key in remove_props: - non_ellipsoid_kwargs.pop(key, None) - if replace_props: - for key, value in replace_props.items(): - non_ellipsoid_kwargs[key] = value - - # Make a dictionary of ellipsoid properties, to be added to both a test - # ellipsoid and the grid-mapping cf_var. - ellipsoid_kwargs = {"semi_major_axis": 6377563.396} - if inverse_flattening: - ellipsoid_kwargs["inverse_flattening"] = 299.3249646 - else: - ellipsoid_kwargs["semi_minor_axis"] = 6356256.909 - - cf_grid_var_kwargs = non_ellipsoid_kwargs.copy() - cf_grid_var_kwargs.update(ellipsoid_kwargs) - cf_grid_var = mock.Mock(spec=[], **cf_grid_var_kwargs) - cs = build_geostationary_coordinate_system(None, cf_grid_var) - ellipsoid = iris.coord_systems.GeogCS(**ellipsoid_kwargs) - expected = Geostationary(ellipsoid=ellipsoid, **non_ellipsoid_kwargs) - self.assertEqual(cs, expected) - - def test_valid(self): - self._test(inverse_flattening=False) - - def test_inverse_flattening(self): - self._test(inverse_flattening=True) - - def test_false_offsets_missing(self): - self._test(remove_props=["false_easting", "false_northing"]) - - def test_false_offsets_none(self): - self._test( - replace_props={"false_easting": None, "false_northing": None} - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_azimuthal_equal_area_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_azimuthal_equal_area_coordinate_system.py deleted file mode 100644 index 05185a4cf5..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_azimuthal_equal_area_coordinate_system.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_lambert_azimuthal_equal_area_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import LambertAzimuthalEqualArea -from iris.fileformats._nc_load_rules.helpers import ( - build_lambert_azimuthal_equal_area_coordinate_system, -) - - -class TestBuildLambertAzimuthalEqualAreaCoordinateSystem(tests.IrisTest): - def _test(self, inverse_flattening=False, no_optionals=False): - if no_optionals: - # Most properties are optional for this system. - gridvar_props = {} - # Setup all the expected default values - test_lat = 0 - test_lon = 0 - test_easting = 0 - test_northing = 0 - else: - # Choose test values and setup corresponding named properties. - test_lat = -35 - test_lon = 175 - test_easting = -100 - test_northing = 200 - gridvar_props = dict( - latitude_of_projection_origin=test_lat, - longitude_of_projection_origin=test_lon, - false_easting=test_easting, - false_northing=test_northing, - ) - - # Add ellipsoid args. - gridvar_props["semi_major_axis"] = 6377563.396 - if inverse_flattening: - gridvar_props["inverse_flattening"] = 299.3249646 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, inverse_flattening=299.3249646 - ) - else: - gridvar_props["semi_minor_axis"] = 6356256.909 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, 6356256.909 - ) - - cf_grid_var = mock.Mock(spec=[], **gridvar_props) - - cs = build_lambert_azimuthal_equal_area_coordinate_system( - None, cf_grid_var - ) - - expected = LambertAzimuthalEqualArea( - latitude_of_projection_origin=test_lat, - longitude_of_projection_origin=test_lon, - false_easting=test_easting, - false_northing=test_northing, - ellipsoid=expected_ellipsoid, - ) - - self.assertEqual(cs, expected) - - def test_basic(self): - self._test() - - def test_inverse_flattening(self): - # Check when inverse_flattening is provided instead of semi_minor_axis. - self._test(inverse_flattening=True) - - def test_no_optionals(self): - # Check defaults, when all optional attributes are absent. - self._test(no_optionals=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_conformal_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_conformal_coordinate_system.py deleted file mode 100644 index 22bb7149b1..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_lambert_conformal_coordinate_system.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_lambert_conformal_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import LambertConformal -from iris.fileformats._nc_load_rules.helpers import ( - build_lambert_conformal_coordinate_system, -) - - -class TestBuildLambertConformalCoordinateSystem(tests.IrisTest): - def _test(self, inverse_flattening=False, no_optionals=False): - if no_optionals: - # Most properties are optional in this case. - gridvar_props = {} - # Setup all the expected default values - test_lat = 39 - test_lon = -96 - test_easting = 0 - test_northing = 0 - test_parallels = (33, 45) - else: - # Choose test values and setup corresponding named properties. - test_lat = -35 - test_lon = 175 - test_easting = -100 - test_northing = 200 - test_parallels = (-27, 3) - gridvar_props = dict( - latitude_of_projection_origin=test_lat, - longitude_of_central_meridian=test_lon, - false_easting=test_easting, - false_northing=test_northing, - standard_parallel=test_parallels, - ) - - # Add ellipsoid args. - gridvar_props["semi_major_axis"] = 6377563.396 - if inverse_flattening: - gridvar_props["inverse_flattening"] = 299.3249646 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, inverse_flattening=299.3249646 - ) - else: - gridvar_props["semi_minor_axis"] = 6356256.909 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, 6356256.909 - ) - - cf_grid_var = mock.Mock(spec=[], **gridvar_props) - - cs = build_lambert_conformal_coordinate_system(None, cf_grid_var) - - expected = LambertConformal( - central_lat=test_lat, - central_lon=test_lon, - false_easting=test_easting, - false_northing=test_northing, - secant_latitudes=test_parallels, - ellipsoid=expected_ellipsoid, - ) - - self.assertEqual(cs, expected) - - def test_basic(self): - self._test() - - def test_inverse_flattening(self): - # Check when inverse_flattening is provided instead of semi_minor_axis. - self._test(inverse_flattening=True) - - def test_no_optionals(self): - # Check defaults, when all optional attributes are absent. - self._test(no_optionals=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py deleted file mode 100644 index ab61d3b1b2..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_mercator_coordinate_system.py +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_mercator_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import Mercator -from iris.fileformats._nc_load_rules.helpers import ( - build_mercator_coordinate_system, -) - - -class TestBuildMercatorCoordinateSystem(tests.IrisTest): - def test_valid(self): - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=-90, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - standard_parallel=10, - ) - - cs = build_mercator_coordinate_system(None, cf_grid_var) - - expected = Mercator( - longitude_of_projection_origin=( - cf_grid_var.longitude_of_projection_origin - ), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - standard_parallel=(cf_grid_var.standard_parallel), - ) - self.assertEqual(cs, expected) - - def test_inverse_flattening(self): - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=-90, - semi_major_axis=6377563.396, - inverse_flattening=299.3249646, - standard_parallel=10, - ) - - cs = build_mercator_coordinate_system(None, cf_grid_var) - - expected = Mercator( - longitude_of_projection_origin=( - cf_grid_var.longitude_of_projection_origin - ), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, - inverse_flattening=cf_grid_var.inverse_flattening, - ), - standard_parallel=(cf_grid_var.standard_parallel), - ) - self.assertEqual(cs, expected) - - def test_longitude_missing(self): - cf_grid_var = mock.Mock( - spec=[], - semi_major_axis=6377563.396, - inverse_flattening=299.3249646, - standard_parallel=10, - ) - - cs = build_mercator_coordinate_system(None, cf_grid_var) - - expected = Mercator( - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, - inverse_flattening=cf_grid_var.inverse_flattening, - ), - standard_parallel=(cf_grid_var.standard_parallel), - ) - self.assertEqual(cs, expected) - - def test_standard_parallel_missing(self): - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=-90, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - - cs = build_mercator_coordinate_system(None, cf_grid_var) - - expected = Mercator( - longitude_of_projection_origin=( - cf_grid_var.longitude_of_projection_origin - ), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - ) - self.assertEqual(cs, expected) - - def test_scale_factor_at_projection_origin(self): - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=-90, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - scale_factor_at_projection_origin=1.3, - ) - - cs = build_mercator_coordinate_system(None, cf_grid_var) - - expected = Mercator( - longitude_of_projection_origin=( - cf_grid_var.longitude_of_projection_origin - ), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - scale_factor_at_projection_origin=( - cf_grid_var.scale_factor_at_projection_origin - ), - ) - self.assertEqual(cs, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py deleted file mode 100755 index 09cfde9d5b..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_polar_stereographic_coordinate_system.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_polar_stereographic_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import PolarStereographic -from iris.fileformats._nc_load_rules.helpers import ( - build_polar_stereographic_coordinate_system, -) - - -class TestBuildPolarStereographicCoordinateSystem(tests.IrisTest): - def test_valid_north(self): - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - - cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) - - expected = PolarStereographic( - central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), - central_lat=(cf_grid_var.latitude_of_projection_origin), - scale_factor_at_projection_origin=( - cf_grid_var.scale_factor_at_projection_origin - ), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - ) - self.assertEqual(cs, expected) - - def test_valid_south(self): - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=-90, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - - cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) - - expected = PolarStereographic( - central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), - central_lat=(cf_grid_var.latitude_of_projection_origin), - scale_factor_at_projection_origin=( - cf_grid_var.scale_factor_at_projection_origin - ), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - ) - self.assertEqual(cs, expected) - - def test_valid_with_standard_parallel(self): - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - standard_parallel=30, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - - cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) - - expected = PolarStereographic( - central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), - central_lat=(cf_grid_var.latitude_of_projection_origin), - true_scale_lat=(cf_grid_var.standard_parallel), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - ) - self.assertEqual(cs, expected) - - def test_valid_with_false_easting_northing(self): - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - scale_factor_at_projection_origin=1, - false_easting=30, - false_northing=40, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - - cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) - - expected = PolarStereographic( - central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), - central_lat=(cf_grid_var.latitude_of_projection_origin), - scale_factor_at_projection_origin=( - cf_grid_var.scale_factor_at_projection_origin - ), - false_easting=(cf_grid_var.false_easting), - false_northing=(cf_grid_var.false_northing), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - ) - self.assertEqual(cs, expected) - - def test_valid_nonzero_veritcal_lon(self): - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=30, - latitude_of_projection_origin=90, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - - cs = build_polar_stereographic_coordinate_system(None, cf_grid_var) - - expected = PolarStereographic( - central_lon=(cf_grid_var.straight_vertical_longitude_from_pole), - central_lat=(cf_grid_var.latitude_of_projection_origin), - scale_factor_at_projection_origin=( - cf_grid_var.scale_factor_at_projection_origin - ), - ellipsoid=iris.coord_systems.GeogCS( - cf_grid_var.semi_major_axis, cf_grid_var.semi_minor_axis - ), - ) - self.assertEqual(cs, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_stereographic_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_stereographic_coordinate_system.py deleted file mode 100644 index 3796aeebab..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_stereographic_coordinate_system.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_sterographic_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import Stereographic -from iris.fileformats._nc_load_rules.helpers import ( - build_stereographic_coordinate_system, -) - - -class TestBuildStereographicCoordinateSystem(tests.IrisTest): - def _test(self, inverse_flattening=False, no_offsets=False): - test_easting = -100 - test_northing = 200 - test_scale_factor = 1.2 - gridvar_props = dict( - latitude_of_projection_origin=0, - longitude_of_projection_origin=0, - false_easting=test_easting, - false_northing=test_northing, - scale_factor_at_projection_origin=test_scale_factor, - semi_major_axis=6377563.396, - ) - - if inverse_flattening: - gridvar_props["inverse_flattening"] = 299.3249646 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, inverse_flattening=299.3249646 - ) - else: - gridvar_props["semi_minor_axis"] = 6356256.909 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, 6356256.909 - ) - - if no_offsets: - del gridvar_props["false_easting"] - del gridvar_props["false_northing"] - test_easting = 0 - test_northing = 0 - - cf_grid_var = mock.Mock(spec=[], **gridvar_props) - - cs = build_stereographic_coordinate_system(None, cf_grid_var) - - expected = Stereographic( - central_lat=cf_grid_var.latitude_of_projection_origin, - central_lon=cf_grid_var.longitude_of_projection_origin, - false_easting=test_easting, - false_northing=test_northing, - scale_factor_at_projection_origin=test_scale_factor, - ellipsoid=expected_ellipsoid, - ) - - self.assertEqual(cs, expected) - - def test_basic(self): - self._test() - - def test_inverse_flattening(self): - # Check when inverse_flattening is provided instead of semi_minor_axis. - self._test(inverse_flattening=True) - - def test_no_offsets(self): - # Check when false_easting/northing attributes are absent. - self._test(no_offsets=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_transverse_mercator_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_transverse_mercator_coordinate_system.py deleted file mode 100644 index 0096c5df4b..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_transverse_mercator_coordinate_system.py +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_transverse_mercator_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import TransverseMercator -from iris.fileformats._nc_load_rules.helpers import ( - build_transverse_mercator_coordinate_system, -) - - -class TestBuildTransverseMercatorCoordinateSystem(tests.IrisTest): - def _test(self, inverse_flattening=False, no_options=False): - test_easting = -100 - test_northing = 200 - test_scale_factor = 1.234 - gridvar_props = dict( - latitude_of_projection_origin=35.3, - longitude_of_central_meridian=-75, - false_easting=test_easting, - false_northing=test_northing, - scale_factor_at_central_meridian=test_scale_factor, - semi_major_axis=6377563.396, - ) - - if inverse_flattening: - gridvar_props["inverse_flattening"] = 299.3249646 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, inverse_flattening=299.3249646 - ) - else: - gridvar_props["semi_minor_axis"] = 6356256.909 - expected_ellipsoid = iris.coord_systems.GeogCS( - 6377563.396, 6356256.909 - ) - - if no_options: - del gridvar_props["false_easting"] - del gridvar_props["false_northing"] - del gridvar_props["scale_factor_at_central_meridian"] - test_easting = 0 - test_northing = 0 - test_scale_factor = 1.0 - - cf_grid_var = mock.Mock(spec=[], **gridvar_props) - - cs = build_transverse_mercator_coordinate_system(None, cf_grid_var) - - expected = TransverseMercator( - latitude_of_projection_origin=( - cf_grid_var.latitude_of_projection_origin - ), - longitude_of_central_meridian=( - cf_grid_var.longitude_of_central_meridian - ), - false_easting=test_easting, - false_northing=test_northing, - scale_factor_at_central_meridian=test_scale_factor, - ellipsoid=expected_ellipsoid, - ) - - self.assertEqual(cs, expected) - - def test_basic(self): - self._test() - - def test_inverse_flattening(self): - self._test(inverse_flattening=True) - - def test_missing_optionals(self): - self._test(no_options=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_verticalp_coordinate_system.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_verticalp_coordinate_system.py deleted file mode 100644 index f34992c2be..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_build_verticalp_coordinate_system.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -build_vertical_perspective_coordinate_system`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.coord_systems import VerticalPerspective -from iris.fileformats._nc_load_rules.helpers import ( - build_vertical_perspective_coordinate_system, -) - - -class TestBuildVerticalPerspectiveCoordinateSystem(tests.IrisTest): - def _test(self, inverse_flattening=False, no_offsets=False): - """ - Generic test that can check vertical perspective validity with or - without inverse flattening, and false_east/northing-s. - """ - test_easting = 100.0 - test_northing = 200.0 - cf_grid_var_kwargs = { - "spec": [], - "latitude_of_projection_origin": 1.0, - "longitude_of_projection_origin": 2.0, - "perspective_point_height": 2000000.0, - "false_easting": test_easting, - "false_northing": test_northing, - "semi_major_axis": 6377563.396, - } - - ellipsoid_kwargs = {"semi_major_axis": 6377563.396} - if inverse_flattening: - ellipsoid_kwargs["inverse_flattening"] = 299.3249646 - else: - ellipsoid_kwargs["semi_minor_axis"] = 6356256.909 - cf_grid_var_kwargs.update(ellipsoid_kwargs) - - if no_offsets: - del cf_grid_var_kwargs["false_easting"] - del cf_grid_var_kwargs["false_northing"] - test_easting = 0 - test_northing = 0 - - cf_grid_var = mock.Mock(**cf_grid_var_kwargs) - ellipsoid = iris.coord_systems.GeogCS(**ellipsoid_kwargs) - - cs = build_vertical_perspective_coordinate_system(None, cf_grid_var) - expected = VerticalPerspective( - latitude_of_projection_origin=cf_grid_var.latitude_of_projection_origin, - longitude_of_projection_origin=cf_grid_var.longitude_of_projection_origin, - perspective_point_height=cf_grid_var.perspective_point_height, - false_easting=test_easting, - false_northing=test_northing, - ellipsoid=ellipsoid, - ) - - self.assertEqual(cs, expected) - - def test_valid(self): - self._test(inverse_flattening=False) - - def test_inverse_flattening(self): - # Check when inverse_flattening is provided instead of semi_minor_axis. - self._test(inverse_flattening=True) - - def test_no_offsets(self): - # Check when false_easting/northing attributes are absent. - self._test(no_offsets=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_attr_units.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_attr_units.py deleted file mode 100644 index a159ef81a8..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_attr_units.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -get_attr_units`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.fileformats._nc_load_rules.helpers import get_attr_units - - -class TestGetAttrUnits(tests.IrisTest): - @staticmethod - def _make_cf_var(global_attributes=None): - if global_attributes is None: - global_attributes = {} - - cf_group = mock.Mock(global_attributes=global_attributes) - - cf_var = mock.MagicMock( - cf_name="sound_frequency", - cf_data=mock.Mock(spec=[]), - standard_name=None, - long_name=None, - units="\u266b", - dtype=np.float64, - cell_methods=None, - cf_group=cf_group, - ) - return cf_var - - def test_unicode_character(self): - attributes = {} - expected_attributes = {"invalid_units": "\u266b"} - cf_var = self._make_cf_var() - attr_units = get_attr_units(cf_var, attributes) - self.assertEqual(attr_units, "?") - self.assertEqual(attributes, expected_attributes) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_cf_bounds_var.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_cf_bounds_var.py deleted file mode 100644 index ff9c51f40b..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_cf_bounds_var.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -get_cf_bounds_var`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats._nc_load_rules.helpers import ( - CF_ATTR_BOUNDS, - CF_ATTR_CLIMATOLOGY, - get_cf_bounds_var, -) - - -class TestGetCFBoundsVar(tests.IrisTest): - # Tests to check that get_cf_bounds_var will return the bounds_var and - # the correct climatological flag. - def _generic_test(self, test_climatological_bounds=False): - cf_coord_var = mock.MagicMock() - - cf_group_dict = {"TEST": mock.sentinel.bounds_var} - if test_climatological_bounds: - cf_coord_var.cf_group.climatology = cf_group_dict - test_attr = CF_ATTR_CLIMATOLOGY - else: - cf_coord_var.cf_group.bounds = cf_group_dict - test_attr = CF_ATTR_BOUNDS - - for attr in (CF_ATTR_BOUNDS, CF_ATTR_CLIMATOLOGY): - attr_val = "TEST" if attr == test_attr else None - setattr(cf_coord_var, attr, attr_val) - - bounds_var, climatological = get_cf_bounds_var(cf_coord_var) - self.assertIs(bounds_var, mock.sentinel.bounds_var) - self.assertEqual(climatological, test_climatological_bounds) - - def test_bounds_normal(self): - self._generic_test(test_climatological_bounds=False) - - def test_bounds_climatological(self): - self._generic_test(test_climatological_bounds=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_names.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_names.py deleted file mode 100644 index 3c7c496b54..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_get_names.py +++ /dev/null @@ -1,292 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -get_names`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.fileformats._nc_load_rules.helpers import get_names - - -class TestGetNames(tests.IrisTest): - """ - The tests included in this class cover all the variations of possible - combinations of the following inputs: - * standard_name = [None, 'projection_y_coordinate', 'latitude_coordinate'] - * long_name = [None, 'lat_long_name'] - * var_name = ['grid_latitude', 'lat_var_name'] - * coord_name = [None, 'latitude'] - - standard_name, var_name and coord_name each contain a different valid CF - standard name so that it is clear which is being used to set the resulting - standard_name. - - """ - - @staticmethod - def _make_cf_var(standard_name, long_name, cf_name): - cf_var = mock.Mock( - cf_name=cf_name, - standard_name=standard_name, - long_name=long_name, - units="degrees", - dtype=np.float64, - cell_methods=None, - cf_group=mock.Mock(global_attributes={}), - ) - return cf_var - - def check_names(self, inputs, expected): - # Inputs - attributes on the fake CF Variable. Note: coord_name is - # optionally set in some pyke rules. - standard_name, long_name, var_name, coord_name = inputs - # Expected - The expected names and attributes. - exp_std_name, exp_long_name, exp_var_name, exp_attributes = expected - - cf_var = self._make_cf_var( - standard_name=standard_name, long_name=long_name, cf_name=var_name - ) - attributes = {} - res_standard_name, res_long_name, res_var_name = get_names( - cf_var, coord_name, attributes - ) - - # Check the names and attributes are as expected. - self.assertEqual(res_standard_name, exp_std_name) - self.assertEqual(res_long_name, exp_long_name) - self.assertEqual(res_var_name, exp_var_name) - self.assertEqual(attributes, exp_attributes) - - def test_var_name_valid(self): - # Only var_name is set and it is set to a valid standard name. - inp = (None, None, "grid_latitude", None) - exp = ("grid_latitude", None, "grid_latitude", {}) - self.check_names(inp, exp) - - def test_var_name_valid_coord_name_set(self): - # var_name is a valid standard name, coord_name is also set. - inp = (None, None, "grid_latitude", "latitude") - exp = ("latitude", None, "grid_latitude", {}) - self.check_names(inp, exp) - - def test_var_name_invalid(self): - # Only var_name is set but it is not a valid standard name. - inp = (None, None, "lat_var_name", None) - exp = (None, None, "lat_var_name", {}) - self.check_names(inp, exp) - - def test_var_name_invalid_coord_name_set(self): - # var_name is not a valid standard name, the coord_name is also set. - inp = (None, None, "lat_var_name", "latitude") - exp = ("latitude", None, "lat_var_name", {}) - self.check_names(inp, exp) - - def test_long_name_set_var_name_valid(self): - # long_name is not None, var_name is set to a valid standard name. - inp = (None, "lat_long_name", "grid_latitude", None) - exp = ("grid_latitude", "lat_long_name", "grid_latitude", {}) - self.check_names(inp, exp) - - def test_long_name_set_var_name_valid_coord_name_set(self): - # long_name is not None, var_name is set to a valid standard name, and - # coord_name is set. - inp = (None, "lat_long_name", "grid_latitude", "latitude") - exp = ("latitude", "lat_long_name", "grid_latitude", {}) - self.check_names(inp, exp) - - def test_long_name_set_var_name_invalid(self): - # long_name is not None, var_name is not set to a valid standard name. - inp = (None, "lat_long_name", "lat_var_name", None) - exp = (None, "lat_long_name", "lat_var_name", {}) - self.check_names(inp, exp) - - def test_long_name_set_var_name_invalid_coord_name_set(self): - # long_name is not None, var_name is not set to a valid standard name, - # and coord_name is set. - inp = (None, "lat_long_name", "lat_var_name", "latitude") - exp = ("latitude", "lat_long_name", "lat_var_name", {}) - self.check_names(inp, exp) - - def test_std_name_valid_var_name_valid(self): - # standard_name is a valid standard name, var_name is a valid standard - # name. - inp = ("projection_y_coordinate", None, "grid_latitude", None) - exp = ("projection_y_coordinate", None, "grid_latitude", {}) - self.check_names(inp, exp) - - def test_std_name_valid_var_name_valid_coord_name_set(self): - # standard_name is a valid standard name, var_name is a valid standard - # name, coord_name is set. - inp = ("projection_y_coordinate", None, "grid_latitude", "latitude") - exp = ("projection_y_coordinate", None, "grid_latitude", {}) - self.check_names(inp, exp) - - def test_std_name_valid_var_name_invalid(self): - # standard_name is a valid standard name, var_name is not a valid - # standard name. - inp = ("projection_y_coordinate", None, "lat_var_name", None) - exp = ("projection_y_coordinate", None, "lat_var_name", {}) - self.check_names(inp, exp) - - def test_std_name_valid_var_name_invalid_coord_name_set(self): - # standard_name is a valid standard name, var_name is not a valid - # standard name, coord_name is set. - inp = ("projection_y_coordinate", None, "lat_var_name", "latitude") - exp = ("projection_y_coordinate", None, "lat_var_name", {}) - self.check_names(inp, exp) - - def test_std_name_valid_long_name_set_var_name_valid(self): - # standard_name is a valid standard name, long_name is not None, - # var_name is a valid standard name. - inp = ( - "projection_y_coordinate", - "lat_long_name", - "grid_latitude", - None, - ) - exp = ("projection_y_coordinate", "lat_long_name", "grid_latitude", {}) - self.check_names(inp, exp) - - def test_std_name_valid_long_name_set_var_name_valid_coord_name_set(self): - # standard_name is a valid standard name, long_name is not None, - # var_name is a valid standard name, coord_name is set. - inp = ( - "projection_y_coordinate", - "lat_long_name", - "grid_latitude", - "latitude", - ) - exp = ("projection_y_coordinate", "lat_long_name", "grid_latitude", {}) - self.check_names(inp, exp) - - def test_std_name_valid_long_name_set_var_name_invalid(self): - # standard_name is a valid standard name, long_name is not None, - # var_name is not a valid standard name. - inp = ( - "projection_y_coordinate", - "lat_long_name", - "lat_var_name", - None, - ) - exp = ("projection_y_coordinate", "lat_long_name", "lat_var_name", {}) - self.check_names(inp, exp) - - def test_std_name_valid_long_name_set_var_name_invalid_coord_name_set( - self, - ): - # standard_name is a valid standard name, long_name is not None, - # var_name is not a valid standard name, coord_name is set. - inp = ( - "projection_y_coordinate", - "lat_long_name", - "lat_var_name", - "latitude", - ) - exp = ("projection_y_coordinate", "lat_long_name", "lat_var_name", {}) - self.check_names(inp, exp) - - def test_std_name_invalid_var_name_valid(self): - # standard_name is not a valid standard name, var_name is a valid - # standard name. - inp = ("latitude_coord", None, "grid_latitude", None) - exp = ("grid_latitude", None, "grid_latitude", {}) - self.check_names(inp, exp) - - def test_std_name_invalid_var_name_valid_coord_name_set(self): - # standard_name is not a valid standard name, var_name is a valid - # standard name, coord_name is set. - inp = ("latitude_coord", None, "grid_latitude", "latitude") - exp = ( - "latitude", - None, - "grid_latitude", - {"invalid_standard_name": "latitude_coord"}, - ) - self.check_names(inp, exp) - - def test_std_name_invalid_var_name_invalid(self): - # standard_name is not a valid standard name, var_name is not a valid - # standard name. - inp = ("latitude_coord", None, "lat_var_name", None) - exp = (None, None, "lat_var_name", {}) - self.check_names(inp, exp) - - def test_std_name_invalid_var_name_invalid_coord_name_set(self): - # standard_name is not a valid standard name, var_name is not a valid - # standard name, coord_name is set. - inp = ("latitude_coord", None, "lat_var_name", "latitude") - exp = ( - "latitude", - None, - "lat_var_name", - {"invalid_standard_name": "latitude_coord"}, - ) - self.check_names(inp, exp) - - def test_std_name_invalid_long_name_set_var_name_valid(self): - # standard_name is not a valid standard name, long_name is not None - # var_name is a valid standard name. - inp = ("latitude_coord", "lat_long_name", "grid_latitude", None) - exp = ( - "grid_latitude", - "lat_long_name", - "grid_latitude", - {"invalid_standard_name": "latitude_coord"}, - ) - self.check_names(inp, exp) - - def test_std_name_invalid_long_name_set_var_name_valid_coord_name_set( - self, - ): - # standard_name is not a valid standard name, long_name is not None, - # var_name is a valid standard name, coord_name is set. - inp = ("latitude_coord", "lat_long_name", "grid_latitude", "latitude") - exp = ( - "latitude", - "lat_long_name", - "grid_latitude", - {"invalid_standard_name": "latitude_coord"}, - ) - self.check_names(inp, exp) - - def test_std_name_invalid_long_name_set_var_name_invalid(self): - # standard_name is not a valid standard name, long_name is not None - # var_name is not a valid standard name. - inp = ("latitude_coord", "lat_long_name", "lat_var_name", None) - exp = ( - None, - "lat_long_name", - "lat_var_name", - {"invalid_standard_name": "latitude_coord"}, - ) - self.check_names(inp, exp) - - def test_std_name_invalid_long_name_set_var_name_invalid_coord_name_set( - self, - ): - # standard_name is not a valid standard name, long_name is not None, - # var_name is not a valid standard name, coord_name is set. - inp = ("latitude_coord", "lat_long_name", "lat_var_name", "latitude") - exp = ( - "latitude", - "lat_long_name", - "lat_var_name", - {"invalid_standard_name": "latitude_coord"}, - ) - self.check_names(inp, exp) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py deleted file mode 100644 index bb94adc72e..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_mercator_parameters.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -has_supported_mercator_parameters`. - -""" - -from unittest import mock -import warnings - -from iris.fileformats._nc_load_rules.helpers import ( - has_supported_mercator_parameters, -) - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - - -def _engine(cf_grid_var, cf_name): - cf_group = {cf_name: cf_grid_var} - cf_var = mock.Mock(cf_group=cf_group) - return mock.Mock(cf_var=cf_var) - - -class TestHasSupportedMercatorParameters(tests.IrisTest): - def test_valid_base(self): - cf_name = "mercator" - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=-90, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_mercator_parameters(engine, cf_name) - - self.assertTrue(is_valid) - - def test_valid_false_easting_northing(self): - cf_name = "mercator" - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=-90, - false_easting=15, - false_northing=10, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_mercator_parameters(engine, cf_name) - - self.assertTrue(is_valid) - - def test_valid_standard_parallel(self): - cf_name = "mercator" - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=-90, - false_easting=0, - false_northing=0, - standard_parallel=15, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_mercator_parameters(engine, cf_name) - - self.assertTrue(is_valid) - - def test_valid_scale_factor(self): - cf_name = "mercator" - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=0, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=0.9, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_mercator_parameters(engine, cf_name) - - self.assertTrue(is_valid) - - def test_invalid_scale_factor_and_standard_parallel(self): - # Scale factor and standard parallel cannot both be specified for - # Mercator projections - cf_name = "mercator" - cf_grid_var = mock.Mock( - spec=[], - longitude_of_projection_origin=0, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=0.9, - standard_parallel=20, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_mercator_parameters(engine, cf_name) - - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex( - str(warns[0]), - "both " - '"scale_factor_at_projection_origin" and "standard_parallel"', - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py deleted file mode 100755 index 6e6d6e4e81..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_has_supported_polar_stereographic_parameters.py +++ /dev/null @@ -1,242 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -has_supported_polar_stereographic_parameters`. - -""" - -from unittest import mock -import warnings - -from iris.fileformats._nc_load_rules.helpers import ( - has_supported_polar_stereographic_parameters, -) - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - - -def _engine(cf_grid_var, cf_name): - cf_group = {cf_name: cf_grid_var} - cf_var = mock.Mock(cf_group=cf_group) - return mock.Mock(cf_var=cf_var) - - -class TestHasSupportedPolarStereographicParameters(tests.IrisTest): - def test_valid_base_north(self): - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertTrue(is_valid) - - def test_valid_base_south(self): - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=-90, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertTrue(is_valid) - - def test_valid_straight_vertical_longitude(self): - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=30, - latitude_of_projection_origin=90, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertTrue(is_valid) - - def test_valid_false_easting_northing(self): - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - false_easting=15, - false_northing=10, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertTrue(is_valid) - - def test_valid_standard_parallel(self): - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - false_easting=0, - false_northing=0, - standard_parallel=15, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertTrue(is_valid) - - def test_valid_scale_factor(self): - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=0.9, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertTrue(is_valid) - - def test_invalid_scale_factor_and_standard_parallel(self): - # Scale factor and standard parallel cannot both be specified for - # Polar Stereographic projections - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=0.9, - standard_parallel=20, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex( - str(warns[0]), - "both " - '"scale_factor_at_projection_origin" and "standard_parallel"', - ) - - def test_absent_scale_factor_and_standard_parallel(self): - # Scale factor and standard parallel cannot both be specified for - # Polar Stereographic projections - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=90, - false_easting=0, - false_northing=0, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex( - str(warns[0]), - 'One of "scale_factor_at_projection_origin" and ' - '"standard_parallel" is required.', - ) - - def test_invalid_latitude_of_projection_origin(self): - # Scale factor and standard parallel cannot both be specified for - # Polar Stereographic projections - cf_name = "polar_stereographic" - cf_grid_var = mock.Mock( - spec=[], - straight_vertical_longitude_from_pole=0, - latitude_of_projection_origin=45, - false_easting=0, - false_northing=0, - scale_factor_at_projection_origin=1, - semi_major_axis=6377563.396, - semi_minor_axis=6356256.909, - ) - engine = _engine(cf_grid_var, cf_name) - - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - is_valid = has_supported_polar_stereographic_parameters( - engine, cf_name - ) - - self.assertFalse(is_valid) - self.assertEqual(len(warns), 1) - self.assertRegex( - str(warns[0]), - r'"latitude_of_projection_origin" must be \+90 or -90\.', - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_reorder_bounds_data.py b/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_reorder_bounds_data.py deleted file mode 100644 index 1ee0cfbf2e..0000000000 --- a/lib/iris/tests/unit/fileformats/nc_load_rules/helpers/test_reorder_bounds_data.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.fileformats._nc_load_rules.helpers.\ -reorder_bounds_data`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.fileformats._nc_load_rules.helpers import reorder_bounds_data - - -class Test(tests.IrisTest): - def test_fastest_varying(self): - bounds_data = np.arange(24).reshape(2, 3, 4) - cf_bounds_var = mock.Mock( - dimensions=("foo", "bar", "nv"), cf_name="wibble_bnds" - ) - cf_coord_var = mock.Mock(dimensions=("foo", "bar")) - - res = reorder_bounds_data(bounds_data, cf_bounds_var, cf_coord_var) - # Vertex dimension (nv) is already at the end. - self.assertArrayEqual(res, bounds_data) - - def test_slowest_varying(self): - bounds_data = np.arange(24).reshape(4, 2, 3) - cf_bounds_var = mock.Mock(dimensions=("nv", "foo", "bar")) - cf_coord_var = mock.Mock(dimensions=("foo", "bar")) - - res = reorder_bounds_data(bounds_data, cf_bounds_var, cf_coord_var) - # Move zeroth dimension (nv) to the end. - expected = np.rollaxis(bounds_data, 0, bounds_data.ndim) - self.assertArrayEqual(res, expected) - - def test_different_dim_names(self): - bounds_data = np.arange(24).reshape(2, 3, 4) - cf_bounds_var = mock.Mock( - dimensions=("foo", "bar", "nv"), cf_name="wibble_bnds" - ) - cf_coord_var = mock.Mock(dimensions=("x", "y"), cf_name="wibble") - with self.assertRaisesRegex(ValueError, "dimension names"): - reorder_bounds_data(bounds_data, cf_bounds_var, cf_coord_var) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/__init__.py b/lib/iris/tests/unit/fileformats/netcdf/__init__.py deleted file mode 100644 index 732094f67a..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.netcdf` module.""" diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py b/lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py deleted file mode 100644 index 7c2ae96158..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.netcdf.loader` module.""" diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test__get_cf_var_data.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__get_cf_var_data.py deleted file mode 100644 index 054c8e2db1..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test__get_cf_var_data.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.netcdf._get_cf_var_data` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from dask.array import Array as dask_array -import numpy as np - -from iris._lazy_data import _optimum_chunksize -import iris.fileformats.cf -from iris.fileformats.netcdf.loader import _get_cf_var_data - - -class Test__get_cf_var_data(tests.IrisTest): - def setUp(self): - self.filename = "DUMMY" - self.shape = (300000, 240, 200) - self.expected_chunks = _optimum_chunksize(self.shape, self.shape) - - def _make(self, chunksizes): - cf_data = mock.Mock(_FillValue=None) - cf_data.chunking = mock.MagicMock(return_value=chunksizes) - cf_var = mock.MagicMock( - spec=iris.fileformats.cf.CFVariable, - dtype=np.dtype("i4"), - cf_data=cf_data, - cf_name="DUMMY_VAR", - shape=self.shape, - ) - return cf_var - - def test_cf_data_type(self): - chunks = [1, 12, 100] - cf_var = self._make(chunks) - lazy_data = _get_cf_var_data(cf_var, self.filename) - self.assertIsInstance(lazy_data, dask_array) - - def test_cf_data_chunks(self): - chunks = [2500, 240, 200] - cf_var = self._make(chunks) - lazy_data = _get_cf_var_data(cf_var, self.filename) - lazy_data_chunks = [c[0] for c in lazy_data.chunks] - expected_chunks = _optimum_chunksize(chunks, self.shape) - self.assertArrayEqual(lazy_data_chunks, expected_chunks) - - def test_cf_data_no_chunks(self): - # No chunks means chunks are calculated from the array's shape by - # `iris._lazy_data._optimum_chunksize()`. - chunks = None - cf_var = self._make(chunks) - lazy_data = _get_cf_var_data(cf_var, self.filename) - lazy_data_chunks = [c[0] for c in lazy_data.chunks] - self.assertArrayEqual(lazy_data_chunks, self.expected_chunks) - - def test_cf_data_contiguous(self): - # Chunks 'contiguous' is equivalent to no chunks. - chunks = "contiguous" - cf_var = self._make(chunks) - lazy_data = _get_cf_var_data(cf_var, self.filename) - lazy_data_chunks = [c[0] for c in lazy_data.chunks] - self.assertArrayEqual(lazy_data_chunks, self.expected_chunks) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py deleted file mode 100644 index 841935cc81..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_aux_factory.py +++ /dev/null @@ -1,193 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.netcdf._load_aux_factory` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock -import warnings - -import numpy as np - -from iris.coords import DimCoord -from iris.cube import Cube -from iris.fileformats.netcdf.loader import _load_aux_factory - - -class TestAtmosphereHybridSigmaPressureCoordinate(tests.IrisTest): - def setUp(self): - standard_name = "atmosphere_hybrid_sigma_pressure_coordinate" - self.requires = dict(formula_type=standard_name) - self.ap = mock.MagicMock(units="units") - self.ps = mock.MagicMock(units="units") - coordinates = [(mock.sentinel.b, "b"), (self.ps, "ps")] - self.cube_parts = dict(coordinates=coordinates) - self.engine = mock.Mock( - requires=self.requires, cube_parts=self.cube_parts - ) - self.cube = mock.create_autospec(Cube, spec_set=True, instance=True) - # Patch out the check_dependencies functionality. - func = "iris.aux_factory.HybridPressureFactory._check_dependencies" - patcher = mock.patch(func) - patcher.start() - self.addCleanup(patcher.stop) - - def test_formula_terms_ap(self): - self.cube_parts["coordinates"].append((self.ap, "ap")) - self.requires["formula_terms"] = dict(ap="ap", b="b", ps="ps") - _load_aux_factory(self.engine, self.cube) - # Check cube.add_aux_coord method. - self.assertEqual(self.cube.add_aux_coord.call_count, 0) - # Check cube.add_aux_factory method. - self.assertEqual(self.cube.add_aux_factory.call_count, 1) - args, _ = self.cube.add_aux_factory.call_args - self.assertEqual(len(args), 1) - factory = args[0] - self.assertEqual(factory.delta, self.ap) - self.assertEqual(factory.sigma, mock.sentinel.b) - self.assertEqual(factory.surface_air_pressure, self.ps) - - def test_formula_terms_a_p0(self): - coord_a = DimCoord(np.arange(5), units="1") - coord_p0 = DimCoord(10, units="Pa") - coord_expected = DimCoord( - np.arange(5) * 10, - units="Pa", - long_name="vertical pressure", - var_name="ap", - ) - self.cube_parts["coordinates"].extend( - [(coord_a, "a"), (coord_p0, "p0")] - ) - self.requires["formula_terms"] = dict(a="a", b="b", ps="ps", p0="p0") - _load_aux_factory(self.engine, self.cube) - # Check cube.coord_dims method. - self.assertEqual(self.cube.coord_dims.call_count, 1) - args, _ = self.cube.coord_dims.call_args - self.assertEqual(len(args), 1) - self.assertIs(args[0], coord_a) - # Check cube.add_aux_coord method. - self.assertEqual(self.cube.add_aux_coord.call_count, 1) - args, _ = self.cube.add_aux_coord.call_args - self.assertEqual(len(args), 2) - self.assertEqual(args[0], coord_expected) - self.assertIsInstance(args[1], mock.Mock) - # Check cube.add_aux_factory method. - self.assertEqual(self.cube.add_aux_factory.call_count, 1) - args, _ = self.cube.add_aux_factory.call_args - self.assertEqual(len(args), 1) - factory = args[0] - self.assertEqual(factory.delta, coord_expected) - self.assertEqual(factory.sigma, mock.sentinel.b) - self.assertEqual(factory.surface_air_pressure, self.ps) - - def test_formula_terms_a_p0__promote_a_units_unknown_to_dimensionless( - self, - ): - coord_a = DimCoord(np.arange(5), units="unknown") - coord_p0 = DimCoord(10, units="Pa") - coord_expected = DimCoord( - np.arange(5) * 10, - units="Pa", - long_name="vertical pressure", - var_name="ap", - ) - self.cube_parts["coordinates"].extend( - [(coord_a, "a"), (coord_p0, "p0")] - ) - self.requires["formula_terms"] = dict(a="a", b="b", ps="ps", p0="p0") - _load_aux_factory(self.engine, self.cube) - # Check cube.coord_dims method. - self.assertEqual(self.cube.coord_dims.call_count, 1) - args, _ = self.cube.coord_dims.call_args - self.assertEqual(len(args), 1) - self.assertIs(args[0], coord_a) - self.assertEqual("1", args[0].units) - # Check cube.add_aux_coord method. - self.assertEqual(self.cube.add_aux_coord.call_count, 1) - args, _ = self.cube.add_aux_coord.call_args - self.assertEqual(len(args), 2) - self.assertEqual(args[0], coord_expected) - self.assertIsInstance(args[1], mock.Mock) - # Check cube.add_aux_factory method. - self.assertEqual(self.cube.add_aux_factory.call_count, 1) - args, _ = self.cube.add_aux_factory.call_args - self.assertEqual(len(args), 1) - factory = args[0] - self.assertEqual(factory.delta, coord_expected) - self.assertEqual(factory.sigma, mock.sentinel.b) - self.assertEqual(factory.surface_air_pressure, self.ps) - - def test_formula_terms_p0_non_scalar(self): - coord_p0 = DimCoord(np.arange(5)) - self.cube_parts["coordinates"].append((coord_p0, "p0")) - self.requires["formula_terms"] = dict(p0="p0") - with self.assertRaises(ValueError): - _load_aux_factory(self.engine, self.cube) - - def test_formula_terms_p0_bounded(self): - coord_a = DimCoord(np.arange(5)) - coord_p0 = DimCoord(1, bounds=[0, 2], var_name="p0") - self.cube_parts["coordinates"].extend( - [(coord_a, "a"), (coord_p0, "p0")] - ) - self.requires["formula_terms"] = dict(a="a", b="b", ps="ps", p0="p0") - with warnings.catch_warnings(record=True) as warn: - warnings.simplefilter("always") - _load_aux_factory(self.engine, self.cube) - self.assertEqual(len(warn), 1) - msg = ( - "Ignoring atmosphere hybrid sigma pressure scalar " - "coordinate {!r} bounds.".format(coord_p0.name()) - ) - self.assertEqual(msg, str(warn[0].message)) - - def _check_no_delta(self): - # Check cube.add_aux_coord method. - self.assertEqual(self.cube.add_aux_coord.call_count, 0) - # Check cube.add_aux_factory method. - self.assertEqual(self.cube.add_aux_factory.call_count, 1) - args, _ = self.cube.add_aux_factory.call_args - self.assertEqual(len(args), 1) - factory = args[0] - # Check that the factory has no delta term - self.assertEqual(factory.delta, None) - self.assertEqual(factory.sigma, mock.sentinel.b) - self.assertEqual(factory.surface_air_pressure, self.ps) - - def test_formula_terms_ap_missing_coords(self): - self.requires["formula_terms"] = dict(ap="ap", b="b", ps="ps") - with mock.patch("warnings.warn") as warn: - _load_aux_factory(self.engine, self.cube) - warn.assert_called_once_with( - "Unable to find coordinate for variable " "'ap'" - ) - self._check_no_delta() - - def test_formula_terms_no_delta_terms(self): - self.requires["formula_terms"] = dict(b="b", ps="ps") - _load_aux_factory(self.engine, self.cube) - self._check_no_delta() - - def test_formula_terms_no_p0_term(self): - coord_a = DimCoord(np.arange(5), units="Pa") - self.cube_parts["coordinates"].append((coord_a, "a")) - self.requires["formula_terms"] = dict(a="a", b="b", ps="ps") - _load_aux_factory(self.engine, self.cube) - self._check_no_delta() - - def test_formula_terms_no_a_term(self): - coord_p0 = DimCoord(10, units="1") - self.cube_parts["coordinates"].append((coord_p0, "p0")) - self.requires["formula_terms"] = dict(a="p0", b="b", ps="ps") - _load_aux_factory(self.engine, self.cube) - self._check_no_delta() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_cube.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_cube.py deleted file mode 100644 index 6e28a2f8e4..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test__load_cube.py +++ /dev/null @@ -1,181 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.netcdf._load_cube` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.coords import DimCoord -import iris.fileformats.cf -from iris.fileformats.netcdf.loader import _load_cube - - -class TestCoordAttributes(tests.IrisTest): - @staticmethod - def _patcher(engine, cf, cf_group): - coordinates = [] - for coord in cf_group: - engine.cube.add_aux_coord(coord) - coordinates.append((coord, coord.name())) - engine.cube_parts["coordinates"] = coordinates - - def setUp(self): - this = "iris.fileformats.netcdf.loader._assert_case_specific_facts" - patch = mock.patch(this, side_effect=self._patcher) - patch.start() - self.addCleanup(patch.stop) - self.engine = mock.Mock() - self.filename = "DUMMY" - self.flag_masks = mock.sentinel.flag_masks - self.flag_meanings = mock.sentinel.flag_meanings - self.flag_values = mock.sentinel.flag_values - self.valid_range = mock.sentinel.valid_range - self.valid_min = mock.sentinel.valid_min - self.valid_max = mock.sentinel.valid_max - - def _make(self, names, attrs): - coords = [DimCoord(i, long_name=name) for i, name in enumerate(names)] - shape = (1,) - - cf_group = {} - for name, cf_attrs in zip(names, attrs): - cf_attrs_unused = mock.Mock(return_value=cf_attrs) - cf_group[name] = mock.Mock(cf_attrs_unused=cf_attrs_unused) - cf = mock.Mock(cf_group=cf_group) - - cf_data = mock.Mock(_FillValue=None) - cf_data.chunking = mock.MagicMock(return_value=shape) - cf_var = mock.MagicMock( - spec=iris.fileformats.cf.CFVariable, - dtype=np.dtype("i4"), - cf_data=cf_data, - cf_name="DUMMY_VAR", - cf_group=coords, - shape=shape, - ) - return cf, cf_var - - def test_flag_pass_thru(self): - items = [ - ("masks", "flag_masks", self.flag_masks), - ("meanings", "flag_meanings", self.flag_meanings), - ("values", "flag_values", self.flag_values), - ] - for name, attr, value in items: - names = [name] - attrs = [[(attr, value)]] - cf, cf_var = self._make(names, attrs) - cube = _load_cube(self.engine, cf, cf_var, self.filename) - self.assertEqual(len(cube.coords(name)), 1) - coord = cube.coord(name) - self.assertEqual(len(coord.attributes), 1) - self.assertEqual(list(coord.attributes.keys()), [attr]) - self.assertEqual(list(coord.attributes.values()), [value]) - - def test_flag_pass_thru_multi(self): - names = ["masks", "meanings", "values"] - attrs = [ - [("flag_masks", self.flag_masks), ("wibble", "wibble")], - [ - ("flag_meanings", self.flag_meanings), - ("add_offset", "add_offset"), - ], - [("flag_values", self.flag_values)], - [("valid_range", self.valid_range)], - [("valid_min", self.valid_min)], - [("valid_max", self.valid_max)], - ] - cf, cf_var = self._make(names, attrs) - cube = _load_cube(self.engine, cf, cf_var, self.filename) - self.assertEqual(len(cube.coords()), 3) - self.assertEqual(set([c.name() for c in cube.coords()]), set(names)) - expected = [ - attrs[0], - [attrs[1][0]], - attrs[2], - attrs[3], - attrs[4], - attrs[5], - ] - for name, expect in zip(names, expected): - attributes = cube.coord(name).attributes - self.assertEqual(set(attributes.items()), set(expect)) - - -class TestCubeAttributes(tests.IrisTest): - def setUp(self): - this = "iris.fileformats.netcdf.loader._assert_case_specific_facts" - patch = mock.patch(this) - patch.start() - self.addCleanup(patch.stop) - self.engine = mock.Mock() - self.cf = None - self.filename = "DUMMY" - self.flag_masks = mock.sentinel.flag_masks - self.flag_meanings = mock.sentinel.flag_meanings - self.flag_values = mock.sentinel.flag_values - self.valid_range = mock.sentinel.valid_range - self.valid_min = mock.sentinel.valid_min - self.valid_max = mock.sentinel.valid_max - - def _make(self, attrs): - shape = (1,) - cf_attrs_unused = mock.Mock(return_value=attrs) - cf_data = mock.Mock(_FillValue=None) - cf_data.chunking = mock.MagicMock(return_value=shape) - cf_var = mock.MagicMock( - spec=iris.fileformats.cf.CFVariable, - dtype=np.dtype("i4"), - cf_data=cf_data, - cf_name="DUMMY_VAR", - cf_group=mock.Mock(), - cf_attrs_unused=cf_attrs_unused, - shape=shape, - ) - return cf_var - - def test_flag_pass_thru(self): - attrs = [ - ("flag_masks", self.flag_masks), - ("flag_meanings", self.flag_meanings), - ("flag_values", self.flag_values), - ] - for key, value in attrs: - cf_var = self._make([(key, value)]) - cube = _load_cube(self.engine, self.cf, cf_var, self.filename) - self.assertEqual(len(cube.attributes), 1) - self.assertEqual(list(cube.attributes.keys()), [key]) - self.assertEqual(list(cube.attributes.values()), [value]) - - def test_flag_pass_thru_multi(self): - attrs = [ - ("flag_masks", self.flag_masks), - ("wibble", "wobble"), - ("flag_meanings", self.flag_meanings), - ("add_offset", "add_offset"), - ("flag_values", self.flag_values), - ("standard_name", "air_temperature"), - ("valid_range", self.valid_range), - ("valid_min", self.valid_min), - ("valid_max", self.valid_max), - ] - - # Expect everything from above to be returned except those - # corresponding to exclude_ind. - expected = set([attrs[ind] for ind in [0, 1, 2, 4, 6, 7, 8]]) - cf_var = self._make(attrs) - cube = _load_cube(self.engine, self.cf, cf_var, self.filename) - self.assertEqual(len(cube.attributes), len(expected)) - self.assertEqual(set(cube.attributes.items()), expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py b/lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py deleted file mode 100644 index 77bb0d3950..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/loader/test__translate_constraints_to_var_callback.py +++ /dev/null @@ -1,102 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.netcdf._translate_constraints_to_var_callback`. - -""" - -from unittest.mock import MagicMock - -import iris -from iris.fileformats.cf import CFDataVariable -from iris.fileformats.netcdf.loader import ( - _translate_constraints_to_var_callback, -) - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests - - -class Test(tests.IrisTest): - data_variables = [ - CFDataVariable("var1", MagicMock(standard_name="x_wind")), - CFDataVariable("var2", MagicMock(standard_name="y_wind")), - CFDataVariable("var1", MagicMock(long_name="x component of wind")), - CFDataVariable( - "var1", - MagicMock(standard_name="x_wind", long_name="x component of wind"), - ), - CFDataVariable("var1", MagicMock()), - ] - - def test_multiple_constraints(self): - constrs = [ - iris.NameConstraint(standard_name="x_wind"), - iris.NameConstraint(var_name="var1"), - ] - result = _translate_constraints_to_var_callback(constrs) - self.assertIsNone(result) - - def test_non_NameConstraint(self): - constr = iris.AttributeConstraint(STASH="m01s00i002") - result = _translate_constraints_to_var_callback(constr) - self.assertIsNone(result) - - def test_str_constraint(self): - result = _translate_constraints_to_var_callback("x_wind") - self.assertIsNone(result) - - def test_Constaint_with_name(self): - constr = iris.Constraint(name="x_wind") - result = _translate_constraints_to_var_callback(constr) - self.assertIsNone(result) - - def test_NameConstraint_standard_name(self): - constr = iris.NameConstraint(standard_name="x_wind") - callback = _translate_constraints_to_var_callback(constr) - result = [callback(var) for var in self.data_variables] - self.assertArrayEqual(result, [True, False, False, True, False]) - - def test_NameConstraint_long_name(self): - constr = iris.NameConstraint(long_name="x component of wind") - callback = _translate_constraints_to_var_callback(constr) - result = [callback(var) for var in self.data_variables] - self.assertArrayEqual(result, [False, False, True, True, False]) - - def test_NameConstraint_var_name(self): - constr = iris.NameConstraint(var_name="var1") - callback = _translate_constraints_to_var_callback(constr) - result = [callback(var) for var in self.data_variables] - self.assertArrayEqual(result, [True, False, True, True, True]) - - def test_NameConstraint_standard_name_var_name(self): - constr = iris.NameConstraint(standard_name="x_wind", var_name="var1") - callback = _translate_constraints_to_var_callback(constr) - result = [callback(var) for var in self.data_variables] - self.assertArrayEqual(result, [True, False, False, True, False]) - - def test_NameConstraint_standard_name_long_name_var_name(self): - constr = iris.NameConstraint( - standard_name="x_wind", - long_name="x component of wind", - var_name="var1", - ) - callback = _translate_constraints_to_var_callback(constr) - result = [callback(var) for var in self.data_variables] - self.assertArrayEqual(result, [False, False, False, True, False]) - - def test_NameConstraint_with_STASH(self): - constr = iris.NameConstraint( - standard_name="x_wind", STASH="m01s00i024" - ) - result = _translate_constraints_to_var_callback(constr) - self.assertIsNone(result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py b/lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py deleted file mode 100644 index a68d5fc5d0..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.netcdf.saver` module.""" diff --git a/lib/iris/tests/unit/fileformats/netcdf/saver/test__FillValueMaskCheckAndStoreTarget.py b/lib/iris/tests/unit/fileformats/netcdf/saver/test__FillValueMaskCheckAndStoreTarget.py deleted file mode 100644 index 77209efafc..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/saver/test__FillValueMaskCheckAndStoreTarget.py +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the `iris.fileformats.netcdf._FillValueMaskCheckAndStoreTarget` -class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.fileformats.netcdf.saver import _FillValueMaskCheckAndStoreTarget - - -class Test__FillValueMaskCheckAndStoreTarget(tests.IrisTest): - def _call_target(self, fill_value, keys, vals): - inner_target = mock.MagicMock() - target = _FillValueMaskCheckAndStoreTarget( - inner_target, fill_value=fill_value - ) - - for key, val in zip(keys, vals): - target[key] = val - - calls = [mock.call(key, val) for key, val in zip(keys, vals)] - inner_target.__setitem__.assert_has_calls(calls) - - return target - - def test___setitem__(self): - self._call_target(None, [1], [2]) - - def test_no_fill_value_not_masked(self): - # Test when the fill value is not present and the data is not masked - keys = [slice(0, 10), slice(10, 15)] - vals = [np.arange(10), np.arange(5)] - fill_value = 16 - target = self._call_target(fill_value, keys, vals) - self.assertFalse(target.contains_value) - self.assertFalse(target.is_masked) - - def test_contains_fill_value_not_masked(self): - # Test when the fill value is present and the data is not masked - keys = [slice(0, 10), slice(10, 15)] - vals = [np.arange(10), np.arange(5)] - fill_value = 5 - target = self._call_target(fill_value, keys, vals) - self.assertTrue(target.contains_value) - self.assertFalse(target.is_masked) - - def test_no_fill_value_masked(self): - # Test when the fill value is not present and the data is masked - keys = [slice(0, 10), slice(10, 15)] - vals = [np.arange(10), np.ma.masked_equal(np.arange(5), 3)] - fill_value = 16 - target = self._call_target(fill_value, keys, vals) - self.assertFalse(target.contains_value) - self.assertTrue(target.is_masked) - - def test_contains_fill_value_masked(self): - # Test when the fill value is present and the data is masked - keys = [slice(0, 10), slice(10, 15)] - vals = [np.arange(10), np.ma.masked_equal(np.arange(5), 3)] - fill_value = 5 - target = self._call_target(fill_value, keys, vals) - self.assertTrue(target.contains_value) - self.assertTrue(target.is_masked) - - def test_fill_value_None(self): - # Test when the fill value is None - keys = [slice(0, 10), slice(10, 15)] - vals = [np.arange(10), np.arange(5)] - fill_value = None - target = self._call_target(fill_value, keys, vals) - self.assertFalse(target.contains_value) - - def test_contains_masked_fill_value(self): - # Test when the fill value is present but masked the data is masked - keys = [slice(0, 10), slice(10, 15)] - vals = [np.arange(10), np.ma.masked_equal(np.arange(10, 15), 13)] - fill_value = 13 - target = self._call_target(fill_value, keys, vals) - self.assertFalse(target.contains_value) - self.assertTrue(target.is_masked) diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py deleted file mode 100644 index 6fa9e9e096..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver.py +++ /dev/null @@ -1,1088 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.netcdf.Saver` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import collections -from contextlib import contextmanager -from unittest import mock - -import numpy as np -from numpy import ma - -import iris -from iris.coord_systems import ( - AlbersEqualArea, - GeogCS, - Geostationary, - LambertAzimuthalEqualArea, - LambertConformal, - Mercator, - RotatedGeogCS, - Stereographic, - TransverseMercator, - VerticalPerspective, -) -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube -from iris.fileformats.netcdf import Saver, _thread_safe_nc -import iris.tests.stock as stock - - -class Test_write(tests.IrisTest): - # ------------------------------------------------------------------------- - # It is not considered necessary to have integration tests for saving - # EVERY coordinate system. A subset are tested below. - # ------------------------------------------------------------------------- - - # Attribute is substituted in test_Saver__lazy. - array_lib = np - - def _transverse_mercator_cube(self, ellipsoid=None): - data = self.array_lib.arange(12).reshape(3, 4) - cube = Cube(data, "air_pressure_anomaly") - trans_merc = TransverseMercator( - 49.0, -2.0, -400000.0, 100000.0, 0.9996012717, ellipsoid - ) - coord = DimCoord( - np.arange(3), - "projection_y_coordinate", - units="m", - coord_system=trans_merc, - ) - cube.add_dim_coord(coord, 0) - coord = DimCoord( - np.arange(4), - "projection_x_coordinate", - units="m", - coord_system=trans_merc, - ) - cube.add_dim_coord(coord, 1) - return cube - - def _mercator_cube(self, ellipsoid=None): - data = self.array_lib.arange(12).reshape(3, 4) - cube = Cube(data, "air_pressure_anomaly") - merc = Mercator(49.0, ellipsoid) - coord = DimCoord( - np.arange(3), - "projection_y_coordinate", - units="m", - coord_system=merc, - ) - cube.add_dim_coord(coord, 0) - coord = DimCoord( - np.arange(4), - "projection_x_coordinate", - units="m", - coord_system=merc, - ) - cube.add_dim_coord(coord, 1) - return cube - - def _stereo_cube(self, ellipsoid=None, scale_factor=None): - data = self.array_lib.arange(12).reshape(3, 4) - cube = Cube(data, "air_pressure_anomaly") - stereo = Stereographic( - -10.0, - 20.0, - 500000.0, - -200000.0, - None, - ellipsoid, - scale_factor_at_projection_origin=scale_factor, - ) - coord = DimCoord( - np.arange(3), - "projection_y_coordinate", - units="m", - coord_system=stereo, - ) - cube.add_dim_coord(coord, 0) - coord = DimCoord( - np.arange(4), - "projection_x_coordinate", - units="m", - coord_system=stereo, - ) - cube.add_dim_coord(coord, 1) - return cube - - def test_transverse_mercator(self): - # Create a Cube with a transverse Mercator coordinate system. - ellipsoid = GeogCS(6377563.396, 6356256.909) - cube = self._transverse_mercator_cube(ellipsoid) - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def test_transverse_mercator_no_ellipsoid(self): - # Create a Cube with a transverse Mercator coordinate system. - cube = self._transverse_mercator_cube() - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def test_mercator(self): - # Create a Cube with a Mercator coordinate system. - ellipsoid = GeogCS(6377563.396, 6356256.909) - cube = self._mercator_cube(ellipsoid) - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def test_stereographic(self): - # Create a Cube with a stereographic coordinate system. - ellipsoid = GeogCS(6377563.396, 6356256.909) - cube = self._stereo_cube(ellipsoid) - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def test_mercator_no_ellipsoid(self): - # Create a Cube with a Mercator coordinate system. - cube = self._mercator_cube() - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def test_stereographic_no_ellipsoid(self): - # Create a Cube with a stereographic coordinate system. - cube = self._stereo_cube() - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def test_stereographic_scale_factor(self): - # Create a Cube with a stereographic coordinate system. - cube = self._stereo_cube(scale_factor=1.3) - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def _simple_cube(self, dtype): - data = self.array_lib.arange(12, dtype=dtype).reshape(3, 4) - points = np.arange(3, dtype=dtype) - bounds = np.arange(6, dtype=dtype).reshape(3, 2) - cube = Cube(data, "air_pressure_anomaly") - coord = DimCoord(points, bounds=bounds, units="1") - cube.add_dim_coord(coord, 0) - return cube - - def test_little_endian(self): - # Create a Cube with little-endian data. - cube = self._simple_cube("f4") - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - result_path = self.result_path("endian", "cdl") - self.assertCDL(nc_path, result_path, flags="") - - def test_zlib(self): - cube = self._simple_cube(">f4") - api = self.patch("iris.fileformats.netcdf.saver._thread_safe_nc") - # Define mocked default fill values to prevent deprecation warning (#4374). - api.default_fillvals = collections.defaultdict(lambda: -99.0) - with Saver("/dummy/path", "NETCDF4") as saver: - saver.write(cube, zlib=True) - dataset = api.DatasetWrapper.return_value - create_var_call = mock.call( - "air_pressure_anomaly", - np.dtype("float32"), - ["dim0", "dim1"], - fill_value=None, - shuffle=True, - least_significant_digit=None, - contiguous=False, - zlib=True, - fletcher32=False, - endian="native", - complevel=4, - chunksizes=None, - ) - self.assertIn(create_var_call, dataset.createVariable.call_args_list) - - def test_least_significant_digit(self): - cube = Cube( - self.array_lib.array([1.23, 4.56, 7.89]), - standard_name="surface_temperature", - long_name=None, - var_name="temp", - units="K", - ) - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, least_significant_digit=1) - cube_saved = iris.load_cube(nc_path) - self.assertEqual( - cube_saved.attributes["least_significant_digit"], 1 - ) - self.assertFalse(np.all(cube.data == cube_saved.data)) - self.assertArrayAllClose(cube.data, cube_saved.data, 0.1) - - def test_default_unlimited_dimensions(self): - # Default is no unlimited dimensions. - cube = self._simple_cube(">f4") - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - self.assertFalse(ds.dimensions["dim0"].isunlimited()) - self.assertFalse(ds.dimensions["dim1"].isunlimited()) - ds.close() - - def test_no_unlimited_dimensions(self): - cube = self._simple_cube(">f4") - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=None) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - for dim in ds.dimensions.values(): - self.assertFalse(dim.isunlimited()) - ds.close() - - def test_invalid_unlimited_dimensions(self): - cube = self._simple_cube(">f4") - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - # should not raise an exception - saver.write(cube, unlimited_dimensions=["not_found"]) - - def test_custom_unlimited_dimensions(self): - cube = self._transverse_mercator_cube() - unlimited_dimensions = [ - "projection_y_coordinate", - "projection_x_coordinate", - ] - # test coordinates by name - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=unlimited_dimensions) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - for dim in unlimited_dimensions: - self.assertTrue(ds.dimensions[dim].isunlimited()) - ds.close() - # test coordinate arguments - with self.temp_filename(".nc") as nc_path: - coords = [cube.coord(dim) for dim in unlimited_dimensions] - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=coords) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - for dim in unlimited_dimensions: - self.assertTrue(ds.dimensions[dim].isunlimited()) - ds.close() - - def test_reserved_attributes(self): - cube = self._simple_cube(">f4") - cube.attributes["dimensions"] = "something something_else" - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - res = ds.getncattr("dimensions") - ds.close() - self.assertEqual(res, "something something_else") - - def test_with_climatology(self): - cube = stock.climatology_3d() - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - self.assertCDL(nc_path) - - def test_dimensional_to_scalar(self): - # Bounds for 1 point are still in a 2D array. - scalar_bounds = self.array_lib.arange(2).reshape(1, 2) - scalar_point = scalar_bounds.mean() - scalar_data = self.array_lib.zeros(1) - scalar_coord = AuxCoord(points=scalar_point, bounds=scalar_bounds) - cube = Cube(scalar_data, aux_coords_and_dims=[(scalar_coord, 0)])[0] - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - # Confirm that the only dimension is the one denoting the number - # of bounds - have successfully saved the 2D bounds array into 1D. - self.assertEqual(["bnds"], list(ds.dimensions.keys())) - ds.close() - - -class Test__create_cf_bounds(tests.IrisTest): - # Method is substituted in test_Saver__lazy. - @staticmethod - def climatology_3d(): - return stock.climatology_3d() - - def _check_bounds_setting(self, climatological=False): - # Generic test that can run with or without a climatological coord. - cube = self.climatology_3d() - coord = cube.coord("time").copy() - # Over-write original value from stock.climatology_3d with test value. - coord.climatological = climatological - - # Set up expected strings. - if climatological: - property_name = "climatology" - varname_extra = "climatology" - else: - property_name = "bounds" - varname_extra = "bnds" - boundsvar_name = "time_" + varname_extra - - # Set up arguments for testing _create_cf_bounds. - saver = mock.MagicMock(spec=Saver) - # NOTE: 'saver' must have spec=Saver to fake isinstance(save, Saver), - # so it can pass as 'self' in the call to _create_cf_cbounds. - # Mock a '_dataset' property; not automatic because 'spec=Saver'. - saver._dataset = mock.MagicMock() - # Mock the '_ensure_valid_dtype' method to return an object with a - # suitable 'shape' and 'dtype'. - saver._ensure_valid_dtype.return_value = mock.Mock( - shape=coord.bounds.shape, dtype=coord.bounds.dtype - ) - var = mock.MagicMock(spec=_thread_safe_nc.VariableWrapper) - - # Make the main call. - Saver._create_cf_bounds(saver, coord, var, "time") - - # Test the call of _setncattr in _create_cf_bounds. - setncattr_call = mock.call( - property_name, boundsvar_name.encode(encoding="ascii") - ) - self.assertEqual(setncattr_call, var.setncattr.call_args) - - # Test the call of createVariable in _create_cf_bounds. - dataset = saver._dataset - expected_dimensions = var.dimensions + ("bnds",) - create_var_call = mock.call( - boundsvar_name, coord.bounds.dtype, expected_dimensions - ) - self.assertEqual(create_var_call, dataset.createVariable.call_args) - - def test_set_bounds_default(self): - self._check_bounds_setting(climatological=False) - - def test_set_bounds_climatology(self): - self._check_bounds_setting(climatological=True) - - -class Test_write__valid_x_cube_attributes(tests.IrisTest): - """Testing valid_range, valid_min and valid_max attributes.""" - - # Attribute is substituted in test_Saver__lazy. - array_lib = np - - def test_valid_range_saved(self): - cube = tests.stock.lat_lon_cube() - cube.data = cube.data.astype("int32") - - vrange = self.array_lib.array([1, 2], dtype="int32") - cube.attributes["valid_range"] = vrange - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=[]) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - self.assertArrayEqual(ds.valid_range, vrange) - ds.close() - - def test_valid_min_saved(self): - cube = tests.stock.lat_lon_cube() - cube.data = cube.data.astype("int32") - - cube.attributes["valid_min"] = 1 - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=[]) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - self.assertArrayEqual(ds.valid_min, 1) - ds.close() - - def test_valid_max_saved(self): - cube = tests.stock.lat_lon_cube() - cube.data = cube.data.astype("int32") - - cube.attributes["valid_max"] = 2 - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=[]) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - self.assertArrayEqual(ds.valid_max, 2) - ds.close() - - -class Test_write__valid_x_coord_attributes(tests.IrisTest): - """Testing valid_range, valid_min and valid_max attributes.""" - - # Attribute is substituted in test_Saver__lazy. - array_lib = np - - def test_valid_range_saved(self): - cube = tests.stock.lat_lon_cube() - cube.data = cube.data.astype("int32") - - vrange = self.array_lib.array([1, 2], dtype="int32") - cube.coord(axis="x").attributes["valid_range"] = vrange - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=[]) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - self.assertArrayEqual( - ds.variables["longitude"].valid_range, vrange - ) - ds.close() - - def test_valid_min_saved(self): - cube = tests.stock.lat_lon_cube() - cube.data = cube.data.astype("int32") - - cube.coord(axis="x").attributes["valid_min"] = 1 - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=[]) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - self.assertArrayEqual(ds.variables["longitude"].valid_min, 1) - ds.close() - - def test_valid_max_saved(self): - cube = tests.stock.lat_lon_cube() - cube.data = cube.data.astype("int32") - - cube.coord(axis="x").attributes["valid_max"] = 2 - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, unlimited_dimensions=[]) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - self.assertArrayEqual(ds.variables["longitude"].valid_max, 2) - ds.close() - - -class Test_write_fill_value(tests.IrisTest): - # Attribute is substituted in test_Saver__lazy. - array_lib = np - - def _make_cube(self, dtype, masked_value=None, masked_index=None): - data = self.array_lib.arange(12, dtype=dtype).reshape(3, 4) - if masked_value is not None: - data = ma.masked_equal(data, masked_value) - if masked_index is not None: - data = self.array_lib.ma.masked_array(data) - data[masked_index] = ma.masked - lat = DimCoord(np.arange(3), "latitude", units="degrees") - lon = DimCoord(np.arange(4), "longitude", units="degrees") - return Cube( - data, - standard_name="air_temperature", - units="K", - dim_coords_and_dims=[(lat, 0), (lon, 1)], - ) - - @contextmanager - def _netCDF_var(self, cube, **kwargs): - # Get the netCDF4 Variable for a cube from a temp file - standard_name = cube.standard_name - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube, **kwargs) - ds = _thread_safe_nc.DatasetWrapper(nc_path) - (var,) = [ - var - for var in ds.variables.values() - if var.standard_name == standard_name - ] - yield var - - def test_fill_value(self): - # Test that a passed fill value is saved as a _FillValue attribute. - cube = self._make_cube(">f4") - fill_value = 12345.0 - with self._netCDF_var(cube, fill_value=fill_value) as var: - self.assertEqual(fill_value, var._FillValue) - - def test_default_fill_value(self): - # Test that if no fill value is passed then there is no _FillValue. - # attribute. - cube = self._make_cube(">f4") - with self._netCDF_var(cube) as var: - self.assertNotIn("_FillValue", var.ncattrs()) - - def test_mask_fill_value(self): - # Test that masked data saves correctly when given a fill value. - index = (1, 1) - fill_value = 12345.0 - cube = self._make_cube(">f4", masked_index=index) - with self._netCDF_var(cube, fill_value=fill_value) as var: - self.assertEqual(fill_value, var._FillValue) - self.assertTrue(var[index].mask) - - def test_mask_default_fill_value(self): - # Test that masked data saves correctly using the default fill value. - index = (1, 1) - cube = self._make_cube(">f4", masked_index=index) - with self._netCDF_var(cube) as var: - self.assertNotIn("_FillValue", var.ncattrs()) - self.assertTrue(var[index].mask) - - def test_contains_fill_value_passed(self): - # Test that a warning is raised if the data contains the fill value. - cube = self._make_cube(">f4") - fill_value = 1 - with self.assertWarnsRegex( - UserWarning, - "contains unmasked data points equal to the fill-value", - ): - with self._netCDF_var(cube, fill_value=fill_value): - pass - - def test_contains_fill_value_byte(self): - # Test that a warning is raised if the data contains the fill value - # when it is of a byte type. - cube = self._make_cube(">i1") - fill_value = 1 - with self.assertWarnsRegex( - UserWarning, - "contains unmasked data points equal to the fill-value", - ): - with self._netCDF_var(cube, fill_value=fill_value): - pass - - def test_contains_default_fill_value(self): - # Test that a warning is raised if the data contains the default fill - # value if no fill_value argument is supplied. - cube = self._make_cube(">f4") - cube.data[0, 0] = _thread_safe_nc.default_fillvals["f4"] - with self.assertWarnsRegex( - UserWarning, - "contains unmasked data points equal to the fill-value", - ): - with self._netCDF_var(cube): - pass - - def test_contains_default_fill_value_byte(self): - # Test that no warning is raised if the data contains the default fill - # value if no fill_value argument is supplied when the data is of a - # byte type. - cube = self._make_cube(">i1") - with self.assertNoWarningsRegexp(r"\(fill\|mask\)"): - with self._netCDF_var(cube): - pass - - def test_contains_masked_fill_value(self): - # Test that no warning is raised if the data contains the fill_value at - # a masked point. - fill_value = 1 - cube = self._make_cube(">f4", masked_value=fill_value) - with self.assertNoWarningsRegexp(r"\(fill\|mask\)"): - with self._netCDF_var(cube, fill_value=fill_value): - pass - - def test_masked_byte_default_fill_value(self): - # Test that a warning is raised when saving masked byte data with no - # fill value supplied. - cube = self._make_cube(">i1", masked_value=1) - with self.assertNoWarningsRegexp(r"\(fill\|mask\)"): - with self._netCDF_var(cube): - pass - - def test_masked_byte_fill_value_passed(self): - # Test that no warning is raised when saving masked byte data with a - # fill value supplied if the the data does not contain the fill_value. - fill_value = 100 - cube = self._make_cube(">i1", masked_value=2) - with self.assertNoWarningsRegexp(r"\(fill\|mask\)"): - with self._netCDF_var(cube, fill_value=fill_value): - pass - - -class Test_cf_valid_var_name(tests.IrisTest): - def test_no_replacement(self): - self.assertEqual(Saver.cf_valid_var_name("valid_Nam3"), "valid_Nam3") - - def test_special_chars(self): - self.assertEqual(Saver.cf_valid_var_name("inv?alid"), "inv_alid") - - def test_leading_underscore(self): - self.assertEqual(Saver.cf_valid_var_name("_invalid"), "var__invalid") - - def test_leading_number(self): - self.assertEqual(Saver.cf_valid_var_name("2invalid"), "var_2invalid") - - def test_leading_invalid(self): - self.assertEqual(Saver.cf_valid_var_name("?invalid"), "var__invalid") - - def test_no_hyphen(self): - # CF explicitly prohibits hyphen, even though it is fine in NetCDF. - self.assertEqual( - Saver.cf_valid_var_name("valid-netcdf"), "valid_netcdf" - ) - - -class _Common__check_attribute_compliance: - # Attribute is substituted in test_Saver__lazy. - array_lib = np - - def setUp(self): - self.container = mock.Mock(name="container", attributes={}) - self.data_dtype = np.dtype("int32") - - patch = mock.patch( - "iris.fileformats.netcdf._thread_safe_nc.DatasetWrapper" - ) - _ = patch.start() - self.addCleanup(patch.stop) - - def set_attribute(self, value): - self.container.attributes[self.attribute] = value - - def assertAttribute(self, value): - self.assertEqual( - np.asarray(self.container.attributes[self.attribute]).dtype, value - ) - - def check_attribute_compliance_call(self, value): - self.set_attribute(value) - with Saver(mock.Mock(), "NETCDF4") as saver: - saver.check_attribute_compliance(self.container, self.data_dtype) - - -class Test_check_attribute_compliance__valid_range( - _Common__check_attribute_compliance, tests.IrisTest -): - @property - def attribute(self): - return "valid_range" - - def test_valid_range_type_coerce(self): - value = self.array_lib.array([1, 2], dtype="float") - self.check_attribute_compliance_call(value) - self.assertAttribute(self.data_dtype) - - def test_valid_range_unsigned_int8_data_signed_range(self): - self.data_dtype = np.dtype("uint8") - value = self.array_lib.array([1, 2], dtype="int8") - self.check_attribute_compliance_call(value) - self.assertAttribute(value.dtype) - - def test_valid_range_cannot_coerce(self): - value = self.array_lib.array([1.5, 2.5], dtype="float64") - msg = '"valid_range" is not of a suitable value' - with self.assertRaisesRegex(ValueError, msg): - self.check_attribute_compliance_call(value) - - def test_valid_range_not_numpy_array(self): - # Ensure we handle the case when not a numpy array is provided. - self.data_dtype = np.dtype("int8") - value = [1, 2] - self.check_attribute_compliance_call(value) - self.assertAttribute(np.int64) - - -class Test_check_attribute_compliance__valid_min( - _Common__check_attribute_compliance, tests.IrisTest -): - @property - def attribute(self): - return "valid_min" - - def test_valid_range_type_coerce(self): - value = self.array_lib.array(1, dtype="float") - self.check_attribute_compliance_call(value) - self.assertAttribute(self.data_dtype) - - def test_valid_range_unsigned_int8_data_signed_range(self): - self.data_dtype = np.dtype("uint8") - value = self.array_lib.array(1, dtype="int8") - self.check_attribute_compliance_call(value) - self.assertAttribute(value.dtype) - - def test_valid_range_cannot_coerce(self): - value = self.array_lib.array(1.5, dtype="float64") - msg = '"valid_min" is not of a suitable value' - with self.assertRaisesRegex(ValueError, msg): - self.check_attribute_compliance_call(value) - - def test_valid_range_not_numpy_array(self): - # Ensure we handle the case when not a numpy array is provided. - self.data_dtype = np.dtype("int8") - value = 1 - self.check_attribute_compliance_call(value) - self.assertAttribute(np.int64) - - -class Test_check_attribute_compliance__valid_max( - _Common__check_attribute_compliance, tests.IrisTest -): - @property - def attribute(self): - return "valid_max" - - def test_valid_range_type_coerce(self): - value = self.array_lib.array(2, dtype="float") - self.check_attribute_compliance_call(value) - self.assertAttribute(self.data_dtype) - - def test_valid_range_unsigned_int8_data_signed_range(self): - self.data_dtype = np.dtype("uint8") - value = self.array_lib.array(2, dtype="int8") - self.check_attribute_compliance_call(value) - self.assertAttribute(value.dtype) - - def test_valid_range_cannot_coerce(self): - value = self.array_lib.array(2.5, dtype="float64") - msg = '"valid_max" is not of a suitable value' - with self.assertRaisesRegex(ValueError, msg): - self.check_attribute_compliance_call(value) - - def test_valid_range_not_numpy_array(self): - # Ensure we handle the case when not a numpy array is provided. - self.data_dtype = np.dtype("int8") - value = 2 - self.check_attribute_compliance_call(value) - self.assertAttribute(np.int64) - - -class Test_check_attribute_compliance__exception_handling( - _Common__check_attribute_compliance, tests.IrisTest -): - def test_valid_range_and_valid_min_valid_max_provided(self): - # Conflicting attributes should raise a suitable exception. - self.data_dtype = np.dtype("int8") - self.container.attributes["valid_range"] = [1, 2] - self.container.attributes["valid_min"] = [1] - msg = 'Both "valid_range" and "valid_min"' - with Saver(mock.Mock(), "NETCDF4") as saver: - with self.assertRaisesRegex(ValueError, msg): - saver.check_attribute_compliance( - self.container, self.data_dtype - ) - - -class Test__cf_coord_identity(tests.IrisTest): - def check_call(self, coord_name, coord_system, units, expected_units): - coord = iris.coords.DimCoord( - [30, 45], coord_name, units=units, coord_system=coord_system - ) - result = Saver._cf_coord_standardised_units(coord) - self.assertEqual(result, expected_units) - - def test_geogcs_latitude(self): - crs = iris.coord_systems.GeogCS(60, 30) - self.check_call( - "latitude", - coord_system=crs, - units="degrees", - expected_units="degrees_north", - ) - - def test_geogcs_longitude(self): - crs = iris.coord_systems.GeogCS(60, 30) - self.check_call( - "longitude", - coord_system=crs, - units="degrees", - expected_units="degrees_east", - ) - - def test_no_coord_system_latitude(self): - self.check_call( - "latitude", - coord_system=None, - units="degrees", - expected_units="degrees_north", - ) - - def test_no_coord_system_longitude(self): - self.check_call( - "longitude", - coord_system=None, - units="degrees", - expected_units="degrees_east", - ) - - def test_passthrough_units(self): - crs = iris.coord_systems.LambertConformal(0, 20) - self.check_call( - "projection_x_coordinate", - coord_system=crs, - units="km", - expected_units="km", - ) - - -class Test__create_cf_grid_mapping(tests.IrisTest): - def _cube_with_cs(self, coord_system): - """Return a simple 2D cube that uses the given coordinate system.""" - cube = stock.lat_lon_cube() - x, y = cube.coord("longitude"), cube.coord("latitude") - x.coord_system = y.coord_system = coord_system - return cube - - def _grid_mapping_variable(self, coord_system): - """ - Return a mock netCDF variable that represents the conversion - of the given coordinate system. - - """ - cube = self._cube_with_cs(coord_system) - - class NCMock(mock.Mock): - def setncattr(self, name, attr): - setattr(self, name, attr) - - # Calls the actual NetCDF saver with appropriate mocking, returning - # the grid variable that gets created. - grid_variable = NCMock(name="NetCDFVariable") - create_var_fn = mock.Mock(side_effect=[grid_variable]) - dataset = mock.Mock(variables=[], createVariable=create_var_fn) - saver = mock.Mock(spec=Saver, _coord_systems=[], _dataset=dataset) - variable = NCMock() - - # This is the method we're actually testing! - Saver._create_cf_grid_mapping(saver, cube, variable) - - self.assertEqual(create_var_fn.call_count, 1) - self.assertEqual( - variable.grid_mapping, grid_variable.grid_mapping_name - ) - return grid_variable - - def _variable_attributes(self, coord_system): - """ - Return the attributes dictionary for the grid mapping variable - that is created from the given coordinate system. - - """ - mock_grid_variable = self._grid_mapping_variable(coord_system) - - # Get the attributes defined on the mock object. - attributes = sorted(mock_grid_variable.__dict__.keys()) - attributes = [name for name in attributes if not name.startswith("_")] - attributes.remove("method_calls") - return {key: getattr(mock_grid_variable, key) for key in attributes} - - def _test(self, coord_system, expected): - actual = self._variable_attributes(coord_system) - - # To see obvious differences, check that they keys are the same. - self.assertEqual(sorted(actual.keys()), sorted(expected.keys())) - # Now check that the values are equivalent. - self.assertEqual(actual, expected) - - def test_rotated_geog_cs(self): - coord_system = RotatedGeogCS(37.5, 177.5, ellipsoid=GeogCS(6371229.0)) - expected = { - "grid_mapping_name": b"rotated_latitude_longitude", - "north_pole_grid_longitude": 0.0, - "grid_north_pole_longitude": 177.5, - "grid_north_pole_latitude": 37.5, - "longitude_of_prime_meridian": 0.0, - "earth_radius": 6371229.0, - } - self._test(coord_system, expected) - - def test_spherical_geog_cs(self): - coord_system = GeogCS(6371229.0) - expected = { - "grid_mapping_name": b"latitude_longitude", - "longitude_of_prime_meridian": 0.0, - "earth_radius": 6371229.0, - } - self._test(coord_system, expected) - - def test_elliptic_geog_cs(self): - coord_system = GeogCS(637, 600) - expected = { - "grid_mapping_name": b"latitude_longitude", - "longitude_of_prime_meridian": 0.0, - "semi_minor_axis": 600.0, - "semi_major_axis": 637.0, - } - self._test(coord_system, expected) - - def test_lambert_conformal(self): - coord_system = LambertConformal( - central_lat=44, - central_lon=2, - false_easting=-2, - false_northing=-5, - secant_latitudes=(38, 50), - ellipsoid=GeogCS(6371000), - ) - expected = { - "grid_mapping_name": b"lambert_conformal_conic", - "latitude_of_projection_origin": 44, - "longitude_of_central_meridian": 2, - "false_easting": -2, - "false_northing": -5, - "standard_parallel": (38, 50), - "earth_radius": 6371000, - "longitude_of_prime_meridian": 0, - } - self._test(coord_system, expected) - - def test_laea_cs(self): - coord_system = LambertAzimuthalEqualArea( - latitude_of_projection_origin=52, - longitude_of_projection_origin=10, - false_easting=100, - false_northing=200, - ellipsoid=GeogCS(6377563.396, 6356256.909), - ) - expected = { - "grid_mapping_name": b"lambert_azimuthal_equal_area", - "latitude_of_projection_origin": 52, - "longitude_of_projection_origin": 10, - "false_easting": 100, - "false_northing": 200, - "semi_major_axis": 6377563.396, - "semi_minor_axis": 6356256.909, - "longitude_of_prime_meridian": 0, - } - self._test(coord_system, expected) - - def test_aea_cs(self): - coord_system = AlbersEqualArea( - latitude_of_projection_origin=52, - longitude_of_central_meridian=10, - false_easting=100, - false_northing=200, - standard_parallels=(38, 50), - ellipsoid=GeogCS(6377563.396, 6356256.909), - ) - expected = { - "grid_mapping_name": b"albers_conical_equal_area", - "latitude_of_projection_origin": 52, - "longitude_of_central_meridian": 10, - "false_easting": 100, - "false_northing": 200, - "standard_parallel": (38, 50), - "semi_major_axis": 6377563.396, - "semi_minor_axis": 6356256.909, - "longitude_of_prime_meridian": 0, - } - self._test(coord_system, expected) - - def test_vp_cs(self): - latitude_of_projection_origin = 1.0 - longitude_of_projection_origin = 2.0 - perspective_point_height = 2000000.0 - false_easting = 100.0 - false_northing = 200.0 - - semi_major_axis = 6377563.396 - semi_minor_axis = 6356256.909 - ellipsoid = GeogCS(semi_major_axis, semi_minor_axis) - - coord_system = VerticalPerspective( - latitude_of_projection_origin=latitude_of_projection_origin, - longitude_of_projection_origin=longitude_of_projection_origin, - perspective_point_height=perspective_point_height, - false_easting=false_easting, - false_northing=false_northing, - ellipsoid=ellipsoid, - ) - expected = { - "grid_mapping_name": b"vertical_perspective", - "latitude_of_projection_origin": latitude_of_projection_origin, - "longitude_of_projection_origin": longitude_of_projection_origin, - "perspective_point_height": perspective_point_height, - "false_easting": false_easting, - "false_northing": false_northing, - "semi_major_axis": semi_major_axis, - "semi_minor_axis": semi_minor_axis, - "longitude_of_prime_meridian": 0, - } - self._test(coord_system, expected) - - def test_geo_cs(self): - latitude_of_projection_origin = 0.0 - longitude_of_projection_origin = 2.0 - perspective_point_height = 2000000.0 - sweep_angle_axis = "x" - false_easting = 100.0 - false_northing = 200.0 - - semi_major_axis = 6377563.396 - semi_minor_axis = 6356256.909 - ellipsoid = GeogCS(semi_major_axis, semi_minor_axis) - - coord_system = Geostationary( - latitude_of_projection_origin=latitude_of_projection_origin, - longitude_of_projection_origin=longitude_of_projection_origin, - perspective_point_height=perspective_point_height, - sweep_angle_axis=sweep_angle_axis, - false_easting=false_easting, - false_northing=false_northing, - ellipsoid=ellipsoid, - ) - expected = { - "grid_mapping_name": b"geostationary", - "latitude_of_projection_origin": latitude_of_projection_origin, - "longitude_of_projection_origin": longitude_of_projection_origin, - "perspective_point_height": perspective_point_height, - "sweep_angle_axis": sweep_angle_axis, - "false_easting": false_easting, - "false_northing": false_northing, - "semi_major_axis": semi_major_axis, - "semi_minor_axis": semi_minor_axis, - "longitude_of_prime_meridian": 0, - } - self._test(coord_system, expected) - - -class Test__create_cf_cell_measure_variable(tests.IrisTest): - # Saving of masked data is disallowed. - - # Attribute is substituted in test_Saver__lazy. - array_lib = np - - def setUp(self): - self.cube = stock.lat_lon_cube() - self.names_map = ["latitude", "longitude"] - masked_array = self.array_lib.ma.masked_array( - [0, 1, 2], mask=[True, False, True] - ) - self.cm = iris.coords.CellMeasure(masked_array, var_name="cell_area") - self.cube.add_cell_measure(self.cm, data_dims=0) - self.exp_emsg = "Cell measures with missing data are not supported." - - def test_masked_data__insitu(self): - # Test that the error is raised in the right place. - with self.temp_filename(".nc") as nc_path: - saver = Saver(nc_path, "NETCDF4") - with self.assertRaisesRegex(ValueError, self.exp_emsg): - saver._create_generic_cf_array_var( - self.cube, self.names_map, self.cm - ) - - def test_masked_data__save_pipeline(self): - # Test that the right error is raised by the saver pipeline. - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - with self.assertRaisesRegex(ValueError, self.exp_emsg): - saver.write(self.cube) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__lazy.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__lazy.py deleted file mode 100644 index eab09b9e4f..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__lazy.py +++ /dev/null @@ -1,128 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Mirror of :mod:`iris.tests.unit.fileformats.netcdf.test_Saver`, but with lazy arrays.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from dask import array as da - -from iris.coords import AuxCoord -from iris.fileformats.netcdf import Saver -from iris.tests import stock -from iris.tests.unit.fileformats.netcdf import test_Saver - - -class LazyMixin(tests.IrisTest): - array_lib = da - - def result_path(self, basename=None, ext=""): - # Precisely mirroring the tests in test_Saver, so use those CDL's. - original = super().result_path(basename, ext) - return original.replace("Saver__lazy", "Saver") - - -class Test_write(LazyMixin, test_Saver.Test_write): - pass - - -class Test__create_cf_bounds(test_Saver.Test__create_cf_bounds): - @staticmethod - def climatology_3d(): - cube = stock.climatology_3d() - aux_coord = AuxCoord.from_coord(cube.coord("time")) - lazy_coord = aux_coord.copy( - aux_coord.lazy_points(), aux_coord.lazy_bounds() - ) - cube.replace_coord(lazy_coord) - return cube - - -class Test_write__valid_x_cube_attributes( - LazyMixin, test_Saver.Test_write__valid_x_cube_attributes -): - pass - - -class Test_write__valid_x_coord_attributes( - LazyMixin, test_Saver.Test_write__valid_x_coord_attributes -): - pass - - -class Test_write_fill_value(LazyMixin, test_Saver.Test_write_fill_value): - pass - - -class Test_check_attribute_compliance__valid_range( - LazyMixin, test_Saver.Test_check_attribute_compliance__valid_range -): - pass - - -class Test_check_attribute_compliance__valid_min( - LazyMixin, test_Saver.Test_check_attribute_compliance__valid_min -): - pass - - -class Test_check_attribute_compliance__valid_max( - LazyMixin, test_Saver.Test_check_attribute_compliance__valid_max -): - pass - - -class Test_check_attribute_compliance__exception_handling( - LazyMixin, test_Saver.Test_check_attribute_compliance__exception_handling -): - pass - - -class Test__create_cf_cell_measure_variable( - LazyMixin, test_Saver.Test__create_cf_cell_measure_variable -): - pass - - -class TestStreamed(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_2d() - self.store_watch = self.patch("dask.array.store") - - def save_common(self, cube_to_save): - with self.temp_filename(".nc") as nc_path: - with Saver(nc_path, "NETCDF4") as saver: - saver.write(cube_to_save) - - def test_realised_not_streamed(self): - self.save_common(self.cube) - self.assertFalse(self.store_watch.called) - - def test_lazy_streamed_data(self): - self.cube.data = self.cube.lazy_data() - self.save_common(self.cube) - self.assertTrue(self.store_watch.called) - - def test_lazy_streamed_coord(self): - aux_coord = AuxCoord.from_coord(self.cube.coords()[0]) - lazy_coord = aux_coord.copy( - aux_coord.lazy_points(), aux_coord.lazy_bounds() - ) - self.cube.replace_coord(lazy_coord) - self.save_common(self.cube) - self.assertTrue(self.store_watch.called) - - def test_lazy_streamed_bounds(self): - aux_coord = AuxCoord.from_coord(self.cube.coords()[0]) - lazy_coord = aux_coord.copy(aux_coord.points, aux_coord.lazy_bounds()) - self.cube.replace_coord(lazy_coord) - self.save_common(self.cube) - self.assertTrue(self.store_watch.called) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py b/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py deleted file mode 100644 index 323b498d9c..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/test_Saver__ugrid.py +++ /dev/null @@ -1,1280 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :class:`iris.fileformats.netcdf.Saver` class. - -WHEN MODIFYING THIS MODULE, CHECK IF ANY CORRESPONDING CHANGES ARE NEEDED IN -:mod:`iris.tests.unit.fileformats.netcdf.test_Saver__lazy.` - -""" -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from pathlib import Path -import shutil -import tempfile - -import numpy as np - -from iris import save -from iris.coords import AuxCoord -from iris.cube import Cube, CubeList -from iris.experimental.ugrid.mesh import Connectivity, Mesh -from iris.experimental.ugrid.save import save_mesh -from iris.fileformats.netcdf import _thread_safe_nc -from iris.tests.stock import realistic_4d - -XY_LOCS = ("x", "y") -XY_NAMES = ("longitude", "latitude") - - -def build_mesh( - n_nodes=2, - n_faces=0, - n_edges=0, - nodecoord_xyargs=None, - edgecoord_xyargs=None, - facecoord_xyargs=None, - conn_role_kwargs=None, # mapping {connectivity-role: connectivity-kwargs} - mesh_kwargs=None, -): - """ - Make a test mesh. - - Mesh has faces edges, face-coords and edge-coords, numbers of which can be - controlled. - - Args: - - * n_nodes, n_faces, n_edges (int): - Basic dimensions of mesh components. Zero means no such location. - * nodecoord_xyargs, edgecoord_xyargs, facecoord_xyargs (pair of dict): - Pairs (x,y) of settings kwargs, applied after initial creation the - relevant location coordinates. - * conn_role_kwargs (dict of string:dict): - Mapping from cf_role name to settings kwargs for connectivities, - applied after initially creating them. - * mesh_kwargs (dict): - Dictionary of key settings to apply to the Mesh, after creating it. - - """ - - def applyargs(coord, kwargs): - if kwargs: - for key, val in kwargs.items(): - # kwargs is a dict - setattr(coord, key, val) - - def apply_xyargs(coords, xyargs): - if xyargs: - for coord, kwargs in zip(coords, xyargs): - # coords and xyargs both iterables : implicitly=(x,y) - applyargs(coord, kwargs) - - node_coords = [ - AuxCoord(np.arange(n_nodes), standard_name=name) for name in XY_NAMES - ] - apply_xyargs(node_coords, nodecoord_xyargs) - - connectivities = {} - edge_coords = [] - face_coords = [] - topology_dimension = 0 - if n_edges: - topology_dimension = 1 - connectivities["edge_node_connectivity"] = Connectivity( - np.zeros((n_edges, 2), np.int32), cf_role="edge_node_connectivity" - ) - edge_coords = [ - AuxCoord(np.arange(n_edges), standard_name=name) - for name in XY_NAMES - ] - apply_xyargs(edge_coords, edgecoord_xyargs) - - if n_faces: - topology_dimension = 2 - connectivities["face_node_connectivity"] = Connectivity( - np.zeros((n_faces, 4), np.int32), cf_role="face_node_connectivity" - ) - face_coords = [ - AuxCoord(np.arange(n_faces), standard_name=name) - for name in XY_NAMES - ] - apply_xyargs(face_coords, facecoord_xyargs) - - mesh_dims = {"node": n_nodes, "edge": n_edges, "face": n_faces} - - if conn_role_kwargs: - for role, kwargs in conn_role_kwargs.items(): - if role in connectivities: - conn = connectivities[role] - else: - loc_from, loc_to, _ = role.split("_") - dims = [mesh_dims[loc] for loc in (loc_from, loc_to)] - conn = Connectivity( - np.zeros(dims, dtype=np.int32), cf_role=role - ) - connectivities[role] = conn - applyargs(conn, kwargs) - - mesh = Mesh( - topology_dimension=topology_dimension, - node_coords_and_axes=zip(node_coords, XY_LOCS), - edge_coords_and_axes=zip(edge_coords, XY_LOCS), - face_coords_and_axes=zip(face_coords, XY_LOCS), - connectivities=connectivities.values(), - ) - applyargs(mesh, mesh_kwargs) - - return mesh - - -def make_mesh(basic=True, **kwargs): - """ - Create a test mesh, with some built-in 'standard' settings. - - Kwargs: - - * basic (bool): - If true (default), create with 'standard' set of test properties. - * kwargs (dict): - Additional kwargs, passed through to 'build_mesh'. - Items here override the 'standard' settings. - - """ - if basic: - # Use some helpful non-minimal settings as our 'basic' mesh. - use_kwargs = dict( - n_nodes=5, - n_faces=2, - nodecoord_xyargs=tuple( - dict(var_name=f"node_{loc}") for loc in XY_LOCS - ), - facecoord_xyargs=tuple( - dict(var_name=f"face_{loc}") for loc in XY_LOCS - ), - mesh_kwargs=dict( - var_name="Mesh2d", - node_dimension="Mesh2d_nodes", - face_dimension="Mesh2d_faces", - ), - ) - use_kwargs.update(kwargs) - else: - use_kwargs = kwargs - - mesh = build_mesh(**use_kwargs) - return mesh - - -def mesh_location_size(mesh, location): - """Get the length of a location-dimension from a mesh.""" - if location == "node": - # Use a node coordinate (which always exists). - node_coord = mesh.node_coords[0] - result = node_coord.shape[0] - else: - # Use a _node_connectivity, if any. - conn_name = f"{location}_node_connectivity" - conn = getattr(mesh, conn_name, None) - if conn is None: - result = 0 - else: - result = conn.shape[conn.location_axis] - return result - - -# A simple "standard" test mesh for multiple uses, which we can use for cubes -# that *share* a mesh (since we don't support mesh equality). -# However, we defer creating this until needed, as it can cause an import loop. -_DEFAULT_MESH = None - - -def default_mesh(): - """Return the unique default mesh, creating it if needed.""" - global _DEFAULT_MESH - if _DEFAULT_MESH is None: - _DEFAULT_MESH = make_mesh() - return _DEFAULT_MESH - - -def make_cube(mesh=None, location="face", **kwargs): - """ - Create a test cube, based on a given mesh + location. - - Kwargs: - - * mesh (:class:`iris.experimental.ugrid.mesh.Mesh` or None): - If None, use 'default_mesh()' - * location (string): - Which mesh element to map the cube to. - * kwargs (dict): - Additional property settings to apply to the cube (after creation). - - """ - if mesh is None: - mesh = default_mesh() - dim_length = mesh_location_size(mesh, location) - cube = Cube(np.zeros(dim_length, np.float32)) - for meshco in mesh.to_MeshCoords(location): - cube.add_aux_coord(meshco, (0,)) - for key, val in kwargs.items(): - setattr(cube, key, val) - return cube - - -def add_height_dim(cube): - """Add an extra initial 'height' dimension onto a cube.""" - cube = cube.copy() # Avoid trashing the input cube. - cube.add_aux_coord(AuxCoord([0.0], standard_name="height", units="m")) - # Make three copies with different heights - cubes = [cube.copy() for _ in range(3)] - for i_cube, cube in enumerate(cubes): - cube.coord("height").points = [i_cube] - # Merge to create an additional 'height' dimension. - cube = CubeList(cubes).merge_cube() - return cube - - -# Special key-string for storing the dimensions of a variable -_VAR_DIMS = "" - - -def scan_dataset(filepath): - """ - Snapshot a netcdf dataset (the key metadata). - - Returns: - dimsdict, varsdict - * dimsdict (dict): - A map of dimension-name: length. - * varsdict (dict): - A map of each variable's properties, {var_name: propsdict} - Each propsdict is {attribute-name: value} over the var's ncattrs(). - Each propsdict ALSO contains a [_VAR_DIMS] entry listing the - variable's dims. - - """ - ds = _thread_safe_nc.DatasetWrapper(filepath) - # dims dict is {name: len} - dimsdict = {name: dim.size for name, dim in ds.dimensions.items()} - # vars dict is {name: {attr:val}} - varsdict = {} - for name, var in ds.variables.items(): - varsdict[name] = {prop: getattr(var, prop) for prop in var.ncattrs()} - varsdict[name][_VAR_DIMS] = list(var.dimensions) - ds.close() - return dimsdict, varsdict - - -def vars_w_props(varsdict, **kwargs): - """ - Subset a vars dict, {name:props}, returning only those where each - =, defined by the given keywords. - Except that '="*"' means that '' merely _exists_, with any value. - - """ - - def check_attrs_match(attrs): - result = True - for key, val in kwargs.items(): - result = key in attrs - if result: - # val='*'' for a simple existence check - result = (val == "*") or attrs[key] == val - if not result: - break - return result - - varsdict = { - name: attrs - for name, attrs in varsdict.items() - if check_attrs_match(attrs) - } - return varsdict - - -def vars_w_dims(varsdict, dim_names): - """Subset a vars dict, returning those which map all the specified dims.""" - varsdict = { - name: propsdict - for name, propsdict in varsdict.items() - if all(dim in propsdict[_VAR_DIMS] for dim in dim_names) - } - return varsdict - - -def vars_meshnames(vars): - """Return the names of all the mesh variables (found by cf_role).""" - return list(vars_w_props(vars, cf_role="mesh_topology").keys()) - - -def vars_meshdim(vars, location, mesh_name=None): - """ - Extract a dim-name for a given element location. - - Args: - * vars (varsdict): - file varsdict, as returned from 'snapshot_dataset'. - * location (string): - a mesh location : 'node' / 'edge' / 'face' - * mesh_name (string or None): - If given, identifies the mesh var. - Otherwise, find a unique mesh var (i.e. there must be exactly 1). - - Returns: - dim_name (string) - The dim-name of the mesh dim for the given location. - - TODO: relies on the element having coordinates, which in future will not - always be the case. This can be fixed - - """ - if mesh_name is None: - # Find "the" meshvar -- assuming there is just one. - (mesh_name,) = vars_meshnames(vars) - mesh_props = vars[mesh_name] - loc_coords = mesh_props[f"{location}_coordinates"].split(" ") - (single_location_dim,) = vars[loc_coords[0]][_VAR_DIMS] - return single_location_dim - - -class TestSaveUgrid__cube(tests.IrisTest): - """Test for saving cubes which have meshes.""" - - @classmethod - def setUpClass(cls): - cls.temp_dir = Path(tempfile.mkdtemp()) - - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.temp_dir) - - def check_save_cubes(self, cube_or_cubes): - """ - Write cubes to a new file in the common temporary directory. - - Use a name unique to this testcase, to avoid any clashes. - - """ - # use 'result_path' to name the file after the test function - tempfile_path = self.result_path(ext=".nc") - # Create a file of that name, but discard the result path and put it - # in the common temporary directory. - tempfile_path = self.temp_dir / Path(tempfile_path).name - - # Save data to the file. - save(cube_or_cubes, tempfile_path) - - return tempfile_path - - def test_basic_mesh(self): - # Save a small mesh example and check aspects of the resulting file. - cube = make_cube() # A simple face-mapped data example. - - # Save and snapshot the result - tempfile_path = self.check_save_cubes(cube) - dims, vars = scan_dataset(tempfile_path) - - # There is exactly 1 mesh var. - (mesh_name,) = vars_meshnames(vars) - - # There is exactly 1 mesh-linked (data)var - data_vars = vars_w_props(vars, mesh="*") - ((a_name, a_props),) = data_vars.items() - mesh_props = vars[mesh_name] - - # The mesh var links to the mesh, with location 'faces' - self.assertEqual(a_name, "unknown") - self.assertEqual(a_props["mesh"], mesh_name) - self.assertEqual(a_props["location"], "face") - - # There are 2 face coords == those listed in the mesh - face_coords = mesh_props["face_coordinates"].split(" ") - self.assertEqual(len(face_coords), 2) - - # The face coords should both map that single dim. - face_dim = vars_meshdim(vars, "face") - self.assertTrue( - all(vars[co][_VAR_DIMS] == [face_dim] for co in face_coords) - ) - - # The dims of the datavar also == [] - self.assertEqual(a_props[_VAR_DIMS], [face_dim]) - - # There are 2 node coordinates == those listed in the mesh. - node_coords = mesh_props["node_coordinates"].split(" ") - self.assertEqual(len(node_coords), 2) - # These are the *only* ones using the 'nodes' dimension. - node_dim = vars_meshdim(vars, "node") - self.assertEqual( - sorted(node_coords), sorted(vars_w_dims(vars, [node_dim]).keys()) - ) - - # There are no edges. - self.assertNotIn("edge_node_connectivity", mesh_props) - self.assertEqual( - len(vars_w_props(vars, cf_role="edge_node_connectivity")), 0 - ) - - # The dims are precisely (nodes, faces, nodes-per-face), in that order. - self.assertEqual( - list(dims.keys()), - ["Mesh2d_nodes", "Mesh2d_faces", "Mesh2d_face_N_nodes"], - ) - - # The variables are exactly (mesh, 2*node-coords, 2*face-coords, - # face-nodes, data) -- in that order - self.assertEqual( - list(vars.keys()), - [ - "Mesh2d", - "node_x", - "node_y", - "face_x", - "face_y", - "mesh2d_faces", - "unknown", - ], - ) - - # For completeness, also check against a full CDL snapshot - self.assertCDL(tempfile_path) - - def test_multi_cubes_common_mesh(self): - cube1 = make_cube(var_name="a") - cube2 = make_cube(var_name="b") - - # Save and snapshot the result - tempfile_path = self.check_save_cubes([cube1, cube2]) - dims, vars = scan_dataset(tempfile_path) - - # there is exactly 1 mesh in the file - (mesh_name,) = vars_meshnames(vars) - - # both the main variables reference the same mesh, and 'face' location - v_a, v_b = vars["a"], vars["b"] - self.assertEqual(v_a["mesh"], mesh_name) - self.assertEqual(v_a["location"], "face") - self.assertEqual(v_b["mesh"], mesh_name) - self.assertEqual(v_b["location"], "face") - - def test_multi_cubes_different_locations(self): - cube1 = make_cube(var_name="a", location="face") - cube2 = make_cube(var_name="b", location="node") - - # Save and snapshot the result - tempfile_path = self.check_save_cubes([cube1, cube2]) - dims, vars = scan_dataset(tempfile_path) - - # there is exactly 1 mesh in the file - (mesh_name,) = vars_meshnames(vars) - - # the main variables reference the same mesh at different locations - v_a, v_b = vars["a"], vars["b"] - self.assertEqual(v_a["mesh"], mesh_name) - self.assertEqual(v_a["location"], "face") - self.assertEqual(v_b["mesh"], mesh_name) - self.assertEqual(v_b["location"], "node") - - # the main variables map the face and node dimensions - face_dim = vars_meshdim(vars, "face") - node_dim = vars_meshdim(vars, "node") - self.assertEqual(v_a[_VAR_DIMS], [face_dim]) - self.assertEqual(v_b[_VAR_DIMS], [node_dim]) - - def test_multi_cubes_equal_meshes(self): - # Make 2 identical meshes - # NOTE: *can't* name these explicitly, as it stops them being identical. - mesh1 = make_mesh() - mesh2 = make_mesh() - cube1 = make_cube(var_name="a", mesh=mesh1) - cube2 = make_cube(var_name="b", mesh=mesh2) - - # Save and snapshot the result - tempfile_path = self.check_save_cubes([cube1, cube2]) - dims, vars = scan_dataset(tempfile_path) - - # there is exactly 1 mesh in the file - mesh_names = vars_meshnames(vars) - self.assertEqual(sorted(mesh_names), ["Mesh2d"]) - - # same dimensions - self.assertEqual( - vars_meshdim(vars, "node", mesh_name="Mesh2d"), "Mesh2d_nodes" - ) - self.assertEqual( - vars_meshdim(vars, "face", mesh_name="Mesh2d"), "Mesh2d_faces" - ) - - # there are exactly two data-variables with a 'mesh' property - mesh_datavars = vars_w_props(vars, mesh="*") - self.assertEqual(["a", "b"], list(mesh_datavars)) - - # the data variables reference the same mesh - a_props, b_props = vars["a"], vars["b"] - for props in a_props, b_props: - self.assertEqual(props["mesh"], "Mesh2d") - self.assertEqual(props["location"], "face") - - # the data variables map the appropriate node dimensions - self.assertEqual(a_props[_VAR_DIMS], ["Mesh2d_faces"]) - self.assertEqual(b_props[_VAR_DIMS], ["Mesh2d_faces_0"]) - - def test_multi_cubes_different_mesh(self): - # Check that we can correctly distinguish 2 different meshes. - cube1 = make_cube(var_name="a") - cube2 = make_cube(var_name="b", mesh=make_mesh(n_faces=4)) - - # Save and snapshot the result - tempfile_path = self.check_save_cubes([cube1, cube2]) - dims, vars = scan_dataset(tempfile_path) - - # there are 2 meshes in the file - mesh_names = vars_meshnames(vars) - self.assertEqual(len(mesh_names), 2) - - # there are two (data)variables with a 'mesh' property - mesh_datavars = vars_w_props(vars, mesh="*") - self.assertEqual(2, len(mesh_datavars)) - self.assertEqual(["a", "b"], sorted(mesh_datavars.keys())) - - # the main variables reference the correct meshes, and 'face' location - a_props, b_props = vars["a"], vars["b"] - mesh_a, loc_a = a_props["mesh"], a_props["location"] - mesh_b, loc_b = b_props["mesh"], b_props["location"] - self.assertNotEqual(mesh_a, mesh_b) - self.assertEqual(loc_a, "face") - self.assertEqual(loc_b, "face") - - def test_nonmesh_dim(self): - # Check where the data variable has a 'normal' dim and a mesh dim. - cube = make_cube() - cube = add_height_dim(cube) - - # Save and snapshot the result - tempfile_path = self.check_save_cubes(cube) - dims, vars = scan_dataset(tempfile_path) - - # have just 1 mesh, including a face and node coordinates. - (mesh_name,) = vars_meshnames(vars) - # Check we have faces, and identify the faces dim - face_dim = vars_meshdim(vars, "face", mesh_name) - # Also just check we *have* a recognisable node-coordinate - vars_meshdim(vars, "node", mesh_name) - - # have just 1 data-variable - ((data_name, data_props),) = vars_w_props(vars, mesh="*").items() - - # data maps to the height + mesh dims - self.assertEqual(data_props[_VAR_DIMS], ["height", face_dim]) - self.assertEqual(data_props["mesh"], mesh_name) - self.assertEqual(data_props["location"], "face") - - @tests.skip_data - def test_nonmesh_hybrid_dim(self): - # Check a case with a hybrid non-mesh dimension - cube = realistic_4d() - # Strip off the time and longitude dims, to make it simpler. - cube = cube[0, ..., 0] - # Remove all the unwanted coords (also loses the coord-system) - lose_coords = ( - "time", - "forecast_period", - "grid_longitude", - "grid_latitude", - ) - for coord in lose_coords: - cube.remove_coord(coord) - - # Add a mesh on the remaining (now anonymous) horizontal dimension. - i_horizontal_dim = len(cube.shape) - 1 - n_places = cube.shape[i_horizontal_dim] - mesh = make_mesh( - n_faces=n_places, - n_nodes=30, # arbitrary + unrealistic, but doesn't actually matter - ) - # Attach the mesh by adding MeshCoords - for coord in mesh.to_MeshCoords("face"): - cube.add_aux_coord(coord, (i_horizontal_dim,)) - - # Save and snapshot the result - tempfile_path = self.check_save_cubes(cube) - dims, vars = scan_dataset(tempfile_path) - - # have just 1 mesh, including face and node coordinates. - (mesh_name,) = vars_meshnames(vars) - face_dim = vars_meshdim(vars, "face", mesh_name) - _ = vars_meshdim(vars, "node", mesh_name) - - # have hybrid vertical dimension, with all the usual term variables. - self.assertIn("model_level_number", dims) - vert_vars = list(vars_w_dims(vars, ["model_level_number"]).keys()) - # The list of file variables mapping the vertical dimension: - # = the data-var, plus all the height terms - self.assertEqual( - vert_vars, - [ - "air_potential_temperature", - "model_level_number", - "level_height", - "level_height_bnds", - "sigma", - "sigma_bnds", - ], - ) - - # have just 1 data-variable, which maps to hybrid-height and mesh dims - ((data_name, data_props),) = vars_w_props(vars, mesh="*").items() - self.assertEqual( - data_props[_VAR_DIMS], ["model_level_number", face_dim] - ) - self.assertEqual(data_props["mesh"], mesh_name) - self.assertEqual(data_props["location"], "face") - - def test_alternate_cube_dim_order(self): - # A cube transposed from the 'usual' order - # Should work much the same as the "basic" case. - cube_1 = make_cube(var_name="a") - cube_1 = add_height_dim(cube_1) - - cube_2 = cube_1.copy() - cube_2.var_name = "b" - cube_2.transpose() - - # Save and snapshot the result - tempfile_path = self.check_save_cubes([cube_1, cube_2]) - dims, vars = scan_dataset(tempfile_path) - - # There is only 1 mesh - (mesh_name,) = vars_meshnames(vars) - - # both variables reference the same mesh - v_a, v_b = vars["a"], vars["b"] - self.assertEqual(v_a["mesh"], mesh_name) - self.assertEqual(v_a["location"], "face") - self.assertEqual(v_b["mesh"], mesh_name) - self.assertEqual(v_b["location"], "face") - - # Check the var dimensions - self.assertEqual(v_a[_VAR_DIMS], ["height", "Mesh2d_faces"]) - self.assertEqual(v_b[_VAR_DIMS], ["Mesh2d_faces", "height"]) - - -class TestSaveUgrid__mesh(tests.IrisTest): - """Tests for saving meshes to a file.""" - - @classmethod - def setUpClass(cls): - cls.temp_dir = Path(tempfile.mkdtemp()) - - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.temp_dir) - - def check_save_mesh(self, mesh): - """ - Write a mesh to a new file in the common temporary directory. - - Use a name unique to this testcase, to avoid any clashes. - - """ - # use 'result_path' to name the file after the test function - tempfile_path = self.result_path(ext=".nc") - # Create a file of that name, but discard the result path and put it - # in the common temporary directory. - tempfile_path = self.temp_dir / Path(tempfile_path).name - - # Save data to the file. - save_mesh(mesh, tempfile_path) - - return tempfile_path - - def test_connectivity_dim_order(self): - """ - Test a mesh with some connectivities in the 'other' order. - - This should also create a property with the dimension name. - - """ - # Make a mesh with both faces *and* some edges - mesh = make_mesh(n_edges=7) - # Get the face-node and edge-node connectivities - face_nodes_conn = mesh.face_node_connectivity - edge_nodes_conn = mesh.edge_node_connectivity - # Transpose them : N.B. this sets location_axis=1, as it should be. - nodesfirst_faces_conn = face_nodes_conn.transpose() - nodesfirst_edges_conn = edge_nodes_conn.transpose() - # Make a new mesh with both face and edge connectivities 'transposed'. - mesh2 = Mesh( - topology_dimension=mesh.topology_dimension, - node_coords_and_axes=zip(mesh.node_coords, XY_LOCS), - face_coords_and_axes=zip(mesh.face_coords, XY_LOCS), - connectivities=[nodesfirst_faces_conn, nodesfirst_edges_conn], - ) - - # Save and snapshot the result - tempfile_path = self.check_save_mesh(mesh2) - dims, vars = scan_dataset(tempfile_path) - - # Check shape and dimensions of the associated connectivity variables. - (mesh_name,) = vars_meshnames(vars) - mesh_props = vars[mesh_name] - faceconn_name = mesh_props["face_node_connectivity"] - edgeconn_name = mesh_props["edge_node_connectivity"] - faceconn_props = vars[faceconn_name] - edgeconn_props = vars[edgeconn_name] - self.assertEqual( - faceconn_props[_VAR_DIMS], ["Mesh_2d_face_N_nodes", "Mesh2d_face"] - ) - self.assertEqual( - edgeconn_props[_VAR_DIMS], ["Mesh_2d_edge_N_nodes", "Mesh2d_edge"] - ) - - # Check the dimension lengths are also as expected - self.assertEqual(dims["Mesh2d_face"], 2) - self.assertEqual(dims["Mesh_2d_face_N_nodes"], 4) - self.assertEqual(dims["Mesh2d_edge"], 7) - self.assertEqual(dims["Mesh_2d_edge_N_nodes"], 2) - - # the mesh has extra location-dimension properties - self.assertEqual(mesh_props["face_dimension"], "Mesh2d_face") - self.assertEqual(mesh_props["edge_dimension"], "Mesh2d_edge") - - def test_connectivity_start_index(self): - """Test a mesh where some connectivities have start_index = 1.""" - # Make a mesh with both faces *and* some edges - mesh = make_mesh(n_edges=7) - # Get the face-node and edge-node connectivities - face_nodes_conn = mesh.face_node_connectivity - edge_nodes_conn = mesh.edge_node_connectivity - edge_nodes_conn2 = Connectivity( - indices=edge_nodes_conn.indices + 1, - cf_role=edge_nodes_conn.cf_role, - var_name="edges_x_2", - start_index=1, - ) - # Make a new mesh with altered connectivities. - mesh2 = Mesh( - topology_dimension=mesh.topology_dimension, - node_coords_and_axes=zip(mesh.node_coords, XY_LOCS), - face_coords_and_axes=zip(mesh.face_coords, XY_LOCS), - connectivities=[face_nodes_conn, edge_nodes_conn2], - ) - - # Save and snapshot the result - tempfile_path = self.check_save_mesh(mesh2) - dims, vars = scan_dataset(tempfile_path) - - # Check shape and dimensions of the associated connectivity variables. - (mesh_name,) = vars_meshnames(vars) - mesh_props = vars[mesh_name] - faceconn_name = mesh_props["face_node_connectivity"] - edgeconn_name = mesh_props["edge_node_connectivity"] - faceconn_props = vars[faceconn_name] - edgeconn_props = vars[edgeconn_name] - self.assertEqual(faceconn_props["start_index"], 0) - self.assertEqual(edgeconn_props["start_index"], 1) - - def test_nonuniform_connectivity(self): - # Check handling of connectivities with missing points. - n_faces = 7 - mesh = make_mesh(n_faces=n_faces) - - # In this case, add on a partial face-face connectivity. - # construct a vaguely plausible face-face index array - indices = np.ma.arange(n_faces * 4).reshape((7, 4)) - indices = indices % 7 - # make some missing points -- i.e. not all faces have 4 neighbours - indices[(2, (2, 3))] = np.ma.masked - indices[(3, (0, 2))] = np.ma.masked - indices[6, :] = np.ma.masked - - conn = Connectivity( - indices, - cf_role="face_face_connectivity", - ) - mesh.add_connectivities(conn) - - # Save and snapshot the result - tempfile_path = self.check_save_mesh(mesh) - dims, vars = scan_dataset(tempfile_path) - - # Check that the mesh saved with the additional connectivity - (mesh_name,) = vars_meshnames(vars) - mesh_props = vars[mesh_name] - self.assertIn("face_face_connectivity", mesh_props) - ff_conn_name = mesh_props["face_face_connectivity"] - - # check that the connectivity has the corrects dims and fill-property - ff_props = vars[ff_conn_name] - self.assertEqual( - ff_props[_VAR_DIMS], ["Mesh2d_faces", "Mesh2d_face_N_faces"] - ) - self.assertIn("_FillValue", ff_props) - self.assertEqual(ff_props["_FillValue"], -1) - - # Check that a 'normal' connectivity does *not* have a _FillValue - fn_conn_name = mesh_props["face_node_connectivity"] - fn_props = vars[fn_conn_name] - self.assertNotIn("_FillValue", fn_props) - - # For what it's worth, *also* check the actual data array in the file - ds = _thread_safe_nc.DatasetWrapper(tempfile_path) - conn_var = ds.variables[ff_conn_name] - data = conn_var[:] - ds.close() - self.assertIsInstance(data, np.ma.MaskedArray) - self.assertEqual(data.fill_value, -1) - # Compare raw values stored to indices, but with -1 at missing points - raw_data = data.data - filled_indices = indices.filled(-1) - self.assertArrayEqual(raw_data, filled_indices) - - def test_one_dimensional(self): - # Test a mesh with edges only. - mesh = make_mesh( - n_edges=5, n_faces=0, mesh_kwargs={"var_name": "Mesh1d"} - ) - - # Save and snapshot the result - tempfile_path = self.check_save_mesh(mesh) - dims, vars = scan_dataset(tempfile_path) - - # there is a single mesh-var - (mesh_name,) = vars_meshnames(vars) - - # the dims include edges but not faces - self.assertEqual( - list(dims.keys()), - ["Mesh1d_node", "Mesh1d_edge", "Mesh1d_edge_N_nodes"], - ) - self.assertEqual(vars_meshdim(vars, "node"), "Mesh1d_node") - self.assertEqual(vars_meshdim(vars, "edge"), "Mesh1d_edge") - - # check suitable mesh properties - self.assertEqual(mesh_name, "Mesh1d") - mesh_props = vars[mesh_name] - self.assertEqual(mesh_props["topology_dimension"], 1) - self.assertIn("edge_node_connectivity", mesh_props) - self.assertNotIn("face_node_connectivity", mesh_props) - - def test_location_coord_units(self): - # Check that units on mesh locations are handled correctly. - # NOTE: at present, the Mesh class cannot handle coordinates that are - # not recognised by 'guess_coord_axis' == suitable standard names - mesh = make_mesh( - nodecoord_xyargs=( - { - "standard_name": "projection_x_coordinate", - "var_name": "node_x", - "units": "degrees", # should NOT convert to 'degrees_east' - "axis": "x", # N.B. this is quietly dropped !! - }, - { - "standard_name": "projection_y_coordinate", - "var_name": "node_y", - "units": "ms-1", - "axis": "y", # N.B. this is quietly dropped !! - }, - ), - facecoord_xyargs=( - { - "standard_name": "longitude", - "var_name": "face_x", - "units": "", # SHOULD result in no units property - }, - { - "standard_name": "latitude", - "var_name": "face_y", # SHOULD convert to 'degrees_north' - "units": "degrees", - }, - ), - ) - - # Save and snapshot the result - tempfile_path = self.check_save_mesh(mesh) - dims, vars = scan_dataset(tempfile_path) - - # there is a single mesh-var - (mesh_name,) = vars_meshnames(vars) - - # find the node- and face-coordinate variables - node_x = vars["node_x"] - node_y = vars["node_y"] - face_x = vars["face_x"] - face_y = vars["face_y"] - - # Check that units are as expected. - # 1. 'long/lat' degree units are converted to east/north - # 2. non- (plain) lonlat are NOT converted - # 3. other names remain as whatever was given - # 4. no units on input --> none on output - self.assertEqual(node_x["units"], "degrees") - self.assertEqual(node_y["units"], "ms-1") - self.assertNotIn("units", face_x) - self.assertEqual(face_y["units"], "degrees_north") - - # Check also that we did not add 'axis' properties. - # We should *only* do that for dim-coords. - self.assertNotIn("axis", node_x) - self.assertNotIn("axis", node_y) - self.assertNotIn("axis", face_x) - self.assertNotIn("axis", face_y) - - @staticmethod - def _namestext(names): - name_texts = [ - f'{title}="{name}"' - for title, name in zip(("standard", "long", "var"), names) - ] - return f'({" ".join(name_texts)})' - - def test_mesh_names(self): - # Check the selection of mesh-variables names. - # N.B. this is basically centralised in Saver._get_mesh_variable_name, - # but we test in an implementation-neutral way (as it's fairly easy). - mesh_names_tests = [ - # no names : based on dimensionality - ( - (None, None, None), - (None, None, "Mesh_2d"), - ), - # var_name only - ( - (None, None, "meshvar_x"), - (None, None, "meshvar_x"), - ), - # standard_name only : does not apply to Mesh - ( - ("air_temperature", None, None), - ("air_temperature", None, "Mesh_2d"), - ), - # long_name only - ( - (None, "my_long_name", None), - (None, "my_long_name", "my_long_name"), - ), - # long_name that needs "fixing" - ( - (None, "my long name&%!", None), - (None, "my long name&%!", "my_long_name___"), - ), - # standard + long names - ( - ("air_temperature", "this_long_name", None), - ("air_temperature", "this_long_name", "this_long_name"), - ), - # long + var names - ( - (None, "my_longname", "varname"), - (None, "my_longname", "varname"), - ), - # all 3 names - ( - ("air_temperature", "airtemp long name", "meshvar_varname_1"), - ("air_temperature", "airtemp long name", "meshvar_varname_1"), - ), - ] - for given_names, expected_names in mesh_names_tests: - mesh_stdname, mesh_longname, mesh_varname = given_names - mesh_name_kwargs = { - "standard_name": mesh_stdname, - "long_name": mesh_longname, - "var_name": mesh_varname, - } - # Make a mesh, with the mesh names set for the testcase - mesh = make_mesh(mesh_kwargs=mesh_name_kwargs) - - filepath = self.check_save_mesh(mesh) - dims, vars = scan_dataset(filepath) - - (mesh_name,) = vars_meshnames(vars) - mesh_props = vars[mesh_name] - result_names = ( - mesh_props.get("standard_name", None), - mesh_props.get("long_name", None), - mesh_name, - ) - fail_msg = ( - f"Unexpected resulting names {self._namestext(result_names)} " - f"when saving mesh with {self._namestext(given_names)}" - ) - self.assertEqual(expected_names, result_names, fail_msg) - - def test_location_coord_names(self): - # Check the selection of mesh-element coordinate names. - # Check the selection of mesh-variables names. - # N.B. this is basically centralised in Saver._get_mesh_variable_name, - # but we test in an implementation-neutral way (as it's fairly easy). - - # Options here are limited because the Mesh relies on guess_axis so, - # for now anyway, coords *must* have a known X/Y-type standard-name - coord_names_tests = [ - # standard_name only - ( - ("longitude", None, None), - ("longitude", None, "longitude"), - ), - # standard + long names --> standard - ( - ("grid_longitude", "long name", None), - ("grid_longitude", "long name", "grid_longitude"), - ), - # standard + var names - ( - ("grid_longitude", None, "var_name"), - ("grid_longitude", None, "var_name"), - ), - # all 3 names - ( - ("projection_x_coordinate", "long name", "x_var_name"), - ("projection_x_coordinate", "long name", "x_var_name"), - ), - # # no standard name ? - # # not possible at present, as Mesh requires a recognisable - # # standard_name to identify the axis of a location-coord. - # # TODO: test this if+when Mesh usage is relaxed - # ( - # (None, None, 'node_x'), - # (None, None, "node_x"), - # ), - ] - for given_names, expected_names in coord_names_tests: - mesh_stdname, mesh_longname, mesh_varname = given_names - - mesh = make_mesh() - # Apply the names to the node_x coord of the mesh - coord = mesh.node_coords[0] - for key, name in zip( - ("standard_name", "long_name", "var_name"), given_names - ): - setattr(coord, key, name) - - filepath = self.check_save_mesh(mesh) - dims, vars = scan_dataset(filepath) - - (mesh_name,) = vars_meshnames(vars) - coord_varname = vars[mesh_name]["node_coordinates"].split(" ")[0] - coord_props = vars[coord_varname] - result_names = ( - coord_props.get("standard_name", None), - coord_props.get("long_name", None), - coord_varname, - ) - fail_msg = ( - f"Unexpected resulting names {self._namestext(result_names)} " - "when saving mesh coordinate " - f"with {self._namestext(given_names)}" - ) - self.assertEqual(expected_names, result_names, fail_msg) - - def test_mesh_dim_names(self): - # Check the selection of dimension names from the mesh. - - dim_names_tests = [ - (None, "Mesh2d_face"), - ("my_face_dimension", "my_face_dimension"), - ("dim invalid-name &%!", "dim_invalid_name____"), - ] - for given_name, expected_name in dim_names_tests: - mesh = make_mesh(mesh_kwargs={"face_dimension": given_name}) - - filepath = self.check_save_mesh(mesh) - dims, vars = scan_dataset(filepath) - - (mesh_name,) = vars_meshnames(vars) - conn_varname = vars[mesh_name]["face_node_connectivity"] - face_dim = vars[conn_varname][_VAR_DIMS][0] - fail_msg = ( - f'Unexpected resulting dimension name "{face_dim}" ' - f'when saving mesh with dimension name of "{given_name}".' - ) - self.assertEqual(expected_name, face_dim, fail_msg) - - def test_connectivity_names(self): - # Check the selection of connectivity names. - conn_names_tests = [ - # var_name only - ( - (None, None, "meshvar_x"), - (None, None, "meshvar_x"), - ), - # standard_name only - ( - ("air_temperature", None, None), - ("air_temperature", None, "air_temperature"), - ), - # long_name only - ( - (None, "my_long_name", None), - (None, "my_long_name", "my_long_name"), - ), - # standard + long names - ( - ("air_temperature", "airtemp long name", None), - ("air_temperature", "airtemp long name", "air_temperature"), - ), - # standard + var names - ( - ("air_temperature", None, "my_var"), - ("air_temperature", None, "my_var"), - ), - # all 3 names - ( - ("air_temperature", "airtemp long name", "meshvar_varname_1"), - ("air_temperature", "airtemp long name", "meshvar_varname_1"), - ), - # long name only, with invalid content - # N.B. behaves *differently* from same in mesh/coord context - ( - (None, "name with spaces", None), # character validation - (None, "name with spaces", "mesh2d_faces"), - ), - ] - for given_names, expected_names in conn_names_tests: - mesh_stdname, mesh_longname, mesh_varname = given_names - - # Make a mesh and afterwards set the names of one connectivity - mesh = make_mesh() - # Apply test names to the face-node connectivity - conn = mesh.face_node_connectivity - for key, name in zip( - ("standard_name", "long_name", "var_name"), given_names - ): - setattr(conn, key, name) - - filepath = self.check_save_mesh(mesh) - dims, vars = scan_dataset(filepath) - - (mesh_name,) = vars_meshnames(vars) - mesh_props = vars[mesh_name] - conn_name = mesh_props["face_node_connectivity"] - conn_props = vars[conn_name] - result_names = ( - conn_props.get("standard_name", None), - conn_props.get("long_name", None), - conn_name, - ) - fail_msg = ( - f"Unexpected resulting names {self._namestext(result_names)} " - "when saving connectivity " - f"with {self._namestext(given_names)}" - ) - self.assertEqual(expected_names, result_names, fail_msg) - - def _check_two_different_meshes(self, vars): - # there are exactly 2 meshes in the file - mesh_names = vars_meshnames(vars) - self.assertEqual(sorted(mesh_names), ["Mesh2d", "Mesh2d_0"]) - - # they use different dimensions - # mesh1 - self.assertEqual( - vars_meshdim(vars, "node", mesh_name="Mesh2d"), "Mesh2d_nodes" - ) - self.assertEqual( - vars_meshdim(vars, "face", mesh_name="Mesh2d"), "Mesh2d_faces" - ) - if "edge_coordinates" in vars["Mesh2d"]: - self.assertEqual( - vars_meshdim(vars, "edge", mesh_name="Mesh2d"), "Mesh2d_edge" - ) - - # mesh2 - self.assertEqual( - vars_meshdim(vars, "node", mesh_name="Mesh2d_0"), "Mesh2d_nodes_0" - ) - self.assertEqual( - vars_meshdim(vars, "face", mesh_name="Mesh2d_0"), "Mesh2d_faces_0" - ) - if "edge_coordinates" in vars["Mesh2d_0"]: - self.assertEqual( - vars_meshdim(vars, "edge", mesh_name="Mesh2d_0"), - "Mesh2d_edge_0", - ) - - # the relevant coords + connectivities are also distinct - # mesh1 - self.assertEqual(vars["node_x"][_VAR_DIMS], ["Mesh2d_nodes"]) - self.assertEqual(vars["face_x"][_VAR_DIMS], ["Mesh2d_faces"]) - self.assertEqual( - vars["mesh2d_faces"][_VAR_DIMS], - ["Mesh2d_faces", "Mesh2d_face_N_nodes"], - ) - if "edge_coordinates" in vars["Mesh2d"]: - self.assertEqual(vars["longitude"][_VAR_DIMS], ["Mesh2d_edge"]) - self.assertEqual( - vars["mesh2d_edge"][_VAR_DIMS], - ["Mesh2d_edge", "Mesh2d_edge_N_nodes"], - ) - - # mesh2 - self.assertEqual(vars["node_x_0"][_VAR_DIMS], ["Mesh2d_nodes_0"]) - self.assertEqual(vars["face_x_0"][_VAR_DIMS], ["Mesh2d_faces_0"]) - self.assertEqual( - vars["mesh2d_faces_0"][_VAR_DIMS], - ["Mesh2d_faces_0", "Mesh2d_0_face_N_nodes"], - ) - if "edge_coordinates" in vars["Mesh2d_0"]: - self.assertEqual(vars["longitude_0"][_VAR_DIMS], ["Mesh2d_edge_0"]) - self.assertEqual( - vars["mesh2d_edge_0"][_VAR_DIMS], - ["Mesh2d_edge_0", "Mesh2d_0_edge_N_nodes"], - ) - - def test_multiple_equal_mesh(self): - mesh1 = make_mesh() - mesh2 = make_mesh() - - # Save and snapshot the result - tempfile_path = self.check_save_mesh([mesh1, mesh2]) - dims, vars = scan_dataset(tempfile_path) - - # In this case there should be only *one* mesh. - mesh_names = vars_meshnames(vars) - self.assertEqual(1, len(mesh_names)) - - # Check it has the correct number of coords + conns (no duplicates) - # Should have 2 each X and Y coords (face+node): _no_ edge coords. - coord_vars_x = vars_w_props(vars, standard_name="longitude") - coord_vars_y = vars_w_props(vars, standard_name="latitude") - self.assertEqual(2, len(coord_vars_x)) - self.assertEqual(2, len(coord_vars_y)) - - # Check the connectivities are all present: _only_ 1 var of each type. - for conn in mesh1.all_connectivities: - if conn is not None: - conn_vars = vars_w_props(vars, cf_role=conn.cf_role) - self.assertEqual(1, len(conn_vars)) - - def test_multiple_different_meshes(self): - # Create 2 meshes with different faces, but same edges. - # N.B. they should *not* then share an edge dimension ! - mesh1 = make_mesh(n_faces=3, n_edges=2) - mesh2 = make_mesh(n_faces=4, n_edges=2) - - # Save and snapshot the result - tempfile_path = self.check_save_mesh([mesh1, mesh2]) - dims, vars = scan_dataset(tempfile_path) - - # Check there are two independent meshes - self._check_two_different_meshes(vars) - - # Check the dims are as expected - self.assertEqual(dims["Mesh2d_faces"], 3) - self.assertEqual(dims["Mesh2d_faces_0"], 4) - self.assertEqual(dims["Mesh2d_edge"], 2) - self.assertEqual(dims["Mesh2d_edge_0"], 2) - - -# WHEN MODIFYING THIS MODULE, CHECK IF ANY CORRESPONDING CHANGES ARE NEEDED IN -# :mod:`iris.tests.unit.fileformats.netcdf.test_Saver__lazy.` - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_load_cubes.py b/lib/iris/tests/unit/fileformats/netcdf/test_load_cubes.py deleted file mode 100644 index 39992d03a0..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/test_load_cubes.py +++ /dev/null @@ -1,316 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.fileformats.netcdf.load_cubes` function. - -todo: migrate the remaining unit-esque tests from iris.tests.test_netcdf, - switching to use netcdf.load_cubes() instead of iris.load()/load_cube(). - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from pathlib import Path -from shutil import rmtree -import tempfile - -from cf_units import as_unit -import numpy as np - -from iris.coords import AncillaryVariable, CellMeasure -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD -from iris.experimental.ugrid.mesh import MeshCoord -from iris.fileformats.netcdf import load_cubes, logger -from iris.tests.stock.netcdf import ncgen_from_cdl - - -def setUpModule(): - global TMP_DIR - TMP_DIR = Path(tempfile.mkdtemp()) - - -def tearDownModule(): - if TMP_DIR is not None: - rmtree(TMP_DIR) - - -def cdl_to_nc(cdl): - cdl_path = TMP_DIR / "tst.cdl" - nc_path = TMP_DIR / "tst.nc" - ncgen_from_cdl(cdl, cdl_path, nc_path) - return str(nc_path) - - -class Tests(tests.IrisTest): - def test_ancillary_variables(self): - # Note: using a CDL string as a test data reference, rather than a - # binary file. - ref_cdl = """ - netcdf cm_attr { - dimensions: - axv = 3 ; - variables: - int64 qqv(axv) ; - qqv:long_name = "qq" ; - qqv:units = "1" ; - qqv:ancillary_variables = "my_av" ; - int64 axv(axv) ; - axv:units = "1" ; - axv:long_name = "x" ; - double my_av(axv) ; - my_av:units = "1" ; - my_av:long_name = "refs" ; - my_av:custom = "extra-attribute"; - data: - axv = 1, 2, 3; - my_av = 11., 12., 13.; - } - """ - nc_path = cdl_to_nc(ref_cdl) - - # Load with iris.fileformats.netcdf.load_cubes, and check expected content. - cubes = list(load_cubes(nc_path)) - self.assertEqual(len(cubes), 1) - avs = cubes[0].ancillary_variables() - self.assertEqual(len(avs), 1) - expected = AncillaryVariable( - np.ma.array([11.0, 12.0, 13.0]), - long_name="refs", - var_name="my_av", - units="1", - attributes={"custom": "extra-attribute"}, - ) - self.assertEqual(avs[0], expected) - - def test_status_flags(self): - # Note: using a CDL string as a test data reference, rather than a binary file. - ref_cdl = """ - netcdf cm_attr { - dimensions: - axv = 3 ; - variables: - int64 qqv(axv) ; - qqv:long_name = "qq" ; - qqv:units = "1" ; - qqv:ancillary_variables = "my_av" ; - int64 axv(axv) ; - axv:units = "1" ; - axv:long_name = "x" ; - byte my_av(axv) ; - my_av:long_name = "qq status_flag" ; - my_av:flag_values = 1b, 2b ; - my_av:flag_meanings = "a b" ; - data: - axv = 11, 21, 31; - my_av = 1b, 1b, 2b; - } - """ - nc_path = cdl_to_nc(ref_cdl) - - # Load with iris.fileformats.netcdf.load_cubes, and check expected content. - cubes = list(load_cubes(nc_path)) - self.assertEqual(len(cubes), 1) - avs = cubes[0].ancillary_variables() - self.assertEqual(len(avs), 1) - expected = AncillaryVariable( - np.ma.array([1, 1, 2], dtype=np.int8), - long_name="qq status_flag", - var_name="my_av", - units="no_unit", - attributes={ - "flag_values": np.array([1, 2], dtype=np.int8), - "flag_meanings": "a b", - }, - ) - self.assertEqual(avs[0], expected) - - def test_cell_measures(self): - # Note: using a CDL string as a test data reference, rather than a binary file. - ref_cdl = """ - netcdf cm_attr { - dimensions: - axv = 3 ; - ayv = 2 ; - variables: - int64 qqv(ayv, axv) ; - qqv:long_name = "qq" ; - qqv:units = "1" ; - qqv:cell_measures = "area: my_areas" ; - int64 ayv(ayv) ; - ayv:units = "1" ; - ayv:long_name = "y" ; - int64 axv(axv) ; - axv:units = "1" ; - axv:long_name = "x" ; - double my_areas(ayv, axv) ; - my_areas:units = "m2" ; - my_areas:long_name = "standardised cell areas" ; - my_areas:custom = "extra-attribute"; - data: - axv = 11, 12, 13; - ayv = 21, 22; - my_areas = 110., 120., 130., 221., 231., 241.; - } - """ - nc_path = cdl_to_nc(ref_cdl) - - # Load with iris.fileformats.netcdf.load_cubes, and check expected content. - cubes = list(load_cubes(nc_path)) - self.assertEqual(len(cubes), 1) - cms = cubes[0].cell_measures() - self.assertEqual(len(cms), 1) - expected = CellMeasure( - np.ma.array([[110.0, 120.0, 130.0], [221.0, 231.0, 241.0]]), - measure="area", - var_name="my_areas", - long_name="standardised cell areas", - units="m2", - attributes={"custom": "extra-attribute"}, - ) - self.assertEqual(cms[0], expected) - - def test_default_units(self): - # Note: using a CDL string as a test data reference, rather than a binary file. - ref_cdl = """ - netcdf cm_attr { - dimensions: - axv = 3 ; - ayv = 2 ; - variables: - int64 qqv(ayv, axv) ; - qqv:long_name = "qq" ; - qqv:ancillary_variables = "my_av" ; - qqv:cell_measures = "area: my_areas" ; - int64 ayv(ayv) ; - ayv:long_name = "y" ; - int64 axv(axv) ; - axv:units = "1" ; - axv:long_name = "x" ; - double my_av(axv) ; - my_av:long_name = "refs" ; - double my_areas(ayv, axv) ; - my_areas:long_name = "areas" ; - data: - axv = 11, 12, 13; - ayv = 21, 22; - my_areas = 110., 120., 130., 221., 231., 241.; - } - """ - nc_path = cdl_to_nc(ref_cdl) - - # Load with iris.fileformats.netcdf.load_cubes, and check expected content. - cubes = list(load_cubes(nc_path)) - self.assertEqual(len(cubes), 1) - self.assertEqual(cubes[0].units, as_unit("unknown")) - self.assertEqual(cubes[0].coord("y").units, as_unit("unknown")) - self.assertEqual(cubes[0].coord("x").units, as_unit(1)) - self.assertEqual( - cubes[0].ancillary_variable("refs").units, as_unit("unknown") - ) - self.assertEqual( - cubes[0].cell_measure("areas").units, as_unit("unknown") - ) - - -class TestsMesh(tests.IrisTest): - @classmethod - def setUpClass(cls): - cls.ref_cdl = """ - netcdf mesh_test { - dimensions: - node = 3 ; - face = 1 ; - vertex = 3 ; - levels = 2 ; - variables: - int mesh ; - mesh:cf_role = "mesh_topology" ; - mesh:topology_dimension = 2 ; - mesh:node_coordinates = "node_x node_y" ; - mesh:face_coordinates = "face_x face_y" ; - mesh:face_node_connectivity = "face_nodes" ; - float node_x(node) ; - node_x:standard_name = "longitude" ; - float node_y(node) ; - node_y:standard_name = "latitude" ; - float face_x(face) ; - face_x:standard_name = "longitude" ; - float face_y(face) ; - face_y:standard_name = "latitude" ; - int face_nodes(face, vertex) ; - face_nodes:cf_role = "face_node_connectivity" ; - face_nodes:start_index = 0 ; - int levels(levels) ; - float node_data(levels, node) ; - node_data:coordinates = "node_x node_y" ; - node_data:location = "node" ; - node_data:mesh = "mesh" ; - float face_data(levels, face) ; - face_data:coordinates = "face_x face_y" ; - face_data:location = "face" ; - face_data:mesh = "mesh" ; - data: - mesh = 0; - node_x = 0., 2., 1.; - node_y = 0., 0., 1.; - face_x = 0.5; - face_y = 0.5; - face_nodes = 0, 1, 2; - levels = 1, 2; - node_data = 0., 0., 0.; - face_data = 0.; - } - """ - cls.nc_path = cdl_to_nc(cls.ref_cdl) - with PARSE_UGRID_ON_LOAD.context(): - cls.mesh_cubes = list(load_cubes(cls.nc_path)) - - def test_mesh_handled(self): - cubes_no_ugrid = list(load_cubes(self.nc_path)) - self.assertEqual(4, len(cubes_no_ugrid)) - self.assertEqual(2, len(self.mesh_cubes)) - - def test_standard_dims(self): - for cube in self.mesh_cubes: - self.assertIsNotNone(cube.coords("levels")) - - def test_mesh_coord(self): - cube = [ - cube for cube in self.mesh_cubes if cube.var_name == "face_data" - ][0] - face_x = cube.coord("longitude") - face_y = cube.coord("latitude") - - for coord in (face_x, face_y): - self.assertIsInstance(coord, MeshCoord) - self.assertEqual("face", coord.location) - self.assertArrayEqual(np.ma.array([0.5]), coord.points) - - self.assertEqual("x", face_x.axis) - self.assertEqual("y", face_y.axis) - self.assertEqual(face_x.mesh, face_y.mesh) - self.assertArrayEqual(np.ma.array([[0.0, 2.0, 1.0]]), face_x.bounds) - self.assertArrayEqual(np.ma.array([[0.0, 0.0, 1.0]]), face_y.bounds) - - def test_shared_mesh(self): - cube_meshes = [cube.coord("latitude").mesh for cube in self.mesh_cubes] - self.assertEqual(cube_meshes[0], cube_meshes[1]) - - def test_missing_mesh(self): - ref_cdl = self.ref_cdl.replace( - 'face_data:mesh = "mesh"', 'face_data:mesh = "mesh2"' - ) - nc_path = cdl_to_nc(ref_cdl) - - # No error when mesh handling not activated. - _ = list(load_cubes(nc_path)) - - with PARSE_UGRID_ON_LOAD.context(): - log_regex = r"File does not contain mesh.*" - with self.assertLogs(logger, level="DEBUG", msg_regex=log_regex): - _ = list(load_cubes(nc_path)) diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py b/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py deleted file mode 100644 index bbde2d0a2d..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/test_parse_cell_methods.py +++ /dev/null @@ -1,196 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.fileformats.netcdf.parse_cell_methods`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.coords import CellMethod -from iris.fileformats.netcdf import parse_cell_methods - - -class Test(tests.IrisTest): - def test_simple(self): - cell_method_strings = [ - "time: mean", - "time : mean", - ] - expected = (CellMethod(method="mean", coords="time"),) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_with_interval(self): - cell_method_strings = [ - "time: variance (interval: 1 hr)", - "time : variance (interval: 1 hr)", - ] - expected = ( - CellMethod(method="variance", coords="time", intervals="1 hr"), - ) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_multiple_axes(self): - cell_method_strings = [ - "lat: lon: standard_deviation", - "lat: lon : standard_deviation", - "lat : lon: standard_deviation", - "lat : lon : standard_deviation", - ] - expected = ( - CellMethod(method="standard_deviation", coords=["lat", "lon"]), - ) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_multiple(self): - cell_method_strings = [ - "time: maximum (interval: 1 hr) time: mean (interval: 1 day)", - "time : maximum (interval: 1 hr) time: mean (interval: 1 day)", - "time: maximum (interval: 1 hr) time : mean (interval: 1 day)", - "time : maximum (interval: 1 hr) time : mean (interval: 1 day)", - ] - expected = ( - CellMethod(method="maximum", coords="time", intervals="1 hr"), - CellMethod(method="mean", coords="time", intervals="1 day"), - ) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_comment(self): - cell_method_strings = [ - "time: maximum (interval: 1 hr comment: first bit) " - "time: mean (interval: 1 day comment: second bit)", - "time : maximum (interval: 1 hr comment: first bit) " - "time: mean (interval: 1 day comment: second bit)", - "time: maximum (interval: 1 hr comment: first bit) " - "time : mean (interval: 1 day comment: second bit)", - "time : maximum (interval: 1 hr comment: first bit) " - "time : mean (interval: 1 day comment: second bit)", - ] - expected = ( - CellMethod( - method="maximum", - coords="time", - intervals="1 hr", - comments="first bit", - ), - CellMethod( - method="mean", - coords="time", - intervals="1 day", - comments="second bit", - ), - ) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_comment_brackets(self): - cell_method_strings = [ - "time: minimum within days (comment: 18h(day-1)-18h)", - "time : minimum within days (comment: 18h(day-1)-18h)", - ] - expected = ( - CellMethod( - method="minimum within days", - coords="time", - intervals=None, - comments="18h(day-1)-18h", - ), - ) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_comment_bracket_mismatch_warning(self): - cell_method_strings = [ - "time: minimum within days (comment: 18h day-1)-18h)", - "time : minimum within days (comment: 18h day-1)-18h)", - ] - for cell_method_str in cell_method_strings: - with self.assertWarns( - UserWarning, - msg="Cell methods may be incorrectly parsed due to mismatched brackets", - ): - _ = parse_cell_methods(cell_method_str) - - def test_badly_formatted_warning(self): - cell_method_strings = [ - # "time: maximum (interval: 1 hr comment: first bit " - # "time: mean (interval: 1 day comment: second bit)", - "time: (interval: 1 hr comment: first bit) " - "time: mean (interval: 1 day comment: second bit)", - "time: maximum (interval: 1 hr comment: first bit) " - "time: (interval: 1 day comment: second bit)", - ] - for cell_method_str in cell_method_strings: - with self.assertWarns( - UserWarning, - msg=f"Failed to fully parse cell method string: {cell_method_str}", - ): - _ = parse_cell_methods(cell_method_str) - - def test_portions_of_cells(self): - cell_method_strings = [ - "area: mean where sea_ice over sea", - "area : mean where sea_ice over sea", - ] - expected = ( - CellMethod(method="mean where sea_ice over sea", coords="area"), - ) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_climatology(self): - cell_method_strings = [ - "time: minimum within days time: mean over days", - "time : minimum within days time: mean over days", - "time: minimum within days time : mean over days", - "time : minimum within days time : mean over days", - ] - expected = ( - CellMethod(method="minimum within days", coords="time"), - CellMethod(method="mean over days", coords="time"), - ) - for cell_method_str in cell_method_strings: - res = parse_cell_methods(cell_method_str) - self.assertEqual(res, expected) - - def test_climatology_with_unknown_method(self): - cell_method_strings = [ - "time: min within days time: mean over days", - "time : min within days time: mean over days", - "time: min within days time : mean over days", - "time : min within days time : mean over days", - ] - expected = ( - CellMethod(method="min within days", coords="time"), - CellMethod(method="mean over days", coords="time"), - ) - for cell_method_str in cell_method_strings: - with mock.patch("warnings.warn") as warn: - res = parse_cell_methods(cell_method_str) - self.assertIn( - "NetCDF variable contains unknown cell method 'min'", - warn.call_args[0][0], - ) - self.assertEqual(res, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/netcdf/test_save.py b/lib/iris/tests/unit/fileformats/netcdf/test_save.py deleted file mode 100644 index b274a8be0d..0000000000 --- a/lib/iris/tests/unit/fileformats/netcdf/test_save.py +++ /dev/null @@ -1,363 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.netcdf.save` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from pathlib import Path -from shutil import rmtree -from tempfile import mkdtemp -from unittest import mock - -import numpy as np - -import iris -from iris.coords import AuxCoord, DimCoord -from iris.cube import Cube, CubeList -from iris.experimental.ugrid import PARSE_UGRID_ON_LOAD -from iris.fileformats.netcdf import ( - CF_CONVENTIONS_VERSION, - _thread_safe_nc, - save, -) -from iris.tests.stock import lat_lon_cube -from iris.tests.stock.mesh import sample_mesh_cube - - -class Test_conventions(tests.IrisTest): - def setUp(self): - self.cube = Cube([0]) - self.custom_conventions = "convention1 convention2" - self.cube.attributes["Conventions"] = self.custom_conventions - self.options = iris.config.netcdf - - def test_custom_conventions__ignored(self): - # Ensure that we drop existing conventions attributes and replace with - # CF convention. - with self.temp_filename(".nc") as nc_path: - save(self.cube, nc_path, "NETCDF4") - ds = _thread_safe_nc.DatasetWrapper(nc_path) - res = ds.getncattr("Conventions") - ds.close() - self.assertEqual(res, CF_CONVENTIONS_VERSION) - - def test_custom_conventions__allowed(self): - # Ensure that existing conventions attributes are passed through if the - # relevant Iris option is set. - with mock.patch.object(self.options, "conventions_override", True): - with self.temp_filename(".nc") as nc_path: - save(self.cube, nc_path, "NETCDF4") - ds = _thread_safe_nc.DatasetWrapper(nc_path) - res = ds.getncattr("Conventions") - ds.close() - self.assertEqual(res, self.custom_conventions) - - def test_custom_conventions__allowed__missing(self): - # Ensure the default conventions attribute is set if the relevant Iris - # option is set but there is no custom conventions attribute. - del self.cube.attributes["Conventions"] - with mock.patch.object(self.options, "conventions_override", True): - with self.temp_filename(".nc") as nc_path: - save(self.cube, nc_path, "NETCDF4") - ds = _thread_safe_nc.DatasetWrapper(nc_path) - res = ds.getncattr("Conventions") - ds.close() - self.assertEqual(res, CF_CONVENTIONS_VERSION) - - -class Test_attributes(tests.IrisTest): - def test_attributes_arrays(self): - # Ensure that attributes containing NumPy arrays can be equality - # checked and their cubes saved as appropriate. - c1 = Cube([1], attributes={"bar": np.arange(2)}) - c2 = Cube([2], attributes={"bar": np.arange(2)}) - - with self.temp_filename("foo.nc") as nc_out: - save([c1, c2], nc_out) - ds = _thread_safe_nc.DatasetWrapper(nc_out) - res = ds.getncattr("bar") - ds.close() - self.assertArrayEqual(res, np.arange(2)) - - def test_no_special_attribute_clash(self): - # Ensure that saving multiple cubes with netCDF4 protected attributes - # works as expected. - # Note that here we are testing variable attribute clashes only - by - # saving multiple cubes the attributes are saved as variable - # attributes rather than global attributes. - c1 = Cube([0], var_name="test", attributes={"name": "bar"}) - c2 = Cube([0], var_name="test_1", attributes={"name": "bar_1"}) - - with self.temp_filename("foo.nc") as nc_out: - save([c1, c2], nc_out) - ds = _thread_safe_nc.DatasetWrapper(nc_out) - res = ds.variables["test"].getncattr("name") - res_1 = ds.variables["test_1"].getncattr("name") - ds.close() - self.assertEqual(res, "bar") - self.assertEqual(res_1, "bar_1") - - -class Test_unlimited_dims(tests.IrisTest): - def test_no_unlimited_dims(self): - cube = lat_lon_cube() - with self.temp_filename("foo.nc") as nc_out: - save(cube, nc_out) - ds = _thread_safe_nc.DatasetWrapper(nc_out) - self.assertFalse(ds.dimensions["latitude"].isunlimited()) - - def test_unlimited_dim_latitude(self): - cube = lat_lon_cube() - unlim_dim_name = "latitude" - with self.temp_filename("foo.nc") as nc_out: - save(cube, nc_out, unlimited_dimensions=[unlim_dim_name]) - ds = _thread_safe_nc.DatasetWrapper(nc_out) - self.assertTrue(ds.dimensions[unlim_dim_name].isunlimited()) - - -class Test_fill_value(tests.IrisTest): - def setUp(self): - self.standard_names = [ - "air_temperature", - "air_potential_temperature", - "air_temperature_anomaly", - ] - - def _make_cubes(self): - lat = DimCoord(np.arange(3), "latitude", units="degrees") - lon = DimCoord(np.arange(4), "longitude", units="degrees") - data = np.arange(12, dtype="f4").reshape(3, 4) - return CubeList( - Cube( - data, - standard_name=name, - units="K", - dim_coords_and_dims=[(lat, 0), (lon, 1)], - ) - for name in self.standard_names - ) - - def test_None(self): - # Test that when no fill_value argument is passed, the fill_value - # argument to Saver.write is None or not present. - cubes = self._make_cubes() - with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: - save(cubes, "dummy.nc") - - # Get the Saver.write mock - with Saver() as saver: - write = saver.write - - self.assertEqual(3, write.call_count) - for call in write.mock_calls: - _, _, kwargs = call - if "fill_value" in kwargs: - self.assertIs(None, kwargs["fill_value"]) - - def test_single(self): - # Test that when a single value is passed as the fill_value argument, - # that value is passed to each call to Saver.write - cubes = self._make_cubes() - fill_value = 12345.0 - with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: - save(cubes, "dummy.nc", fill_value=fill_value) - - # Get the Saver.write mock - with Saver() as saver: - write = saver.write - - self.assertEqual(3, write.call_count) - for call in write.mock_calls: - _, _, kwargs = call - self.assertEqual(fill_value, kwargs["fill_value"]) - - def test_multiple(self): - # Test that when a list is passed as the fill_value argument, - # each element is passed to separate calls to Saver.write - cubes = self._make_cubes() - fill_values = [123.0, 456.0, 789.0] - with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: - save(cubes, "dummy.nc", fill_value=fill_values) - - # Get the Saver.write mock - with Saver() as saver: - write = saver.write - - self.assertEqual(3, write.call_count) - for call, fill_value in zip(write.mock_calls, fill_values): - _, _, kwargs = call - self.assertEqual(fill_value, kwargs["fill_value"]) - - def test_single_string(self): - # Test that when a string is passed as the fill_value argument, - # that value is passed to calls to Saver.write - cube = Cube(["abc", "def", "hij"]) - fill_value = "xyz" - with mock.patch("iris.fileformats.netcdf.saver.Saver") as Saver: - save(cube, "dummy.nc", fill_value=fill_value) - - # Get the Saver.write mock - with Saver() as saver: - write = saver.write - - self.assertEqual(1, write.call_count) - _, _, kwargs = write.mock_calls[0] - self.assertEqual(fill_value, kwargs["fill_value"]) - - def test_multi_wrong_length(self): - # Test that when a list of a different length to the number of cubes - # is passed as the fill_value argument, an error is raised - cubes = self._make_cubes() - fill_values = [1.0, 2.0, 3.0, 4.0] - with mock.patch("iris.fileformats.netcdf.saver.Saver"): - with self.assertRaises(ValueError): - save(cubes, "dummy.nc", fill_value=fill_values) - - -class Test_HdfSaveBug(tests.IrisTest): - """ - Check for a known problem with netcdf4. - - If you create dimension with the same name as an existing variable, there - is a specific problem, relating to HDF so limited to netcdf-4 formats. - See : https://github.com/Unidata/netcdf-c/issues/1772 - - In all these testcases, a straightforward translation to the file would be - able to save [cube_2, cube_1], but *not* [cube_1, cube_2], - because the latter creates a dim of the same name as the 'cube_1' data - variable. - - Here, we are testing the specific workarounds in Iris netcdf save which - avoids that problem. - Unfortunately, owing to the complexity of the iris.fileformats.netcdf.Saver - code, there are several separate places where this had to be fixed. - - N.B. we also check that the data (mostly) survives a save-load roundtrip. - To identify the read-back cubes with the originals, we use var-names, - which works because the save code opts to adjust dimension names _instead_. - - """ - - def _check_save_and_reload(self, cubes): - tempdir = Path(mkdtemp()) - filepath = tempdir / "tmp.nc" - try: - # Save the given cubes. - save(cubes, filepath) - - # Load them back for roundtrip testing. - with PARSE_UGRID_ON_LOAD.context(): - new_cubes = iris.load(str(filepath)) - - # There should definitely still be the same number of cubes. - self.assertEqual(len(new_cubes), len(cubes)) - - # Get results in the input order, matching by var_names. - result = [new_cubes.extract_cube(cube.var_name) for cube in cubes] - - # Check that input + output match cube-for-cube. - # NB in this codeblock, before we destroy the temporary file. - for cube_in, cube_out in zip(cubes, result): - # Using special tolerant equivalence-check. - self.assertSameCubes(cube_in, cube_out) - - finally: - rmtree(tempdir) - - # Return result cubes for any additional checks. - return result - - def assertSameCubes(self, cube1, cube2): - """ - A special tolerant cube compare. - - Ignore any 'Conventions' attributes. - Ignore all var-names. - - """ - - def clean_cube(cube): - cube = cube.copy() # dont modify the original - # Remove any 'Conventions' attributes - cube.attributes.pop("Conventions", None) - # Remove var-names (as original mesh components wouldn't have them) - cube.var_name = None - for coord in cube.coords(): - coord.var_name = None - mesh = cube.mesh - if mesh: - mesh.var_name = None - for component in mesh.coords() + mesh.connectivities(): - component.var_name = None - - return cube - - self.assertEqual(clean_cube(cube1), clean_cube(cube2)) - - def test_dimcoord_varname_collision(self): - cube_2 = Cube([0, 1], var_name="cube_2") - x_dim = DimCoord([0, 1], long_name="dim_x", var_name="dimco_name") - cube_2.add_dim_coord(x_dim, 0) - # First cube has a varname which collides with the dimcoord. - cube_1 = Cube([0, 1], long_name="cube_1", var_name="dimco_name") - # Test save + loadback - reload_1, reload_2 = self._check_save_and_reload([cube_1, cube_2]) - # As re-loaded, the coord will have a different varname. - self.assertEqual(reload_2.coord("dim_x").var_name, "dimco_name_0") - - def test_anonymous_dim_varname_collision(self): - # Second cube is going to name an anonymous dim. - cube_2 = Cube([0, 1], var_name="cube_2") - # First cube has a varname which collides with the dim-name. - cube_1 = Cube([0, 1], long_name="cube_1", var_name="dim0") - # Add a dimcoord to prevent the *first* cube having an anonymous dim. - x_dim = DimCoord([0, 1], long_name="dim_x", var_name="dimco_name") - cube_1.add_dim_coord(x_dim, 0) - # Test save + loadback - self._check_save_and_reload([cube_1, cube_2]) - - def test_bounds_dim_varname_collision(self): - cube_2 = Cube([0, 1], var_name="cube_2") - x_dim = DimCoord([0, 1], long_name="dim_x", var_name="dimco_name") - x_dim.guess_bounds() - cube_2.add_dim_coord(x_dim, 0) - # First cube has a varname which collides with the bounds dimension. - cube_1 = Cube([0], long_name="cube_1", var_name="bnds") - # Test save + loadback - self._check_save_and_reload([cube_1, cube_2]) - - def test_string_dim_varname_collision(self): - cube_2 = Cube([0, 1], var_name="cube_2") - # NOTE: it *should* be possible for a cube with string data to cause - # this collision, but cubes with string data are currently not working. - # See : https://github.com/SciTools/iris/issues/4412 - x_dim = AuxCoord( - ["this", "that"], long_name="dim_x", var_name="string_auxco" - ) - cube_2.add_aux_coord(x_dim, 0) - cube_1 = Cube([0], long_name="cube_1", var_name="string4") - # Test save + loadback - self._check_save_and_reload([cube_1, cube_2]) - - def test_mesh_location_dim_varname_collision(self): - cube_2 = sample_mesh_cube() - cube_2.var_name = "cube_2" # Make it identifiable - cube_1 = Cube([0], long_name="cube_1", var_name="Mesh2d_node") - # Test save + loadback - self._check_save_and_reload([cube_1, cube_2]) - - def test_connectivity_dim_varname_collision(self): - cube_2 = sample_mesh_cube() - cube_2.var_name = "cube_2" # Make it identifiable - cube_1 = Cube([0], long_name="cube_1", var_name="Mesh_2d_face_N_nodes") - # Test save + loadback - self._check_save_and_reload([cube_1, cube_2]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nimrod_load_rules/__init__.py b/lib/iris/tests/unit/fileformats/nimrod_load_rules/__init__.py deleted file mode 100644 index 429ee9ce1f..0000000000 --- a/lib/iris/tests/unit/fileformats/nimrod_load_rules/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.nimrod_load_rules` module.""" diff --git a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py deleted file mode 100644 index a15337f849..0000000000 --- a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_units.py +++ /dev/null @@ -1,147 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the `iris.fileformats.nimrod_load_rules.units` function. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.cube import Cube -from iris.fileformats.nimrod import NimrodField -from iris.fileformats.nimrod_load_rules import NIMROD_DEFAULT, units - - -class Test(tests.IrisTest): - NIMROD_LOCATION = "iris.fileformats.nimrod_load_rules" - - def setUp(self): - self.field = mock.Mock( - units="", - int_mdi=-32767, - float32_mdi=NIMROD_DEFAULT, - spec=NimrodField, - ) - self.cube = Cube(np.ones((3, 3), dtype=np.float32)) - - def _call_units(self, data=None, units_str=None): - if data is not None: - self.cube.data = data - if units_str: - self.field.units = units_str - units(self.cube, self.field) - - def test_null(self): - with mock.patch("warnings.warn") as warn: - self._call_units(units_str="m") - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "m") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - - def test_times32(self): - with mock.patch("warnings.warn") as warn: - self._call_units( - data=np.ones_like(self.cube.data) * 32, units_str="mm/hr*32" - ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "mm/hr") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - - def test_visibility_units(self): - with mock.patch("warnings.warn") as warn: - self._call_units( - data=((np.ones_like(self.cube.data) / 2) - 25000), - units_str="m/2-25k", - ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "m") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - - def test_power_in_units(self): - with mock.patch("warnings.warn") as warn: - self._call_units( - data=np.ones_like(self.cube.data) * 1000, units_str="mm*10^3" - ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "mm") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - - def test_ug_per_m3_units(self): - with mock.patch("warnings.warn") as warn: - self._call_units( - data=((np.ones_like(self.cube.data) * 10)), - units_str="ug/m3E1", - ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "ug/m3") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - - def test_g_per_kg(self): - with mock.patch("warnings.warn") as warn: - self._call_units( - data=((np.ones_like(self.cube.data) * 1000)), units_str="g/Kg" - ) - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "kg/kg") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - - def test_unit_expection_dictionary(self): - with mock.patch("warnings.warn") as warn: - self._call_units(units_str="mb") - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "hPa") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - - def test_per_second(self): - with mock.patch("warnings.warn") as warn: - self._call_units(units_str="/s") - self.assertEqual(warn.call_count, 0) - self.assertEqual(self.cube.units, "s^-1") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - - def test_unhandled_unit(self): - with mock.patch("warnings.warn") as warn: - self._call_units(units_str="kittens") - self.assertEqual(warn.call_count, 1) - self.assertEqual(self.cube.units, "") - self.assertArrayAlmostEqual( - self.cube.data, np.ones_like(self.cube.data) - ) - self.assertEqual(self.cube.data.dtype, np.float32) - self.assertEqual(self.cube.attributes["invalid_units"], "kittens") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py b/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py deleted file mode 100644 index 44dcf8ac48..0000000000 --- a/lib/iris/tests/unit/fileformats/nimrod_load_rules/test_vertical_coord.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the `iris.fileformats.nimrod_load_rules.vertical_coord` -function. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats.nimrod import NimrodField -from iris.fileformats.nimrod_load_rules import ( - NIMROD_DEFAULT, - TranslationWarning, - vertical_coord, -) - - -class Test(tests.IrisTest): - NIMROD_LOCATION = "iris.fileformats.nimrod_load_rules" - - def setUp(self): - self.field = mock.Mock( - vertical_coord=NIMROD_DEFAULT, - vertical_coord_type=NIMROD_DEFAULT, - reference_vertical_coord=NIMROD_DEFAULT, - reference_vertical_coord_type=NIMROD_DEFAULT, - int_mdi=-32767, - float32_mdi=NIMROD_DEFAULT, - spec=NimrodField, - ) - self.cube = mock.Mock() - - def _call_vertical_coord( - self, - vertical_coord_val=None, - vertical_coord_type=None, - reference_vertical_coord=None, - reference_vertical_coord_type=None, - ): - if vertical_coord_val: - self.field.vertical_coord = vertical_coord_val - if vertical_coord_type: - self.field.vertical_coord_type = vertical_coord_type - if reference_vertical_coord: - self.field.reference_vertical_coord = reference_vertical_coord - if reference_vertical_coord_type: - self.field.reference_vertical_coord_type = ( - reference_vertical_coord_type - ) - vertical_coord(self.cube, self.field) - - def test_unhandled(self): - with mock.patch("warnings.warn") as warn: - self._call_vertical_coord( - vertical_coord_val=1.0, vertical_coord_type=-1 - ) - warn.assert_called_once_with( - "Vertical coord -1 not yet handled", TranslationWarning - ) - - def test_null(self): - with mock.patch("warnings.warn") as warn: - self._call_vertical_coord(vertical_coord_type=NIMROD_DEFAULT) - self._call_vertical_coord(vertical_coord_type=self.field.int_mdi) - self.assertEqual(warn.call_count, 0) - - def test_ground_level(self): - with mock.patch("warnings.warn") as warn: - self._call_vertical_coord( - vertical_coord_val=9999.0, vertical_coord_type=0 - ) - self.assertEqual(warn.call_count, 0) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/__init__.py b/lib/iris/tests/unit/fileformats/pp/__init__.py deleted file mode 100644 index f309b6848a..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.pp` module.""" diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPDataProxy.py b/lib/iris/tests/unit/fileformats/pp/test_PPDataProxy.py deleted file mode 100644 index d70e573296..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test_PPDataProxy.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.PPDataProxy` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats.pp import PPDataProxy, SplittableInt - - -class Test_lbpack(tests.IrisTest): - def test_lbpack_SplittableInt(self): - lbpack = mock.Mock(spec_set=SplittableInt) - proxy = PPDataProxy(None, None, None, None, None, lbpack, None, None) - self.assertEqual(proxy.lbpack, lbpack) - self.assertIs(proxy.lbpack, lbpack) - - def test_lbpack_raw(self): - lbpack = 4321 - proxy = PPDataProxy(None, None, None, None, None, lbpack, None, None) - self.assertEqual(proxy.lbpack, lbpack) - self.assertIsNot(proxy.lbpack, lbpack) - self.assertIsInstance(proxy.lbpack, SplittableInt) - self.assertEqual(proxy.lbpack.n1, lbpack % 10) - self.assertEqual(proxy.lbpack.n2, lbpack // 10 % 10) - self.assertEqual(proxy.lbpack.n3, lbpack // 100 % 10) - self.assertEqual(proxy.lbpack.n4, lbpack // 1000 % 10) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test_PPField.py b/lib/iris/tests/unit/fileformats/pp/test_PPField.py deleted file mode 100644 index 316894ded1..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test_PPField.py +++ /dev/null @@ -1,354 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.PPField` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -import iris.fileformats.pp as pp -from iris.fileformats.pp import PPField, SplittableInt - -# The PPField class is abstract, so to test we define a minimal, -# concrete subclass with the `t1` and `t2` properties. -# -# NB. We define dummy header items to allow us to zero the unused header -# items when written to disk and get consistent results. - - -DUMMY_HEADER = [ - ("dummy1", (0, 11)), - ("lbtim", (12,)), - ("dummy2", (13,)), - ("lblrec", (14,)), - ("dummy3", (15, 16)), - ("lbrow", (17,)), - ("dummy4", (18,)), - ("lbext", (19,)), - ("lbpack", (20,)), - ("dummy5", (21, 37)), - ("lbuser", (38, 39, 40, 41, 42, 43, 44)), - ("brsvd", (45, 46, 47, 48)), - ("bdatum", (49,)), - ("dummy6", (50, 61)), - ("bmdi", (62,)), - ("dummy7", (63,)), -] - - -class DummyPPField(PPField): - HEADER_DEFN = DUMMY_HEADER - HEADER_DICT = dict(DUMMY_HEADER) - - def _ready_for_save(self): - self.dummy1 = 0 - self.dummy2 = 0 - self.dummy3 = 0 - self.dummy4 = 0 - self.dummy5 = 0 - self.dummy6 = 0 - self.dummy7 = 0 - self.lbtim = 0 - self.lblrec = 0 - self.lbrow = 0 - self.lbext = 0 - self.lbpack = 0 - self.lbuser = 0 - self.brsvd = 0 - self.bdatum = 0 - self.bmdi = -1e30 - return self - - @property - def t1(self): - return None - - @property - def t2(self): - return None - - -class Test_save(tests.IrisTest): - def test_float64(self): - # Tests down-casting of >f8 data to >f4. - - def field_checksum(data): - field = DummyPPField()._ready_for_save() - field.data = data - with self.temp_filename(".pp") as temp_filename: - with open(temp_filename, "wb") as pp_file: - field.save(pp_file) - checksum = self.file_checksum(temp_filename) - return checksum - - data_64 = np.linspace(0, 1, num=10, endpoint=False).reshape(2, 5) - checksum_32 = field_checksum(data_64.astype(">f4")) - msg = "Downcasting array precision from float64 to float32 for save." - with self.assertWarnsRegex(UserWarning, msg): - checksum_64 = field_checksum(data_64.astype(">f8")) - self.assertEqual(checksum_32, checksum_64) - - def test_masked_mdi_value_warning(self): - # Check that an unmasked MDI value raises a warning. - field = DummyPPField()._ready_for_save() - field.bmdi = -123.4 - # Make float32 data, as float64 default produces an extra warning. - field.data = np.ma.masked_array( - [1.0, field.bmdi, 3.0], dtype=np.float32 - ) - msg = "PPField data contains unmasked points" - with self.assertWarnsRegex(UserWarning, msg): - with self.temp_filename(".pp") as temp_filename: - with open(temp_filename, "wb") as pp_file: - field.save(pp_file) - - def test_unmasked_mdi_value_warning(self): - # Check that MDI in *unmasked* data raises a warning. - field = DummyPPField()._ready_for_save() - field.bmdi = -123.4 - # Make float32 data, as float64 default produces an extra warning. - field.data = np.array([1.0, field.bmdi, 3.0], dtype=np.float32) - msg = "PPField data contains unmasked points" - with self.assertWarnsRegex(UserWarning, msg): - with self.temp_filename(".pp") as temp_filename: - with open(temp_filename, "wb") as pp_file: - field.save(pp_file) - - def test_mdi_masked_value_nowarning(self): - # Check that a *masked* MDI value does not raise a warning. - field = DummyPPField()._ready_for_save() - field.bmdi = -123.4 - # Make float32 data, as float64 default produces an extra warning. - field.data = np.ma.masked_array( - [1.0, 2.0, 3.0], mask=[0, 1, 0], dtype=np.float32 - ) - # Set underlying data value at masked point to BMDI value. - field.data.data[1] = field.bmdi - self.assertArrayAllClose(field.data.data[1], field.bmdi) - with self.assertNoWarningsRegexp(r"\(mask\|fill\)"): - with self.temp_filename(".pp") as temp_filename: - with open(temp_filename, "wb") as pp_file: - field.save(pp_file) - - -class Test_calendar(tests.IrisTest): - def test_greg(self): - field = DummyPPField() - field.lbtim = SplittableInt(1, {"ia": 2, "ib": 1, "ic": 0}) - self.assertEqual(field.calendar, "standard") - - def test_360(self): - field = DummyPPField() - field.lbtim = SplittableInt(2, {"ia": 2, "ib": 1, "ic": 0}) - self.assertEqual(field.calendar, "360_day") - - def test_365(self): - field = DummyPPField() - field.lbtim = SplittableInt(4, {"ia": 2, "ib": 1, "ic": 0}) - self.assertEqual(field.calendar, "365_day") - - -class Test_coord_system(tests.IrisTest): - def _check_cs(self, bplat, bplon, rotated): - field = DummyPPField() - field.bplat = bplat - field.bplon = bplon - with mock.patch( - "iris.fileformats.pp.iris.coord_systems" - ) as mock_cs_mod: - result = field.coord_system() - if not rotated: - # It should return a standard unrotated CS. - self.assertTrue(mock_cs_mod.GeogCS.call_count == 1) - self.assertEqual(result, mock_cs_mod.GeogCS()) - else: - # It should return a rotated CS with the correct makeup. - self.assertTrue(mock_cs_mod.GeogCS.call_count == 1) - self.assertTrue(mock_cs_mod.RotatedGeogCS.call_count == 1) - self.assertEqual(result, mock_cs_mod.RotatedGeogCS()) - self.assertEqual( - mock_cs_mod.RotatedGeogCS.call_args_list[0], - mock.call(bplat, bplon, ellipsoid=mock_cs_mod.GeogCS()), - ) - - def test_normal_unrotated(self): - # Check that 'normal' BPLAT,BPLON=90,0 produces an unrotated system. - self._check_cs(bplat=90, bplon=0, rotated=False) - - def test_bplon_180_unrotated(self): - # Check that BPLAT,BPLON=90,180 behaves the same as 90,0. - self._check_cs(bplat=90, bplon=180, rotated=False) - - def test_odd_bplat_rotated(self): - # Show that BPLAT != 90 produces a rotated field. - self._check_cs(bplat=75, bplon=180, rotated=True) - - def test_odd_bplon_rotated(self): - # Show that BPLON != 0 or 180 produces a rotated field. - self._check_cs(bplat=90, bplon=123.45, rotated=True) - - -class Test__init__(tests.IrisTest): - def setUp(self): - header_longs = np.zeros(pp.NUM_LONG_HEADERS, dtype=np.int_) - header_floats = np.zeros(pp.NUM_FLOAT_HEADERS, dtype=np.float64) - self.header = list(header_longs) + list(header_floats) - - def test_no_headers(self): - field = DummyPPField() - self.assertIsNone(field._raw_header) - self.assertIsNone(field.raw_lbtim) - self.assertIsNone(field.raw_lbpack) - - def test_lbtim_lookup(self): - self.assertEqual(DummyPPField.HEADER_DICT["lbtim"], (12,)) - - def test_lbpack_lookup(self): - self.assertEqual(DummyPPField.HEADER_DICT["lbpack"], (20,)) - - def test_raw_lbtim(self): - raw_lbtim = 4321 - (loc,) = DummyPPField.HEADER_DICT["lbtim"] - self.header[loc] = raw_lbtim - field = DummyPPField(header=self.header) - self.assertEqual(field.raw_lbtim, raw_lbtim) - - def test_raw_lbpack(self): - raw_lbpack = 4321 - (loc,) = DummyPPField.HEADER_DICT["lbpack"] - self.header[loc] = raw_lbpack - field = DummyPPField(header=self.header) - self.assertEqual(field.raw_lbpack, raw_lbpack) - - -class Test__getattr__(tests.IrisTest): - def setUp(self): - header_longs = np.zeros(pp.NUM_LONG_HEADERS, dtype=np.int_) - header_floats = np.zeros(pp.NUM_FLOAT_HEADERS, dtype=np.float64) - self.header = list(header_longs) + list(header_floats) - - def test_attr_singular_long(self): - lbrow = 1234 - (loc,) = DummyPPField.HEADER_DICT["lbrow"] - self.header[loc] = lbrow - field = DummyPPField(header=self.header) - self.assertEqual(field.lbrow, lbrow) - - def test_attr_multi_long(self): - lbuser = (100, 101, 102, 103, 104, 105, 106) - loc = DummyPPField.HEADER_DICT["lbuser"] - self.header[loc[0] : loc[-1] + 1] = lbuser - field = DummyPPField(header=self.header) - self.assertEqual(field.lbuser, lbuser) - - def test_attr_singular_float(self): - bdatum = 1234 - (loc,) = DummyPPField.HEADER_DICT["bdatum"] - self.header[loc] = bdatum - field = DummyPPField(header=self.header) - self.assertEqual(field.bdatum, bdatum) - - def test_attr_multi_float(self): - brsvd = (100, 101, 102, 103) - loc = DummyPPField.HEADER_DICT["brsvd"] - start = loc[0] - stop = loc[-1] + 1 - self.header[start:stop] = brsvd - field = DummyPPField(header=self.header) - self.assertEqual(field.brsvd, brsvd) - - def test_attr_lbtim(self): - raw_lbtim = 4321 - (loc,) = DummyPPField.HEADER_DICT["lbtim"] - self.header[loc] = raw_lbtim - field = DummyPPField(header=self.header) - result = field.lbtim - self.assertEqual(result, raw_lbtim) - self.assertIsInstance(result, SplittableInt) - result = field._lbtim - self.assertEqual(result, raw_lbtim) - self.assertIsInstance(result, SplittableInt) - - def test_attr_lbpack(self): - raw_lbpack = 4321 - (loc,) = DummyPPField.HEADER_DICT["lbpack"] - self.header[loc] = raw_lbpack - field = DummyPPField(header=self.header) - result = field.lbpack - self.assertEqual(result, raw_lbpack) - self.assertIsInstance(result, SplittableInt) - result = field._lbpack - self.assertEqual(result, raw_lbpack) - self.assertIsInstance(result, SplittableInt) - - def test_attr_raw_lbtim_assign(self): - field = DummyPPField(header=self.header) - self.assertEqual(field.raw_lbpack, 0) - self.assertEqual(field.lbtim, 0) - raw_lbtim = 4321 - field.lbtim = raw_lbtim - self.assertEqual(field.raw_lbtim, raw_lbtim) - self.assertNotIsInstance(field.raw_lbtim, SplittableInt) - - def test_attr_raw_lbpack_assign(self): - field = DummyPPField(header=self.header) - self.assertEqual(field.raw_lbpack, 0) - self.assertEqual(field.lbpack, 0) - raw_lbpack = 4321 - field.lbpack = raw_lbpack - self.assertEqual(field.raw_lbpack, raw_lbpack) - self.assertNotIsInstance(field.raw_lbpack, SplittableInt) - - def test_attr_unknown(self): - with self.assertRaises(AttributeError): - DummyPPField().x - - -class Test_lbtim(tests.IrisTest): - def test_get_splittable(self): - headers = [0] * 64 - headers[12] = 12345 - field = DummyPPField(headers) - self.assertIsInstance(field.lbtim, SplittableInt) - self.assertEqual(field.lbtim.ia, 123) - self.assertEqual(field.lbtim.ib, 4) - self.assertEqual(field.lbtim.ic, 5) - - def test_set_int(self): - headers = [0] * 64 - headers[12] = 12345 - field = DummyPPField(headers) - field.lbtim = 34567 - self.assertIsInstance(field.lbtim, SplittableInt) - self.assertEqual(field.lbtim.ia, 345) - self.assertEqual(field.lbtim.ib, 6) - self.assertEqual(field.lbtim.ic, 7) - self.assertEqual(field.raw_lbtim, 34567) - - def test_set_splittable(self): - # Check that assigning a SplittableInt to lbtim uses the integer - # value. In other words, check that you can't assign an - # arbitrary SplittableInt with crazy named attributes. - headers = [0] * 64 - headers[12] = 12345 - field = DummyPPField(headers) - si = SplittableInt(34567, {"foo": 0}) - field.lbtim = si - self.assertIsInstance(field.lbtim, SplittableInt) - with self.assertRaises(AttributeError): - field.lbtim.foo - self.assertEqual(field.lbtim.ia, 345) - self.assertEqual(field.lbtim.ib, 6) - self.assertEqual(field.lbtim.ic, 7) - self.assertEqual(field.raw_lbtim, 34567) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test__convert_constraints.py b/lib/iris/tests/unit/fileformats/pp/test__convert_constraints.py deleted file mode 100644 index 514e326393..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test__convert_constraints.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.load` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris -from iris.fileformats.pp import STASH, _convert_constraints - - -class Test_convert_constraints(tests.IrisTest): - def _single_stash(self): - constraint = iris.AttributeConstraint(STASH="m01s03i236") - return _convert_constraints(constraint) - - def test_single_stash(self): - pp_filter = self._single_stash() - stcube = mock.Mock(stash=STASH.from_msi("m01s03i236")) - self.assertTrue(pp_filter(stcube)) - - def test_stash_object(self): - constraint = iris.AttributeConstraint( - STASH=STASH.from_msi("m01s03i236") - ) - pp_filter = _convert_constraints(constraint) - stcube = mock.Mock(stash=STASH.from_msi("m01s03i236")) - self.assertTrue(pp_filter(stcube)) - - def test_surface_altitude(self): - # Ensure that surface altitude fields are not filtered. - pp_filter = self._single_stash() - orography_cube = mock.Mock(stash=STASH.from_msi("m01s00i033")) - self.assertTrue(pp_filter(orography_cube)) - - def test_surface_pressure(self): - # Ensure that surface pressure fields are not filtered. - pp_filter = self._single_stash() - pressure_cube = mock.Mock(stash=STASH.from_msi("m01s00i001")) - self.assertTrue(pp_filter(pressure_cube)) - - def test_double_stash(self): - stcube236 = mock.Mock(stash=STASH.from_msi("m01s03i236")) - stcube4 = mock.Mock(stash=STASH.from_msi("m01s00i004")) - stcube7 = mock.Mock(stash=STASH.from_msi("m01s00i007")) - constraints = [ - iris.AttributeConstraint(STASH="m01s03i236"), - iris.AttributeConstraint(STASH="m01s00i004"), - ] - pp_filter = _convert_constraints(constraints) - self.assertTrue(pp_filter(stcube236)) - self.assertTrue(pp_filter(stcube4)) - self.assertFalse(pp_filter(stcube7)) - - def test_callable_stash(self): - stcube236 = mock.Mock(stash=STASH.from_msi("m01s03i236")) - stcube4 = mock.Mock(stash=STASH.from_msi("m01s00i004")) - stcube7 = mock.Mock(stash=STASH.from_msi("m01s00i007")) - con1 = iris.AttributeConstraint(STASH=lambda s: s.endswith("004")) - con2 = iris.AttributeConstraint(STASH=lambda s: s == "m01s00i007") - constraints = [con1, con2] - pp_filter = _convert_constraints(constraints) - self.assertFalse(pp_filter(stcube236)) - self.assertTrue(pp_filter(stcube4)) - self.assertTrue(pp_filter(stcube7)) - - def test_multiple_with_stash(self): - constraints = [ - iris.Constraint("air_potential_temperature"), - iris.AttributeConstraint(STASH="m01s00i004"), - ] - pp_filter = _convert_constraints(constraints) - self.assertIsNone(pp_filter) - - def test_no_stash(self): - constraints = [ - iris.Constraint("air_potential_temperature"), - iris.AttributeConstraint(source="asource"), - ] - pp_filter = _convert_constraints(constraints) - self.assertIsNone(pp_filter) - - def test_no_constraint(self): - constraints = [] - pp_filter = _convert_constraints(constraints) - self.assertIsNone(pp_filter) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test__create_field_data.py b/lib/iris/tests/unit/fileformats/pp/test__create_field_data.py deleted file mode 100644 index 16d2b500a5..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test__create_field_data.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp._create_field_data` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -import iris.fileformats.pp as pp - - -class Test__create_field_data(tests.IrisTest): - def test_loaded_bytes(self): - # Check that a field with LoadedArrayBytes in core_data gets the - # result of a suitable call to _data_bytes_to_shaped_array(). - mock_loaded_bytes = mock.Mock(spec=pp.LoadedArrayBytes) - core_data = mock.MagicMock(return_value=mock_loaded_bytes) - field = mock.Mock(core_data=core_data) - data_shape = mock.Mock() - land_mask = mock.Mock() - with mock.patch( - "iris.fileformats.pp._data_bytes_to_shaped_array" - ) as convert_bytes: - convert_bytes.return_value = mock.sentinel.array - pp._create_field_data(field, data_shape, land_mask) - - self.assertIs(field.data, mock.sentinel.array) - convert_bytes.assert_called_once_with( - mock_loaded_bytes.bytes, - field.lbpack, - field.boundary_packing, - data_shape, - mock_loaded_bytes.dtype, - field.bmdi, - land_mask, - ) - - def test_deferred_bytes(self): - # Check that a field with deferred array bytes in core_data gets a - # dask array. - fname = mock.sentinel.fname - position = mock.sentinel.position - n_bytes = mock.sentinel.n_bytes - newbyteorder = mock.Mock(return_value=mock.sentinel.dtype) - dtype = mock.Mock(newbyteorder=newbyteorder) - deferred_bytes = (fname, position, n_bytes, dtype) - core_data = mock.MagicMock(return_value=deferred_bytes) - field = mock.Mock(core_data=core_data) - data_shape = (100, 120) - proxy = mock.Mock( - dtype=np.dtype("f4"), - shape=data_shape, - spec=pp.PPDataProxy, - ndim=len(data_shape), - ) - # We can't directly inspect the concrete data source underlying - # the dask array, so instead we patch the proxy creation and check it's - # being created and invoked correctly. - with mock.patch("iris.fileformats.pp.PPDataProxy") as PPDataProxy: - PPDataProxy.return_value = proxy - pp._create_field_data(field, data_shape, land_mask_field=None) - # The data should be assigned via field.data. As this is a mock object - # we can check the attribute directly. - self.assertEqual(field.data.shape, data_shape) - self.assertEqual(field.data.dtype, np.dtype("f4")) - # Is it making use of a correctly configured proxy? - # NB. We know it's *using* the result of this call because - # that's where the dtype came from above. - PPDataProxy.assert_called_once_with( - (data_shape), - dtype, - fname, - position, - n_bytes, - field.raw_lbpack, - field.boundary_packing, - field.bmdi, - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py b/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py deleted file mode 100644 index 73913c6219..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test__data_bytes_to_shaped_array.py +++ /dev/null @@ -1,209 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the `iris.fileformats.pp._data_bytes_to_shaped_array` function. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import io -from unittest import mock - -import numpy as np -import numpy.ma as ma -import pytest - -import iris.fileformats.pp as pp - - -@pytest.mark.parametrize("data_shape", [(2, 3)]) -@pytest.mark.parametrize( - "expected_shape", [(2, 3), (3, 2), (1, 3), (2, 2), (3, 3), (2, 4)] -) -@pytest.mark.parametrize( - "data_type", [np.float32, np.int32, np.int16, np.int8] -) -def test_data_padding__no_compression(data_shape, expected_shape, data_type): - data = np.empty(data_shape, dtype=data_type) - - # create the field data buffer - buffer = io.BytesIO() - buffer.write(data) - buffer.seek(0) - data_bytes = buffer.read() - - lbpack = pp.SplittableInt(0, dict(n1=0, n2=1)) - boundary_packing = None - mdi = -1 - args = ( - data_bytes, - lbpack, - boundary_packing, - expected_shape, - data_type, - mdi, - ) - data_length, expected_length = np.prod(data_shape), np.prod(expected_shape) - - if expected_length <= data_length: - result = pp._data_bytes_to_shaped_array(*args) - assert result.shape == expected_shape - else: - emsg = r"data containing \d+ words does not match expected length" - with pytest.raises(ValueError, match=emsg): - _ = pp._data_bytes_to_shaped_array(*args) - - -class Test__data_bytes_to_shaped_array__lateral_boundary_compression( - tests.IrisTest -): - def setUp(self): - self.data_shape = 30, 40 - y_halo, x_halo, rim = 2, 3, 4 - - data_len = np.prod(self.data_shape) - decompressed = np.arange(data_len).reshape(*self.data_shape) - decompressed *= np.arange(self.data_shape[1]) % 3 + 1 - - decompressed_mask = np.zeros(self.data_shape, np.bool_) - decompressed_mask[ - y_halo + rim : -(y_halo + rim), x_halo + rim : -(x_halo + rim) - ] = True - - self.decompressed = ma.masked_array( - decompressed, mask=decompressed_mask - ) - - self.north = decompressed[-(y_halo + rim) :, :] - self.east = decompressed[ - y_halo + rim : -(y_halo + rim), -(x_halo + rim) : - ] - self.south = decompressed[: y_halo + rim, :] - self.west = decompressed[ - y_halo + rim : -(y_halo + rim), : x_halo + rim - ] - - # Get the bytes of the north, east, south, west arrays combined. - buf = io.BytesIO() - buf.write(self.north.copy()) - buf.write(self.east.copy()) - buf.write(self.south.copy()) - buf.write(self.west.copy()) - buf.seek(0) - self.data_payload_bytes = buf.read() - - def test_boundary_decompression(self): - boundary_packing = mock.Mock(rim_width=4, x_halo=3, y_halo=2) - lbpack = mock.Mock(n1=0) - r = pp._data_bytes_to_shaped_array( - self.data_payload_bytes, - lbpack, - boundary_packing, - self.data_shape, - self.decompressed.dtype, - -9223372036854775808, - ) - r = ma.masked_array(r, np.isnan(r), fill_value=-9223372036854775808) - self.assertMaskedArrayEqual(r, self.decompressed) - - -class Test__data_bytes_to_shaped_array__land_packed(tests.IrisTest): - def setUp(self): - # Sets up some useful arrays for use with the land/sea mask - # decompression. - self.land = np.array( - [[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1]], dtype=np.float64 - ) - sea = ~self.land.astype(np.bool_) - self.land_masked_data = np.array([1, 3, 4.5]) - self.sea_masked_data = np.array([1, 3, 4.5, -4, 5, 0, 1, 2, 3]) - - # Compute the decompressed land mask data. - self.decomp_land_data = ma.masked_array( - [[0, 1, 0, 0], [3, 0, 0, 0], [0, 0, 0, 4.5]], - mask=sea, - dtype=np.float64, - ) - # Compute the decompressed sea mask data. - self.decomp_sea_data = ma.masked_array( - [[1, -10, 3, 4.5], [-10, -4, 5, 0], [1, 2, 3, -10]], - mask=self.land, - dtype=np.float64, - ) - - self.land_mask = mock.Mock( - data=self.land, lbrow=self.land.shape[0], lbnpt=self.land.shape[1] - ) - - def create_lbpack(self, value): - name_mapping = dict(n5=slice(4, None), n4=3, n3=2, n2=1, n1=0) - return pp.SplittableInt(value, name_mapping) - - def test_no_land_mask(self): - # Check that without a mask, it returns the raw (compressed) data. - with mock.patch("numpy.frombuffer", return_value=np.arange(3)): - result = pp._data_bytes_to_shaped_array( - mock.Mock(), - self.create_lbpack(120), - None, - (3, 4), - np.dtype(">f4"), - -999, - mask=None, - ) - self.assertArrayAllClose(result, np.arange(3)) - - def test_land_mask(self): - # Check basic land unpacking. - field_data = self.land_masked_data - result = self.check_read_data(field_data, 120, self.land_mask) - self.assertMaskedArrayEqual(result, self.decomp_land_data) - - def test_land_masked_data_too_long(self): - # Check land unpacking with field data that is larger than the mask. - field_data = np.tile(self.land_masked_data, 2) - result = self.check_read_data(field_data, 120, self.land_mask) - self.assertMaskedArrayEqual(result, self.decomp_land_data) - - def test_sea_mask(self): - # Check basic land unpacking. - field_data = self.sea_masked_data - result = self.check_read_data(field_data, 220, self.land_mask) - self.assertMaskedArrayEqual(result, self.decomp_sea_data) - - def test_sea_masked_data_too_long(self): - # Check sea unpacking with field data that is larger than the mask. - field_data = np.tile(self.sea_masked_data, 2) - result = self.check_read_data(field_data, 220, self.land_mask) - self.assertMaskedArrayEqual(result, self.decomp_sea_data) - - def test_bad_lbpack(self): - # Check basic land unpacking. - field_data = self.sea_masked_data - with self.assertRaises(ValueError): - self.check_read_data(field_data, 320, self.land_mask) - - def check_read_data(self, field_data, lbpack, mask): - # Calls pp._data_bytes_to_shaped_array with the necessary mocked - # items, an lbpack instance, the correct data shape and mask instance. - with mock.patch("numpy.frombuffer", return_value=field_data): - data = pp._data_bytes_to_shaped_array( - mock.Mock(), - self.create_lbpack(lbpack), - None, - mask.shape, - np.dtype(">f4"), - -999, - mask=mask, - ) - return ma.masked_array(data, np.isnan(data), fill_value=-999) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test__field_gen.py b/lib/iris/tests/unit/fileformats/pp/test__field_gen.py deleted file mode 100644 index 31ac4f6b19..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test__field_gen.py +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp._field_gen` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import contextlib -import io -from unittest import mock -import warnings - -import numpy as np - -import iris.fileformats.pp as pp - - -class Test(tests.IrisTest): - @contextlib.contextmanager - def mock_for_field_gen(self, fields): - side_effect_fields = list(fields)[:] - - def make_pp_field_override(*args): - # Iterates over the fields passed to this context manager, - # until there are no more, upon which the np.fromfile - # returns an empty list and the while loop in load() is - # broken. - result = side_effect_fields.pop(0) - if not side_effect_fields: - np.fromfile.return_value = [] - return result - - open_func = "builtins.open" - with mock.patch("numpy.fromfile", return_value=[0]), mock.patch( - open_func - ), mock.patch("struct.unpack_from", return_value=[4]), mock.patch( - "iris.fileformats.pp.make_pp_field", - side_effect=make_pp_field_override, - ): - yield - - def gen_fields(self, fields): - with self.mock_for_field_gen(fields): - return list(pp._field_gen("mocked", "mocked")) - - def test_lblrec_invalid(self): - pp_field = mock.Mock(lblrec=2, lbext=0) - with warnings.catch_warnings(record=True) as warn: - warnings.simplefilter("always") - self.gen_fields([pp_field]) - self.assertEqual(len(warn), 1) - wmsg = ( - "LBLREC has a different value to the .* the header in the " - r"file \(8 and 4\)\. Skipping .*" - ) - self.assertRegex(str(warn[0].message), wmsg) - - def test_read_headers_call(self): - # Checks that the two calls to np.fromfile are called in the - # expected way. - pp_field = mock.Mock(lblrec=1, lbext=0, lbuser=[0]) - with self.mock_for_field_gen([pp_field]): - open_fh = mock.MagicMock(spec=io.RawIOBase) - open.return_value = open_fh - next(pp._field_gen("mocked", read_data_bytes=False)) - with open_fh as open_fh_ctx: - calls = [ - mock.call(open_fh_ctx, count=45, dtype=">i4"), - mock.call(open_fh_ctx, count=19, dtype=">f4"), - ] - np.fromfile.assert_has_calls(calls) - with open_fh as open_fh_ctx: - expected_deferred_bytes = ( - "mocked", - open_fh_ctx.tell(), - 4, - np.dtype(">f4"), - ) - self.assertEqual(pp_field.data, expected_deferred_bytes) - - def test_read_data_call(self): - # Checks that data is read if read_data is True. - pp_field = mock.Mock(lblrec=1, lbext=0, lbuser=[0]) - with self.mock_for_field_gen([pp_field]): - open_fh = mock.MagicMock(spec=io.RawIOBase) - open.return_value = open_fh - next(pp._field_gen("mocked", read_data_bytes=True)) - with open_fh as open_fh_ctx: - expected_loaded_bytes = pp.LoadedArrayBytes( - open_fh_ctx.read(), np.dtype(">f4") - ) - self.assertEqual(pp_field.data, expected_loaded_bytes) - - def test_invalid_header_release(self): - # Check that an unknown LBREL value just results in a warning - # and the end of the file iteration instead of raising an error. - with self.temp_filename() as temp_path: - np.zeros(65, dtype="i4").tofile(temp_path) - generator = pp._field_gen(temp_path, False) - with mock.patch("warnings.warn") as warn: - with self.assertRaises(StopIteration): - next(generator) - self.assertEqual(warn.call_count, 1) - self.assertIn("header release number", warn.call_args[0][0]) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test__interpret_field.py b/lib/iris/tests/unit/fileformats/pp/test__interpret_field.py deleted file mode 100644 index 0b83cade76..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test__interpret_field.py +++ /dev/null @@ -1,138 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp._interpret_field` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from copy import deepcopy -from unittest import mock - -import numpy as np - -import iris -import iris.fileformats.pp as pp - - -class Test__interpret_fields__land_packed_fields(tests.IrisTest): - def setUp(self): - return_value = ("dummy", 0, 0, np.dtype("f4")) - core_data = mock.MagicMock(return_value=return_value) - # A field packed using a land/sea mask. - self.pp_field = mock.Mock( - lblrec=1, - lbext=0, - lbuser=[0] * 7, - lbrow=0, - lbnpt=0, - raw_lbpack=21, - lbpack=mock.Mock(n1=0, n2=2, n3=1), - core_data=core_data, - ) - # The field specifying the land/seamask. - lbuser = [None, None, None, 30, None, None, 1] # m01s00i030 - self.land_mask_field = mock.Mock( - lblrec=1, - lbext=0, - lbuser=lbuser, - lbrow=3, - lbnpt=4, - raw_lbpack=0, - core_data=core_data, - ) - - def test_non_deferred_fix_lbrow_lbnpt(self): - # Checks the fix_lbrow_lbnpt is applied to fields which are not - # deferred. - f1, mask = self.pp_field, self.land_mask_field - self.assertEqual(f1.lbrow, 0) - self.assertEqual(f1.lbnpt, 0) - list(pp._interpret_fields([mask, f1])) - self.assertEqual(f1.lbrow, 3) - self.assertEqual(f1.lbnpt, 4) - # Check the data's shape has been updated too. - self.assertEqual(f1.data.shape, (3, 4)) - - def test_fix_lbrow_lbnpt_no_mask_available(self): - # Check a warning is issued when loading a land masked field - # without a land mask. - with mock.patch("warnings.warn") as warn: - list(pp._interpret_fields([self.pp_field])) - self.assertEqual(warn.call_count, 1) - warn_msg = warn.call_args[0][0] - self.assertTrue( - warn_msg.startswith( - "Landmask compressed fields " "existed without a landmask" - ), - "Unexpected warning message: {!r}".format(warn_msg), - ) - - def test_deferred_mask_field(self): - # Check that the order of the load is yielded last if the mask - # hasn't yet been seen. - result = list( - pp._interpret_fields([self.pp_field, self.land_mask_field]) - ) - self.assertEqual(result, [self.land_mask_field, self.pp_field]) - - def test_not_deferred_mask_field(self): - # Check that the order of the load is unchanged if a land mask - # has already been seen. - f1, mask = self.pp_field, self.land_mask_field - mask2 = deepcopy(mask) - result = list(pp._interpret_fields([mask, f1, mask2])) - self.assertEqual(result, [mask, f1, mask2]) - - def test_deferred_fix_lbrow_lbnpt(self): - # Check the fix is also applied to fields which are deferred. - f1, mask = self.pp_field, self.land_mask_field - list(pp._interpret_fields([f1, mask])) - self.assertEqual(f1.lbrow, 3) - self.assertEqual(f1.lbnpt, 4) - - @tests.skip_data - def test_landsea_unpacking_uses_dask(self): - # Ensure that the graph of the (lazy) landsea-masked data contains an - # explicit reference to a (lazy) landsea-mask field. - # Otherwise its compute() will need to invoke another compute(). - # See https://github.com/SciTools/iris/issues/3237 - - # This is too complex to explore in a mock-ist way, so let's load a - # tiny bit of real data ... - testfile_path = tests.get_data_path( - ["FF", "landsea_masked", "testdata_mini_lsm.ff"] - ) - landsea_mask, soil_temp = iris.load_cubes( - testfile_path, ("land_binary_mask", "soil_temperature") - ) - - # Now check that the soil-temp dask graph correctly references the - # landsea mask, in its dask graph. - lazy_mask_array = landsea_mask.core_data() - lazy_soildata_array = soil_temp.core_data() - - # Work out the main dask key for the mask data, as used by 'compute()'. - mask_toplev_key = (lazy_mask_array.name,) + (0,) * lazy_mask_array.ndim - # Get the 'main' calculation entry. - mask_toplev_item = lazy_mask_array.dask[mask_toplev_key] - # This should be a task (a simple fetch). - self.assertTrue(callable(mask_toplev_item[0])) - # Get the key (name) of the array that it fetches. - mask_data_name = mask_toplev_item[1] - - # Check that the item this refers to is a PPDataProxy. - self.assertIsInstance( - lazy_mask_array.dask[mask_data_name], pp.PPDataProxy - ) - - # Check that the soil-temp graph references the *same* lazy element, - # showing that the mask+data calculation is handled by dask. - self.assertIn(mask_data_name, lazy_soildata_array.dask.keys()) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test_as_fields.py b/lib/iris/tests/unit/fileformats/pp/test_as_fields.py deleted file mode 100644 index 3ff228e106..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test_as_fields.py +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.as_fields` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris.fileformats.pp as pp -import iris.tests.stock as stock - - -class TestAsFields(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_3d() - - def test_cube_only(self): - fields = pp.as_fields(self.cube) - for field in fields: - self.assertEqual(field.lbcode, 101) - - def test_field_coords(self): - fields = pp.as_fields( - self.cube, field_coords=["grid_longitude", "grid_latitude"] - ) - for field in fields: - self.assertEqual(field.lbcode, 101) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test_load.py b/lib/iris/tests/unit/fileformats/pp/test_load.py deleted file mode 100644 index 77da1288c2..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test_load.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.load` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris.fileformats.pp as pp - - -class Test_load(tests.IrisTest): - def test_call_structure(self): - # Check that the load function calls the two necessary utility - # functions. - extract_result = mock.Mock() - interpret_patch = mock.patch( - "iris.fileformats.pp._interpret_fields", - autospec=True, - return_value=iter([]), - ) - field_gen_patch = mock.patch( - "iris.fileformats.pp._field_gen", - autospec=True, - return_value=extract_result, - ) - with interpret_patch as interpret, field_gen_patch as field_gen: - pp.load("mock", read_data=True) - - interpret.assert_called_once_with(extract_result) - field_gen.assert_called_once_with( - "mock", read_data_bytes=True, little_ended=False - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test_save.py b/lib/iris/tests/unit/fileformats/pp/test_save.py deleted file mode 100644 index 8200259cca..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test_save.py +++ /dev/null @@ -1,362 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.save` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import cf_units -import cftime -import numpy as np -import pytest - -from iris.coords import CellMethod, DimCoord -from iris.fileformats._ff_cross_references import STASH_TRANS -import iris.fileformats.pp as pp -from iris.fileformats.pp_save_rules import _lbproc_rules, verify -import iris.tests.stock as stock - - -@pytest.mark.parametrize( - "unit,modulus", - [ - (cf_units.Unit("radians"), 2 * np.pi), - (cf_units.Unit("degrees"), 360.0), - (None, 360.0), - ], -) -def test_grid_and_pole__scalar_dim_longitude(unit, modulus): - cube = stock.lat_lon_cube()[:, -1:] - assert cube.ndim == 2 - lon = cube.coord("longitude") - lon.units = unit - - field = _pp_save_ppfield_values(cube) - bdx = modulus - assert field.bdx == bdx - assert field.bzx == (lon.points[0] - bdx) - assert field.lbnpt == lon.points.size - - -def _pp_save_ppfield_values(cube): - """ - Emulate saving a cube as PP, and capture the resulting PP field values. - - """ - # Create a test object to stand in for a real PPField. - pp_field = mock.MagicMock(spec=pp.PPField3) - # Add minimal content required by the pp.save operation. - pp_field.HEADER_DEFN = pp.PPField3.HEADER_DEFN - # Save cube to a dummy file, mocking the internally created PPField - with mock.patch("iris.fileformats.pp.PPField3", return_value=pp_field): - target_filelike = mock.Mock(name="target") - target_filelike.mode = "b" - pp.save(cube, target_filelike) - # Return pp-field mock with all the written properties - return pp_field - - -class TestVertical(tests.IrisTest): - def setUp(self): - self.cube = stock.lat_lon_cube() - - def test_pseudo_level(self): - pseudo_level = 123 - coord = DimCoord(pseudo_level, long_name="pseudo_level", units="1") - self.cube.add_aux_coord(coord) - lbuser5_produced = _pp_save_ppfield_values(self.cube).lbuser[4] - self.assertEqual(pseudo_level, lbuser5_produced) - - def test_soil_level(self): - soil_level = 314 - coord = DimCoord(soil_level, long_name="soil_model_level_number") - self.cube.add_aux_coord(coord) - self.cube.standard_name = "moisture_content_of_soil_layer" - field = _pp_save_ppfield_values(self.cube) - self.assertEqual(field.lbvc, 6) - self.assertEqual(field.lblev, soil_level) - self.assertEqual(field.blev, soil_level) - self.assertEqual(field.brsvd[0], 0) - self.assertEqual(field.brlev, 0) - - def test_soil_depth(self): - lower, point, upper = 1, 2, 3 - coord = DimCoord(point, standard_name="depth", bounds=[[lower, upper]]) - self.cube.add_aux_coord(coord) - self.cube.standard_name = "moisture_content_of_soil_layer" - field = _pp_save_ppfield_values(self.cube) - self.assertEqual(field.lbvc, 6) - self.assertEqual(field.lblev, 0) - self.assertEqual(field.blev, point) - self.assertEqual(field.brsvd[0], lower) - self.assertEqual(field.brlev, upper) - - -class TestLbfcProduction(tests.IrisTest): - def setUp(self): - self.cube = stock.lat_lon_cube() - - def check_cube_stash_yields_lbfc(self, stash, lbfc_expected): - if stash: - self.cube.attributes["STASH"] = stash - lbfc_produced = _pp_save_ppfield_values(self.cube).lbfc - self.assertEqual(lbfc_produced, lbfc_expected) - - def test_known_stash(self): - stashcode_str = "m04s07i002" - self.assertIn(stashcode_str, STASH_TRANS) - self.check_cube_stash_yields_lbfc(stashcode_str, 359) - - def test_unknown_stash(self): - stashcode_str = "m99s99i999" - self.assertNotIn(stashcode_str, STASH_TRANS) - self.check_cube_stash_yields_lbfc(stashcode_str, 0) - - def test_no_stash(self): - self.assertNotIn("STASH", self.cube.attributes) - self.check_cube_stash_yields_lbfc(None, 0) - - def check_cube_name_units_yields_lbfc(self, name, units, lbfc_expected): - self.cube.rename(name) - self.cube.units = units - lbfc_produced = _pp_save_ppfield_values(self.cube).lbfc - self.assertEqual( - lbfc_produced, - lbfc_expected, - "Lbfc for ({!r} / {!r}) should be {:d}, " - "got {:d}".format(name, units, lbfc_expected, lbfc_produced), - ) - - def test_name_units_to_lbfc(self): - # Check LBFC value produced from name and units. - self.check_cube_name_units_yields_lbfc("sea_ice_temperature", "K", 209) - - def test_bad_name_units_to_lbfc_0(self): - # Check that badly-formed / unrecognised cases yield LBFC == 0. - self.check_cube_name_units_yields_lbfc( - "sea_ice_temperature", "degC", 0 - ) - self.check_cube_name_units_yields_lbfc("Junk_Name", "K", 0) - - -class TestLbsrceProduction(tests.IrisTest): - def setUp(self): - self.cube = stock.lat_lon_cube() - - def check_cube_um_source_yields_lbsrce( - self, source_str=None, um_version_str=None, lbsrce_expected=None - ): - if source_str is not None: - self.cube.attributes["source"] = source_str - if um_version_str is not None: - self.cube.attributes["um_version"] = um_version_str - lbsrce_produced = _pp_save_ppfield_values(self.cube).lbsrce - self.assertEqual(lbsrce_produced, lbsrce_expected) - - def test_none(self): - self.check_cube_um_source_yields_lbsrce(None, None, 0) - - def test_source_only_no_version(self): - self.check_cube_um_source_yields_lbsrce( - "Data from Met Office Unified Model", None, 1111 - ) - - def test_source_only_with_version(self): - self.check_cube_um_source_yields_lbsrce( - "Data from Met Office Unified Model 12.17", None, 12171111 - ) - - def test_um_version(self): - self.check_cube_um_source_yields_lbsrce( - "Data from Met Office Unified Model 12.17", "25.36", 25361111 - ) - - -class Test_Save__LbprocProduction(tests.IrisTest): - # This test class is a little different to the others. - # If it called `pp.save` via `_pp_save_ppfield_values` it would run - # `pp_save_rules.verify` and run all the save rules. As this class uses - # a 3D cube with a time coord it would run the time rules, which would fail - # because the mock object does not set up the `pp.lbtim` attribute - # correctly (i.e. as a `SplittableInt` object). - # To work around this we call the lbproc rules directly here. - - def setUp(self): - self.cube = stock.realistic_3d() - self.pp_field = mock.MagicMock(spec=pp.PPField3) - self.pp_field.HEADER_DEFN = pp.PPField3.HEADER_DEFN - self.patch("iris.fileformats.pp.PPField3", return_value=self.pp_field) - - def test_no_cell_methods(self): - lbproc = _lbproc_rules(self.cube, self.pp_field).lbproc - self.assertEqual(lbproc, 0) - - def test_mean(self): - self.cube.cell_methods = (CellMethod("mean", "time", "1 hour"),) - lbproc = _lbproc_rules(self.cube, self.pp_field).lbproc - self.assertEqual(lbproc, 128) - - def test_minimum(self): - self.cube.cell_methods = (CellMethod("minimum", "time", "1 hour"),) - lbproc = _lbproc_rules(self.cube, self.pp_field).lbproc - self.assertEqual(lbproc, 4096) - - def test_maximum(self): - self.cube.cell_methods = (CellMethod("maximum", "time", "1 hour"),) - lbproc = _lbproc_rules(self.cube, self.pp_field).lbproc - self.assertEqual(lbproc, 8192) - - -class TestTimeMean(tests.IrisTest): - """ - Tests that time mean cell method is converted to pp appropriately. - - Pattern is pairs of tests - one with time mean method, and one without, to - show divergent behaviour. - - """ - - def test_t1_time_mean(self): - cube = _get_single_time_cube(set_time_mean=True) - tc = cube.coord(axis="t") - expected = tc.units.num2date(0) - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual = pp_field.t1 - - self.assertEqual(expected, actual) - - def test_t1_no_time_mean(self): - cube = _get_single_time_cube() - tc = cube.coord(axis="t") - expected = tc.units.num2date(15) - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual = pp_field.t1 - - self.assertEqual(expected, actual) - - def test_t2_time_mean(self): - cube = _get_single_time_cube(set_time_mean=True) - tc = cube.coord(axis="t") - expected = tc.units.num2date(30) - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual = pp_field.t2 - - self.assertEqual(expected, actual) - - def test_t2_no_time_mean(self): - cube = _get_single_time_cube(set_time_mean=False) - expected = cftime.datetime(0, 0, 0, calendar=None, has_year_zero=True) - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual = pp_field.t2 - self.assertEqual(expected, actual) - - def test_lbft_no_forecast_time(self): - # Different pattern here: checking that lbft hasn't been changed from - # the default value. - cube = _get_single_time_cube() - mock_lbft = mock.sentinel.lbft - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - pp_field.lbft = mock_lbft - verify(cube, pp_field) - actual = pp_field.lbft - - assert mock_lbft is actual - - def test_lbtim_no_time_mean(self): - cube = _get_single_time_cube() - expected_ib = 0 - expected_ic = 2 # 360 day calendar - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual_ib = pp_field.lbtim.ib - actual_ic = pp_field.lbtim.ic - - self.assertEqual(expected_ib, actual_ib) - self.assertEqual(expected_ic, actual_ic) - - def test_lbtim_time_mean(self): - cube = _get_single_time_cube(set_time_mean=True) - expected_ib = 2 # Time mean - expected_ic = 2 # 360 day calendar - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual_ib = pp_field.lbtim.ib - actual_ic = pp_field.lbtim.ic - - self.assertEqual(expected_ib, actual_ib) - self.assertEqual(expected_ic, actual_ic) - - def test_lbproc_no_time_mean(self): - cube = _get_single_time_cube() - expected = 0 - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual = pp_field.lbproc - - self.assertEqual(expected, actual) - - def test_lbproc_time_mean(self): - cube = _get_single_time_cube(set_time_mean=True) - expected = 128 - - with mock.patch( - "iris.fileformats.pp.PPField3", autospec=True - ) as pp_field: - verify(cube, pp_field) - actual = pp_field.lbproc - - self.assertEqual(expected, actual) - - -def _get_single_time_cube(set_time_mean=False): - cube = stock.realistic_3d()[0:1, :, :] - cube.remove_coord("time") - cube.remove_coord("forecast_period") - tc = DimCoord( - points=[15], - standard_name="time", - units=cf_units.Unit("days since epoch", calendar="360_day"), - bounds=[[0, 30]], - ) - cube.add_dim_coord(tc, 0) - if set_time_mean: - cube.cell_methods = (CellMethod("mean", coords="time"),) - return cube - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test_save_fields.py b/lib/iris/tests/unit/fileformats/pp/test_save_fields.py deleted file mode 100644 index fdd470cb47..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test_save_fields.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.save_fields` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -import iris.fileformats.pp as pp - - -def asave(afilehandle): - afilehandle.write("saved") - - -class TestSaveFields(tests.IrisTest): - def setUp(self): - # Create a test object to stand in for a real PPField. - self.pp_field = mock.MagicMock(spec=pp.PPField3) - # Add minimal content required by the pp.save operation. - self.pp_field.HEADER_DEFN = pp.PPField3.HEADER_DEFN - self.pp_field.data = np.zeros((1, 1)) - self.pp_field.save = asave - - def test_save(self): - open_func = "builtins.open" - m = mock.mock_open() - with mock.patch(open_func, m, create=True): - pp.save_fields([self.pp_field], "foo.pp") - self.assertTrue(mock.call("foo.pp", "wb") in m.mock_calls) - self.assertTrue(mock.call().write("saved") in m.mock_calls) - - def test_save_append(self): - open_func = "builtins.open" - m = mock.mock_open() - with mock.patch(open_func, m, create=True): - pp.save_fields([self.pp_field], "foo.pp", append=True) - self.assertTrue(mock.call("foo.pp", "ab") in m.mock_calls) - self.assertTrue(mock.call().write("saved") in m.mock_calls) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp/test_save_pairs_from_cube.py b/lib/iris/tests/unit/fileformats/pp/test_save_pairs_from_cube.py deleted file mode 100644 index cdd3c9cd49..0000000000 --- a/lib/iris/tests/unit/fileformats/pp/test_save_pairs_from_cube.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.pp.save_pairs_from_cube` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.fileformats.pp import save_pairs_from_cube -import iris.tests.stock as stock - - -class TestSaveFields(tests.IrisTest): - def setUp(self): - self.cube = stock.realistic_3d() - - def test_cube_only(self): - slices_and_fields = save_pairs_from_cube(self.cube) - for aslice, field in slices_and_fields: - self.assertEqual(aslice.shape, (9, 11)) - self.assertEqual(field.lbcode, 101) - - def test_field_coords(self): - slices_and_fields = save_pairs_from_cube( - self.cube, field_coords=["grid_longitude", "grid_latitude"] - ) - for aslice, field in slices_and_fields: - self.assertEqual(aslice.shape, (11, 9)) - self.assertEqual(field.lbcode, 101) - - def test_lazy_data(self): - cube = self.cube.copy() - # "Rebase" the cube onto a lazy version of its data. - cube.data = cube.lazy_data() - # Check that lazy data is preserved in save-pairs generation. - slices_and_fields = save_pairs_from_cube(cube) - for aslice, _ in slices_and_fields: - self.assertTrue(aslice.has_lazy_data()) - - def test_default_bmdi(self): - slices_and_fields = save_pairs_from_cube(self.cube) - _, field = next(slices_and_fields) - self.assertEqual(field.bmdi, -1e30) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py b/lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py deleted file mode 100644 index 70d28f7c09..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.pp_load_rules` module.""" diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py deleted file mode 100644 index e194e240c6..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__all_other_rules.py +++ /dev/null @@ -1,306 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the `iris.fileformats.pp_load_rules._all_other_rules` function. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from cf_units import CALENDAR_360_DAY, Unit -from cftime import datetime as nc_datetime -import numpy as np - -from iris.coords import AuxCoord, CellMethod, DimCoord -from iris.fileformats.pp import SplittableInt -from iris.fileformats.pp_load_rules import _all_other_rules -from iris.tests.unit.fileformats import TestField - -# iris.fileformats.pp_load_rules._all_other_rules() returns a tuple of -# of various metadata. This constant is the index into this -# tuple to obtain the cell methods. -CELL_METHODS_INDEX = 5 -DIM_COORDS_INDEX = 6 -AUX_COORDS_INDEX = 7 - - -class TestCellMethods(tests.IrisTest): - def test_time_mean(self): - # lbproc = 128 -> mean - # lbtim.ib = 2 -> simple t1 to t2 interval. - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=0, ib=2, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("mean", "time")] - self.assertEqual(res, expected) - - def test_hourly_mean(self): - # lbtim.ia = 1 -> hourly - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=1, ib=2, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("mean", "time", "1 hour")] - self.assertEqual(res, expected) - - def test_daily_mean(self): - # lbtim.ia = 24 -> daily - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=24, ib=2, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("mean", "time", "24 hour")] - self.assertEqual(res, expected) - - def test_custom_max(self): - field = mock.MagicMock(lbproc=8192, lbtim=mock.Mock(ia=47, ib=2, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("maximum", "time", "47 hour")] - self.assertEqual(res, expected) - - def test_daily_min(self): - # lbproc = 4096 -> min - field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=2, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("minimum", "time", "24 hour")] - self.assertEqual(res, expected) - - def test_time_mean_over_multiple_years(self): - # lbtim.ib = 3 -> interval within a year, over multiple years. - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=0, ib=3, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [ - CellMethod("mean within years", "time"), - CellMethod("mean over years", "time"), - ] - self.assertEqual(res, expected) - - def test_hourly_mean_over_multiple_years(self): - field = mock.MagicMock(lbproc=128, lbtim=mock.Mock(ia=1, ib=3, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [ - CellMethod("mean within years", "time", "1 hour"), - CellMethod("mean over years", "time"), - ] - self.assertEqual(res, expected) - - def test_climatology_max(self): - field = mock.MagicMock(lbproc=8192, lbtim=mock.Mock(ia=24, ib=3, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("maximum", "time")] - self.assertEqual(res, expected) - - def test_climatology_min(self): - field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=3, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("minimum", "time")] - self.assertEqual(res, expected) - - def test_other_lbtim_ib(self): - # lbtim.ib = 5 -> non-specific aggregation - field = mock.MagicMock(lbproc=4096, lbtim=mock.Mock(ia=24, ib=5, ic=3)) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [CellMethod("minimum", "time")] - self.assertEqual(res, expected) - - def test_multiple_unordered_lbprocs(self): - field = mock.MagicMock( - lbproc=192, - bzx=0, - bdx=1, - lbnpt=3, - lbrow=3, - lbtim=mock.Mock(ia=24, ib=5, ic=3), - lbcode=SplittableInt(1), - x_bounds=None, - _x_coord_name=lambda: "longitude", - _y_coord_name=lambda: "latitude", - ) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [ - CellMethod("mean", "time"), - CellMethod("mean", "longitude"), - ] - self.assertEqual(res, expected) - - def test_multiple_unordered_rotated_lbprocs(self): - field = mock.MagicMock( - lbproc=192, - bzx=0, - bdx=1, - lbnpt=3, - lbrow=3, - lbtim=mock.Mock(ia=24, ib=5, ic=3), - lbcode=SplittableInt(101), - x_bounds=None, - _x_coord_name=lambda: "grid_longitude", - _y_coord_name=lambda: "grid_latitude", - ) - res = _all_other_rules(field)[CELL_METHODS_INDEX] - expected = [ - CellMethod("mean", "time"), - CellMethod("mean", "grid_longitude"), - ] - self.assertEqual(res, expected) - - -class TestCrossSectionalTime(TestField): - def test_lbcode3x23(self): - time_bounds = np.array( - [[0.875, 1.125], [1.125, 1.375], [1.375, 1.625], [1.625, 1.875]] - ) - field = mock.MagicMock( - lbproc=0, - bzx=0, - bdx=0, - lbnpt=3, - lbrow=4, - t1=nc_datetime(2000, 1, 2, hour=0, minute=0, second=0), - t2=nc_datetime(2000, 1, 3, hour=0, minute=0, second=0), - lbtim=mock.Mock(ia=1, ib=2, ic=2), - lbcode=SplittableInt( - 31323, {"iy": slice(0, 2), "ix": slice(2, 4)} - ), - x_bounds=None, - y_bounds=time_bounds, - _x_coord_name=lambda: "longitude", - _y_coord_name=lambda: "latitude", - ) - - spec = [ - "lbtim", - "lbcode", - "lbrow", - "lbnpt", - "lbproc", - "lbsrce", - "lbuser", - "bzx", - "bdx", - "bdy", - "bmdi", - "t1", - "t2", - "stash", - "x_bounds", - "y_bounds", - "_x_coord_name", - "_y_coord_name", - ] - field.mock_add_spec(spec) - res = _all_other_rules(field)[DIM_COORDS_INDEX] - - expected_time_points = np.array([1, 1.25, 1.5, 1.75]) + (2000 * 360) - expected_unit = Unit( - "days since 0000-01-01 00:00:00", calendar=CALENDAR_360_DAY - ) - expected = [ - ( - DimCoord( - expected_time_points, - standard_name="time", - units=expected_unit, - bounds=time_bounds, - ), - 0, - ) - ] - self.assertCoordsAndDimsListsMatch(res, expected) - - -class TestLBTIMx2x_ZeroYears(TestField): - _spec = [ - "lbtim", - "lbcode", - "lbrow", - "lbnpt", - "lbproc", - "lbsrce", - "lbhem", - "lbuser", - "bzx", - "bdx", - "bdy", - "bmdi", - "t1", - "t2", - "stash", - "x_bounds", - "y_bounds", - "_x_coord_name", - "_y_coord_name", - ] - - def _make_field( - self, - lbyr=0, - lbyrd=0, - lbmon=3, - lbmond=3, - lbft=0, - bdx=1, - bdy=1, - bmdi=0, - ia=0, - ib=2, - ic=1, - lbcode=SplittableInt(3), - ): - return mock.MagicMock( - lbyr=lbyr, - lbyrd=lbyrd, - lbmon=lbmon, - lbmond=lbmond, - lbft=lbft, - bdx=bdx, - bdy=bdy, - bmdi=bmdi, - lbtim=mock.Mock(ia=ia, ib=ib, ic=ic), - lbcode=lbcode, - ) - - def test_month_coord(self): - field = self._make_field() - field.mock_add_spec(self._spec) - res = _all_other_rules(field)[AUX_COORDS_INDEX] - - expected = [ - (AuxCoord(3, long_name="month_number", units="1"), None), - (AuxCoord("Mar", long_name="month", units=Unit("no unit")), None), - ( - DimCoord( - points=0, - standard_name="forecast_period", - units=Unit("hours"), - ), - None, - ), - ] - self.assertCoordsAndDimsListsMatch(res, expected) - - def test_diff_month(self): - field = self._make_field(lbmon=3, lbmond=4) - field.mock_add_spec(self._spec) - res = _all_other_rules(field)[AUX_COORDS_INDEX] - - self.assertCoordsAndDimsListsMatch(res, []) - - def test_nonzero_year(self): - field = self._make_field(lbyr=1) - field.mock_add_spec(self._spec) - res = _all_other_rules(field)[AUX_COORDS_INDEX] - - self.assertCoordsAndDimsListsMatch(res, []) - - def test_nonzero_yeard(self): - field = self._make_field(lbyrd=1) - field.mock_add_spec(self._spec) - res = _all_other_rules(field)[AUX_COORDS_INDEX] - - self.assertCoordsAndDimsListsMatch(res, []) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py deleted file mode 100644 index c9c4821e0a..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__collapse_degenerate_points_and_bounds.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._collapse_degenerate_points_and_bounds`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats.pp_load_rules import ( - _collapse_degenerate_points_and_bounds, -) - - -class Test(tests.IrisTest): - def test_scalar(self): - array = np.array(1) - points, bounds = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(points, array) - self.assertIsNone(bounds) - - def test_1d_nochange(self): - array = np.array([1, 1, 3]) - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, array) - - def test_1d_collapse(self): - array = np.array([1, 1, 1]) - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([1])) - - def test_2d_nochange(self): - array = np.array([[1, 2, 3], [4, 5, 6]]) - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, array) - - def test_2d_collapse_dim0(self): - array = np.array([[1, 2, 3], [1, 2, 3]]) - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[1, 2, 3]])) - - def test_2d_collapse_dim1(self): - array = np.array([[1, 1, 1], [2, 2, 2]]) - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[1], [2]])) - - def test_2d_collapse_both(self): - array = np.array([[3, 3, 3], [3, 3, 3]]) - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[3]])) - - def test_3d(self): - array = np.array([[[3, 3, 3], [4, 4, 4]], [[3, 3, 3], [4, 4, 4]]]) - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertArrayEqual(result, np.array([[[3], [4]]])) - - def test_multiple_odd_dims(self): - # Test to ensure multiple collapsed dimensions don't interfere. - # make a 5-D array where dimensions 0, 2 and 3 are degenerate. - array = np.arange(3**5).reshape([3] * 5) - array[1:] = array[0:1] - array[:, :, 1:] = array[:, :, 0:1] - array[:, :, :, 1:] = array[:, :, :, 0:1] - result, _ = _collapse_degenerate_points_and_bounds(array) - self.assertEqual(array.shape, (3, 3, 3, 3, 3)) - self.assertEqual(result.shape, (1, 3, 1, 1, 3)) - self.assertTrue(np.all(result == array[0:1, :, 0:1, 0:1, :])) - - def test_bounds_collapse(self): - points = np.array([1, 1, 1]) - bounds = np.array([[0, 1], [0, 1], [0, 1]]) - result_pts, result_bds = _collapse_degenerate_points_and_bounds( - points, bounds - ) - self.assertArrayEqual(result_pts, np.array([1])) - self.assertArrayEqual(result_bds, np.array([[0, 1]])) - - def test_bounds_no_collapse(self): - points = np.array([1, 1, 1]) - bounds = np.array([[0, 1], [0, 1], [0, 2]]) - result_pts, result_bds = _collapse_degenerate_points_and_bounds( - points, bounds - ) - self.assertArrayEqual(result_pts, points) - self.assertArrayEqual(result_bds, bounds) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py deleted file mode 100644 index d3046ee63e..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_pseudo_level_coords.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._convert_pseudo_level_coords`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coords import DimCoord -from iris.fileformats.pp_load_rules import _convert_scalar_pseudo_level_coords -from iris.tests.unit.fileformats import TestField - - -class Test(TestField): - def test_valid(self): - coords_and_dims = _convert_scalar_pseudo_level_coords(lbuser5=21) - self.assertEqual( - coords_and_dims, - [(DimCoord([21], long_name="pseudo_level", units="1"), None)], - ) - - def test_missing_indicator(self): - coords_and_dims = _convert_scalar_pseudo_level_coords(lbuser5=0) - self.assertEqual(coords_and_dims, []) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py deleted file mode 100644 index 759a399dad..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_scalar_realization_coords.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._convert_scalar_realization_coords`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coords import DimCoord -from iris.fileformats.pp_load_rules import _convert_scalar_realization_coords -from iris.tests.unit.fileformats import TestField - - -class Test(TestField): - def test_valid(self): - coords_and_dims = _convert_scalar_realization_coords(lbrsvd4=21) - self.assertEqual( - coords_and_dims, - [(DimCoord([21], standard_name="realization", units="1"), None)], - ) - - def test_missing_indicator(self): - coords_and_dims = _convert_scalar_realization_coords(lbrsvd4=0) - self.assertEqual(coords_and_dims, []) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py deleted file mode 100644 index cf147e5928..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_time_coords.py +++ /dev/null @@ -1,796 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._convert_time_coords`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from cf_units import CALENDAR_360_DAY, CALENDAR_STANDARD, Unit -from cftime import datetime as nc_datetime -import numpy as np - -from iris.coords import AuxCoord, DimCoord -from iris.fileformats.pp import SplittableInt -from iris.fileformats.pp_load_rules import _convert_time_coords -from iris.tests.unit.fileformats import TestField - - -def _lbtim(ia=0, ib=0, ic=0): - return SplittableInt(ic + 10 * (ib + 10 * ia), {"ia": 2, "ib": 1, "ic": 0}) - - -def _lbcode(value=None, ix=None, iy=None): - if value is not None: - result = SplittableInt(value, {"iy": slice(0, 2), "ix": slice(2, 4)}) - else: - # N.B. if 'value' is None, both ix and iy must be set. - result = SplittableInt( - 10000 + 100 * ix + iy, {"iy": slice(0, 2), "ix": slice(2, 4)} - ) - return result - - -_EPOCH_HOURS_UNIT = Unit("hours since epoch", calendar=CALENDAR_STANDARD) -_HOURS_UNIT = Unit("hours") - - -class TestLBTIMx0x_SingleTimepoint(TestField): - def _check_timepoint(self, lbcode, expect_match=True): - lbtim = _lbtim(ib=0, ic=1) - t1 = nc_datetime(1970, 1, 1, hour=6, minute=0, second=0) - t2 = nc_datetime( - 0, 0, 0, calendar=None, has_year_zero=True - ) # not used in result - lbft = None # unused - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - ) - if expect_match: - expect_result = [ - ( - DimCoord( - 24 * 0.25, - standard_name="time", - units=_EPOCH_HOURS_UNIT, - ), - None, - ) - ] - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - - def test_normal_xy_dims(self): - self._check_timepoint(_lbcode(1)) - - def test_non_time_cross_section(self): - self._check_timepoint(_lbcode(ix=1, iy=2)) - - def test_time_cross_section(self): - self._check_timepoint(_lbcode(ix=1, iy=20), expect_match=False) - - -class TestLBTIMx1x_Forecast(TestField): - def _check_forecast(self, lbcode, expect_match=True): - lbtim = _lbtim(ib=1, ic=1) - # Validity time - t1 = nc_datetime(1970, 1, 10, hour=6, minute=0, second=0) - # Forecast time - t2 = nc_datetime(1970, 1, 9, hour=3, minute=0, second=0) - lbft = None # unused - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - ) - if expect_match: - expect_result = [ - ( - DimCoord( - 24 * 1.125, - standard_name="forecast_period", - units="hours", - ), - None, - ), - ( - DimCoord( - 24 * 9.25, - standard_name="time", - units=_EPOCH_HOURS_UNIT, - ), - None, - ), - ( - DimCoord( - 24 * 8.125, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ), - None, - ), - ] - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - - def test_normal_xy(self): - self._check_forecast(_lbcode(1)) - - def test_non_time_cross_section(self): - self._check_forecast(_lbcode(ix=1, iy=2)) - - def test_time_cross_section(self): - self._check_forecast(_lbcode(ix=1, iy=20), expect_match=False) - - def test_exact_hours(self): - lbtim = _lbtim(ib=1, ic=1) - t1 = nc_datetime(2015, 1, 20, hour=7, minute=0, second=0) - t2 = nc_datetime(2015, 1, 20, hour=0, minute=0, second=0) - coords_and_dims = _convert_time_coords( - lbcode=_lbcode(1), - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=None, - ) - (fp, _), (t, _), (frt, _) = coords_and_dims - # These should both be exact whole numbers. - self.assertEqual(fp.points[0], 7) - self.assertEqual(t.points[0], 394927) - - def test_not_exact_hours(self): - lbtim = _lbtim(ib=1, ic=1) - t1 = nc_datetime(2015, 1, 20, hour=7, minute=10, second=0) - t2 = nc_datetime(2015, 1, 20, hour=0, minute=0, second=0) - coords_and_dims = _convert_time_coords( - lbcode=_lbcode(1), - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=None, - ) - (fp, _), (t, _), (frt, _) = coords_and_dims - self.assertArrayAllClose(fp.points[0], 7.1666666, atol=0.0001, rtol=0) - self.assertArrayAllClose(t.points[0], 394927.166666, atol=0.01, rtol=0) - - -class TestLBTIMx2x_TimePeriod(TestField): - def _check_period(self, lbcode, expect_match=True): - lbtim = _lbtim(ib=2, ic=1) - # Start time - t1 = nc_datetime(1970, 1, 9, hour=3, minute=0, second=0) - # End time - t2 = nc_datetime(1970, 1, 10, hour=3, minute=0, second=0) - lbft = 2.0 # sample period - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - ) - if expect_match: - expect_result = [ - ( - DimCoord( - 24 * 9.125 - 2.0, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ), - None, - ), - ( - DimCoord( - standard_name="forecast_period", - units="hours", - points=[-10.0], - bounds=[-22.0, 2.0], - ), - None, - ), - ( - DimCoord( - standard_name="time", - units=_EPOCH_HOURS_UNIT, - points=[24 * 8.625], - bounds=[24 * 8.125, 24 * 9.125], - ), - None, - ), - ] - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - - def test_normal_xy(self): - self._check_period(_lbcode(1)) - - def test_non_time_cross_section(self): - self._check_period(_lbcode(ix=1, iy=2)) - - def test_time_cross_section(self): - self._check_period(_lbcode(ix=1, iy=20), expect_match=False) - - -class TestLBTIMx3x_YearlyAggregation(TestField): - def _check_yearly(self, lbcode, expect_match=True): - lbtim = _lbtim(ib=3, ic=1) - # Start time - t1 = nc_datetime(1970, 1, 9, hour=9, minute=0, second=0) - # End time - t2 = nc_datetime(1972, 1, 11, hour=9, minute=0, second=0) - lbft = 3.0 # sample period - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - ) - if expect_match: - t1_hours = 24 * 8.375 - t2_hours = 24 * (10.375 + 2 * 365) - period_hours = 24.0 * (2 * 365 + 2) - expect_result = [ - ( - DimCoord( - [t2_hours - lbft], - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ), - None, - ), - ( - DimCoord( - standard_name="forecast_period", - units="hours", - points=[lbft], - bounds=[lbft - period_hours, lbft], - ), - None, - ), - ( - DimCoord( - standard_name="time", - units=_EPOCH_HOURS_UNIT, - points=[t2_hours], - bounds=[t1_hours, t2_hours], - ), - None, - ), - ] - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - - def test_normal_xy(self): - self._check_yearly(_lbcode(1)) - - def test_non_time_cross_section(self): - self._check_yearly(_lbcode(ix=1, iy=2)) - - def test_time_cross_section(self): - self._check_yearly(_lbcode(ix=1, iy=20), expect_match=False) - - -class TestLBTIMx2x_ZeroYear(TestField): - def test_(self): - lbtim = _lbtim(ib=2, ic=1) - t1 = nc_datetime(0, 1, 1, has_year_zero=True) - t2 = nc_datetime(0, 1, 31, 23, 59, 00, has_year_zero=True) - lbft = 0 - lbcode = _lbcode(1) - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - ) - self.assertEqual(coords_and_dims, []) - - -class TestLBTIMxxx_Unhandled(TestField): - def test_unrecognised(self): - lbtim = _lbtim(ib=4, ic=1) - t1 = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) - t2 = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) - lbft = None - lbcode = _lbcode(0) - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - ) - self.assertEqual(coords_and_dims, []) - - -class TestLBCODE3xx(TestField): - def test(self): - lbcode = _lbcode(value=31323) - lbtim = _lbtim(ib=2, ic=2) - calendar = CALENDAR_360_DAY - t1 = nc_datetime( - 1970, 1, 3, hour=0, minute=0, second=0, calendar=calendar - ) - t2 = nc_datetime( - 1970, 1, 4, hour=0, minute=0, second=0, calendar=calendar - ) - lbft = 24 * 4 - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - ) - t2_hours = 24 * 3 - expected_result = [ - ( - DimCoord( - [t2_hours - lbft], - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ), - None, - ) - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected_result) - - -class TestArrayInputWithLBTIM_0_0_1(TestField): - def test_t1_list(self): - # lbtim ia = 0, ib = 0, ic = 1 - # with a series of times (t1). - lbcode = _lbcode(1) - lbtim = _lbtim(ia=0, ib=0, ic=1) - hours = np.array([0, 3, 6, 9, 12]) - # Validity time - vector of different values - t1 = [nc_datetime(1970, 1, 9, hour=3 + hour) for hour in hours] - t1_dims = (0,) - # Forecast reference time - scalar (not used) - t2 = nc_datetime(1970, 1, 9, hour=3) - lbft = None - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t1_dims=t1_dims, - ) - - # Expected coords. - time_coord = DimCoord( - (24 * 8) + 3 + hours, standard_name="time", units=_EPOCH_HOURS_UNIT - ) - expected = [(time_coord, (0,))] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - -class TestArrayInputWithLBTIM_0_1_1(TestField): - def test_t1_list_t2_scalar(self): - # lbtim ia = 0, ib = 1, ic = 1 - # with a single forecast reference time (t2) and a series - # of validity times (t1). - lbcode = _lbcode(1) - lbtim = _lbtim(ia=0, ib=1, ic=1) - forecast_period_in_hours = np.array([0, 3, 6, 9, 12]) - # Validity time - vector of different values - t1 = [ - nc_datetime(1970, 1, 9, hour=(3 + fp)) - for fp in forecast_period_in_hours - ] - t1_dims = (0,) - # Forecast reference time - scalar - t2 = nc_datetime(1970, 1, 9, hour=3) - lbft = None # Not used. - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t1_dims=t1_dims, - ) - - # Expected coords. - fp_coord = DimCoord( - forecast_period_in_hours, - standard_name="forecast_period", - units="hours", - ) - time_coord = DimCoord( - (24 * 8) + 3 + forecast_period_in_hours, - standard_name="time", - units=_EPOCH_HOURS_UNIT, - ) - fref_time_coord = DimCoord( - (24 * 8) + 3, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ) - expected = [ - (fp_coord, (0,)), - (time_coord, (0,)), - (fref_time_coord, None), - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - def test_t1_and_t2_list(self): - # lbtim ia = 0, ib = 1, ic = 1 - # with a single repeated forecast reference time (t2) and a series - # of validity times (t1). - lbcode = _lbcode(1) - lbtim = _lbtim(ia=0, ib=1, ic=1) - forecast_period_in_hours = np.array([0, 3, 6, 9, 12]) - # Validity time - vector of different values - t1 = [ - nc_datetime(1970, 1, 9, hour=(3 + fp)) - for fp in forecast_period_in_hours - ] - t1_dims = (0,) - # Forecast reference time - vector of same values - t2 = [ - nc_datetime(1970, 1, 9, hour=3) for _ in forecast_period_in_hours - ] - t2_dims = (0,) - lbft = None # Not used. - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t1_dims=t1_dims, - t2_dims=t2_dims, - ) - - # Expected coords. - fp_coord = DimCoord( - forecast_period_in_hours, - standard_name="forecast_period", - units="hours", - ) - time_coord = DimCoord( - (24 * 8) + 3 + forecast_period_in_hours, - standard_name="time", - units=_EPOCH_HOURS_UNIT, - ) - fref_time_coord = DimCoord( - (24 * 8) + 3, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ) - expected = [ - (fp_coord, (0,)), - (time_coord, (0,)), - (fref_time_coord, None), - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - def test_t1_and_t2_orthogonal_lists(self): - # lbtim ia = 0, ib = 1, ic = 1 - # with a single repeated forecast reference time (t2) and a series - # of validity times (t1). - lbcode = _lbcode(1) - lbtim = _lbtim(ia=0, ib=1, ic=1) - years = np.array([1970, 1971, 1972]) - hours = np.array([3, 6, 9, 12]) - # Validity time - vector of different values - t1 = [nc_datetime(year, 1, 9, hour=12) for year in years] - t1_dims = (0,) - # Forecast reference time - vector of different values - t2 = [nc_datetime(1970, 1, 9, hour=hour) for hour in hours] - t2_dims = (1,) - lbft = None # Not used. - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t1_dims=t1_dims, - t2_dims=t2_dims, - ) - - # Expected coords. - points = [ - [(year - 1970) * 365 * 24 + 12 - hour for hour in hours] - for year in years - ] - fp_coord = AuxCoord( - points, standard_name="forecast_period", units="hours" - ) - points = (years - 1970) * 24 * 365 + (24 * 8) + 12 - time_coord = DimCoord( - points, standard_name="time", units=_EPOCH_HOURS_UNIT - ) - points = (24 * 8) + hours - fref_time_coord = DimCoord( - points, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ) - expected = [ - (fp_coord, (0, 1)), # Spans dims 0 and 1. - (time_coord, (0,)), - (fref_time_coord, (1,)), - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - def test_t1_multi_dim_list_t2_scalar(self): - # Another case of lbtim ia = 0, ib = 1, ic = 1 but - # with a changing forecast reference time (t2) and - # validity time (t1). - lbcode = _lbcode(1) - lbtim = _lbtim(ia=0, ib=1, ic=1) - forecast_period_in_hours = np.array([0, 3, 6, 9, 12]) - years = np.array([1970, 1971, 1972]) - # Validity time - 2d array of different values - t1 = [ - [ - nc_datetime(year, 1, 9, hour=(3 + fp)) - for fp in forecast_period_in_hours - ] - for year in years - ] - t1_dims = (0, 1) - # Forecast reference time - vector of different values - t2 = nc_datetime(1970, 1, 9, hour=3) - lbft = None # Not used. - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t1_dims=t1_dims, - ) - - # Expected coords. - fp_coord = AuxCoord( - [ - forecast_period_in_hours + (year - 1970) * 365 * 24 - for year in years - ], - standard_name="forecast_period", - units="hours", - ) - time_coord = AuxCoord( - [ - (24 * 8) - + 3 - + forecast_period_in_hours - + (year - 1970) * 365 * 24 - for year in years - ], - standard_name="time", - units=_EPOCH_HOURS_UNIT, - ) - fref_time_coord = DimCoord( - (24 * 8) + 3, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ) - expected = [ - (fp_coord, (0, 1)), - (time_coord, (0, 1)), - (fref_time_coord, None), - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - def test_t1_and_t2_nparrays(self): - # lbtim ia = 0, ib = 1, ic = 1 - # with a single repeated forecast reference time (t2) and a series - # of validity times (t1). - lbcode = _lbcode(1) - lbtim = _lbtim(ia=0, ib=1, ic=1) - forecast_period_in_hours = np.array([0, 3, 6, 9, 12]) - # Validity time - vector of different values - t1 = np.array( - [ - nc_datetime(1970, 1, 9, hour=(3 + fp)) - for fp in forecast_period_in_hours - ] - ) - t1_dims = (0,) - # Forecast reference time - vector of same values - t2 = np.array( - [nc_datetime(1970, 1, 9, hour=3) for _ in forecast_period_in_hours] - ) - t2_dims = (0,) - lbft = None # Not used. - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t1_dims=t1_dims, - t2_dims=t2_dims, - ) - - # Expected coords. - fp_coord = DimCoord( - forecast_period_in_hours, - standard_name="forecast_period", - units="hours", - ) - time_coord = DimCoord( - (24 * 8) + 3 + forecast_period_in_hours, - standard_name="time", - units=_EPOCH_HOURS_UNIT, - ) - fref_time_coord = DimCoord( - (24 * 8) + 3, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ) - expected = [ - (fp_coord, (0,)), - (time_coord, (0,)), - (fref_time_coord, None), - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - -class TestArrayInputWithLBTIM_0_2_1(TestField): - def test_t1_list_t2_scalar(self): - lbtim = _lbtim(ib=2, ic=1) - lbcode = _lbcode(1) - hours = np.array([0, 3, 6, 9]) - # Start times - vector - t1 = [nc_datetime(1970, 1, 9, hour=9 + hour) for hour in hours] - t1_dims = (0,) - # End time - scalar - t2 = nc_datetime(1970, 1, 11, hour=9) - lbft = 3.0 # Sample period - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t1_dims=t1_dims, - ) - - # Expected coords. - points = lbft - (48 - hours) / 2.0 - bounds = np.array( - [lbft - (48 - hours), np.ones_like(hours) * lbft] - ).transpose() - fp_coord = AuxCoord( - points, - standard_name="forecast_period", - units="hours", - bounds=bounds, - ) - points = 9 * 24 + 9 + (hours / 2.0) - bounds = np.array( - [8 * 24 + 9 + hours, np.ones_like(hours) * 10 * 24 + 9] - ).transpose() - time_coord = AuxCoord( - points, - standard_name="time", - units=_EPOCH_HOURS_UNIT, - bounds=bounds, - ) - points = 10 * 24 + 9 - lbft - fref_time_coord = DimCoord( - points, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ) - expected = [ - (fp_coord, (0,)), - (time_coord, (0,)), - (fref_time_coord, None), - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - -class TestArrayInputWithLBTIM_0_3_1(TestField): - def test_t1_scalar_t2_list(self): - lbtim = _lbtim(ib=3, ic=1) - lbcode = _lbcode(1) - years = np.array([1972, 1973, 1974]) - # Start times - scalar - t1 = nc_datetime(1970, 1, 9, hour=9) - # End time - vector - t2 = [nc_datetime(year, 1, 11, hour=9) for year in years] - t2_dims = (0,) - lbft = 3.0 # Sample period - - coords_and_dims = _convert_time_coords( - lbcode=lbcode, - lbtim=lbtim, - epoch_hours_unit=_EPOCH_HOURS_UNIT, - t1=t1, - t2=t2, - lbft=lbft, - t2_dims=t2_dims, - ) - - # Expected coords. - leap_year_adjust = np.array([0, 24, 24]) - points = np.ones_like(years) * lbft - bounds = np.array( - [ - lbft - ((years - 1970) * 365 * 24 + 2 * 24 + leap_year_adjust), - points, - ] - ).transpose() - fp_coord = AuxCoord( - points, - standard_name="forecast_period", - units="hours", - bounds=bounds, - ) - points = (years - 1970) * 365 * 24 + 10 * 24 + 9 + leap_year_adjust - bounds = np.array( - [np.ones_like(points) * (8 * 24 + 9), points] - ).transpose() - # The time coordinate is an AuxCoord as the lower bound for each - # cell is the same so it does not meet the monotonicity requirement. - time_coord = AuxCoord( - points, - standard_name="time", - units=_EPOCH_HOURS_UNIT, - bounds=bounds, - ) - fref_time_coord = DimCoord( - points - lbft, - standard_name="forecast_reference_time", - units=_EPOCH_HOURS_UNIT, - ) - expected = [ - (fp_coord, (0,)), - (time_coord, (0,)), - (fref_time_coord, (0,)), - ] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py deleted file mode 100644 index 47552a646a..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__convert_vertical_coords.py +++ /dev/null @@ -1,815 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._convert_vertical_coords`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.aux_factory import HybridHeightFactory, HybridPressureFactory -from iris.coords import AuxCoord, DimCoord -from iris.fileformats.pp import STASH, SplittableInt -from iris.fileformats.pp_load_rules import Reference, _convert_vertical_coords -from iris.tests.unit.fileformats import TestField - - -def _lbcode(value=None, ix=None, iy=None): - if value is not None: - result = SplittableInt(value, {"iy": slice(0, 2), "ix": slice(2, 4)}) - else: - # N.B. if 'value' is None, both ix and iy must be set. - result = SplittableInt( - 10000 + 100 * ix + iy, {"iy": slice(0, 2), "ix": slice(2, 4)} - ) - return result - - -class TestLBVC001_Height(TestField): - def _check_height( - self, - blev, - stash, - expect_normal=True, - expect_fixed_height=None, - dim=None, - ): - lbvc = 1 - lbcode = _lbcode(0) # effectively unused in this case - lblev, bhlev, bhrlev, brsvd1, brsvd2, brlev = ( - None, - None, - None, - None, - None, - None, - ) - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - if expect_normal: - expect_result = [ - ( - DimCoord( - blev, - standard_name="height", - units="m", - attributes={"positive": "up"}, - ), - dim, - ) - ] - elif expect_fixed_height: - expect_result = [ - ( - DimCoord( - expect_fixed_height, - standard_name="height", - units="m", - attributes={"positive": "up"}, - ), - None, - ) - ] - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) - - def test_normal_height__present(self): - self._check_height(blev=12.3, stash=STASH(1, 1, 1)) - - def test_normal_height__present_vector(self): - data = [12.3, 123.4, 1234.5] - dim = 0 - for blev in [data, np.asarray(data)]: - for dim_i in [dim, (dim,)]: - self._check_height(blev=blev, stash=STASH(1, 1, 1), dim=dim_i) - - def test_normal_height__absent(self): - self._check_height(blev=-1, stash=STASH(1, 1, 1), expect_normal=False) - - def test_normal_height__absent_vector(self): - data = [-1, -1, -1] - dim = 1 - for blev in [data, np.asarray(data)]: - for dim_i in [dim, (dim,)]: - self._check_height( - blev=blev, - stash=STASH(1, 1, 1), - expect_normal=False, - dim=dim_i, - ) - - def test_normal_height__absent_mixed_vector(self): - data = [-1, 12.3, -1, 123.4] - dim = 2 - for blev in [data, np.asarray(data)]: - for dim_i in [dim, (dim,)]: - self._check_height( - blev=blev, - stash=STASH(1, 1, 1), - expect_normal=False, - dim=dim_i, - ) - - def test_implied_height_1m5(self): - self._check_height( - blev=75.2, - stash=STASH(1, 3, 236), - expect_normal=False, - expect_fixed_height=1.5, - ) - - def test_implied_height_1m5__vector(self): - data = [1, 2, 3, 4] - dim = 3 - for blev in [data, np.asarray(data)]: - for dim_i in [dim, (dim,)]: - self._check_height( - blev=blev, - stash=STASH(1, 3, 236), - expect_normal=False, - expect_fixed_height=1.5, - dim=dim_i, - ) - - def test_implied_height_10m(self): - self._check_height( - blev=75.2, - stash=STASH(1, 3, 225), - expect_normal=False, - expect_fixed_height=10.0, - ) - - def test_implied_height_10m__vector(self): - data = list(range(10)) - dim = 4 - for blev in [data, np.asarray(data)]: - for dim_i in [dim, (dim,)]: - self._check_height( - blev=blev, - stash=STASH(1, 3, 225), - expect_normal=False, - expect_fixed_height=10.0, - dim=dim_i, - ) - - -class TestLBVC002_Depth(TestField): - def _check_depth( - self, - lbcode, - lblev=23.0, - blev=123.4, - brlev=0.0, - brsvd1=0.0, - expect_bounds=True, - expect_match=True, - expect_mixed=False, - dim=None, - ): - lbvc = 2 - stash = STASH(1, 1, 1) - bhlev, bhrlev, brsvd2 = None, None, None - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - if expect_match: - expect_result = [ - ( - DimCoord( - lblev, - standard_name="model_level_number", - attributes={"positive": "down"}, - units="1", - ), - dim, - ) - ] - if expect_bounds: - brsvd1 = np.atleast_1d(brsvd1) - brlev = np.atleast_1d(brlev) - if expect_mixed: - lower = np.where(brsvd1 == brlev, blev, brsvd1) - upper = np.where(brsvd1 == brlev, blev, brlev) - else: - lower, upper = brsvd1, brlev - bounds = np.vstack((lower, upper)).T - expect_result.append( - ( - DimCoord( - blev, - standard_name="depth", - units="m", - bounds=bounds, - attributes={"positive": "down"}, - ), - dim, - ) - ) - else: - expect_result.append( - ( - DimCoord( - blev, - standard_name="depth", - units="m", - attributes={"positive": "down"}, - ), - dim, - ) - ) - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) - - def test_unbounded(self): - self._check_depth(_lbcode(1), lblev=23.0, expect_bounds=False) - - def test_unbounded__vector(self): - lblev = [1, 2, 3] - blev = [10, 20, 30] - brsvd1 = [5, 15, 25] - brlev = [5, 15, 25] - self._check_depth( - _lbcode(1), - lblev=lblev, - blev=blev, - brsvd1=brsvd1, - brlev=brlev, - expect_bounds=False, - dim=1, - ) - - def test_unbounded__vector_no_depth(self): - lblev = [1, 2, 3] - blev = [10, 20, 30] - brsvd1 = [5, 15, 25] - brlev = [5, 15, 666] # not all equal or all unequal! - self._check_depth( - _lbcode(1), - lblev=lblev, - blev=blev, - brsvd1=brsvd1, - brlev=brlev, - expect_mixed=True, - dim=0, - ) - - def test_bounded(self): - self._check_depth( - _lbcode(1), lblev=23.0, brlev=22.5, brsvd1=23.5, expect_bounds=True - ) - - def test_bounded__vector(self): - lblev = [1, 2, 3] - blev = [10, 20, 30] - brsvd1 = [5, 15, 25] - brlev = [15, 25, 35] - self._check_depth( - _lbcode(1), - lblev=lblev, - blev=blev, - brsvd1=brsvd1, - brlev=brlev, - expect_bounds=True, - dim=1, - ) - - def test_cross_section(self): - self._check_depth(_lbcode(ix=1, iy=2), lblev=23.0, expect_match=False) - - def test_cross_section__vector(self): - lblev = [1, 2, 3] - blev = [10, 20, 30] - brsvd1 = [5, 15, 25] - brlev = [15, 25, 35] - self._check_depth( - _lbcode(ix=1, iy=2), - lblev=lblev, - blev=blev, - brsvd1=brsvd1, - brlev=brlev, - expect_match=False, - dim=1, - ) - - -class TestLBVC006_SoilLevel(TestField): - def _check_soil_level( - self, lbcode, lblev=12.3, expect_match=True, dim=None - ): - lbvc = 6 - stash = STASH(1, 1, 1) - brsvd1, brlev = 0, 0 - if hasattr(lblev, "__iter__"): - brsvd1 = [0] * len(lblev) - brlev = [0] * len(lblev) - blev, bhlev, bhrlev, brsvd2 = None, None, None, None - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - expect_result = [] - if expect_match: - coord = DimCoord( - lblev, - long_name="soil_model_level_number", - attributes={"positive": "down"}, - units="1", - ) - expect_result = [(coord, dim)] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) - - def test_normal(self): - self._check_soil_level(_lbcode(0)) - - def test_normal__vector(self): - lblev = np.arange(10) - self._check_soil_level(_lbcode(0), lblev=lblev, dim=0) - - def test_cross_section(self): - self._check_soil_level(_lbcode(ix=1, iy=2), expect_match=False) - - def test_cross_section__vector(self): - lblev = np.arange(10) - self._check_soil_level( - _lbcode(ix=1, iy=2), lblev=lblev, expect_match=False, dim=0 - ) - - -class TestLBVC006_SoilDepth(TestField): - def _check_soil_depth( - self, - lbcode, - blev=0.05, - brsvd1=0, - brlev=0.1, - expect_match=True, - dim=None, - ): - lbvc = 6 - stash = STASH(1, 1, 1) - lblev, bhlev, bhrlev, brsvd2 = None, None, None, None - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - expect_result = [] - if expect_match: - coord = DimCoord( - blev, - standard_name="depth", - bounds=np.vstack((brsvd1, brlev)).T, - units="m", - attributes={"positive": "down"}, - ) - expect_result = [(coord, dim)] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) - - def test_normal(self): - self._check_soil_depth(_lbcode(0)) - - def test_normal__vector(self): - points = np.arange(10) - self._check_soil_depth( - _lbcode(0), blev=points, brsvd1=points - 1, brlev=points + 1, dim=0 - ) - - def test_bad_bounds(self): - points = [-0.5, 0.5] - lower = [-1, 1] - upper = [-1, 1] - self._check_soil_depth( - _lbcode(0), - blev=points, - brsvd1=lower, - brlev=upper, - dim=0, - expect_match=False, - ) - - def test_cross_section(self): - self._check_soil_depth(_lbcode(ix=1, iy=2), expect_match=False) - - def test_cross_section__vector(self): - points = np.arange(10) - self._check_soil_depth( - _lbcode(ix=1, iy=2), - blev=points, - brsvd1=points - 1, - brlev=points + 1, - expect_match=False, - dim=0, - ) - - -class TestLBVC008_Pressure(TestField): - def _check_pressure(self, lbcode, blev=250.3, expect_match=True, dim=None): - lbvc = 8 - stash = STASH(1, 1, 1) - lblev, bhlev, bhrlev, brsvd1, brsvd2, brlev = ( - None, - None, - None, - None, - None, - None, - ) - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - if expect_match: - expect_result = [ - (DimCoord(blev, long_name="pressure", units="hPa"), dim) - ] - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) - - def test_normal(self): - self._check_pressure(_lbcode(0)) - - def test_normal__vector(self): - blev = [10, 100, 1000, 10000] - self._check_pressure(_lbcode(0), blev=blev, dim=2) - - def test_non_pressure_cross_section(self): - self._check_pressure(_lbcode(ix=10, iy=11)) - - def test_non_pressure_cross_section__vector(self): - blev = np.arange(10) - self._check_pressure(_lbcode(ix=10, iy=11), blev=blev, dim=0) - - def test_pressure_cross_section(self): - self._check_pressure(_lbcode(ix=10, iy=1), expect_match=False) - - def test_pressure_cross_section__vector(self): - blev = np.arange(10) - self._check_pressure( - _lbcode(ix=10, iy=1), blev=blev, dim=1, expect_match=False - ) - - -class TestLBVC019_PotentialTemperature(TestField): - def _check_potm(self, lbcode, blev=130.6, expect_match=True, dim=None): - lbvc = 19 - stash = STASH(1, 1, 1) - lblev, bhlev, bhrlev, brsvd1, brsvd2, brlev = ( - None, - None, - None, - None, - None, - None, - ) - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - if expect_match: - expect_result = [ - ( - DimCoord( - blev, - standard_name="air_potential_temperature", - units="K", - attributes={"positive": "up"}, - ), - dim, - ) - ] - else: - expect_result = [] - self.assertCoordsAndDimsListsMatch(coords_and_dims, expect_result) - self.assertEqual(factories, []) - - def test_normal(self): - self._check_potm(_lbcode(0)) - - def test_normal__vector(self): - blev = list(range(10)) - self._check_potm(_lbcode(0), blev=blev, dim=0) - - def test_cross_section(self): - self._check_potm(_lbcode(ix=10, iy=11), expect_match=False) - - def test_cross_section__vector(self): - blev = np.arange(5) + 100 - self._check_potm( - _lbcode(ix=10, iy=11), blev=blev, dim=1, expect_match=False - ) - - -class TestLBVC009_HybridPressure(TestField): - def _check( - self, - lblev=37.0, - bhlev=850.1, - bhrlev=810.0, - brsvd2=875.0, - blev=0.15, - brlev=0.11, - brsvd1=0.19, - expect_match=True, - dim=None, - ): - lbvc = 9 - lbcode = _lbcode(0) # unused - stash = STASH(1, 1, 1) # unused - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - expect_coords_and_dims = [ - ( - DimCoord( - lblev, - standard_name="model_level_number", - attributes={"positive": "up"}, - units="1", - ), - dim, - ) - ] - - bhrlev = np.atleast_1d(bhrlev) - brsvd2 = np.atleast_1d(brsvd2) - expect_coords_and_dims.append( - ( - DimCoord( - bhlev, - long_name="level_pressure", - units="Pa", - bounds=np.vstack((bhrlev, brsvd2)).T, - ), - dim, - ) - ) - brlev = np.atleast_1d(brlev) - brsvd1 = np.atleast_1d(brsvd1) - expect_coords_and_dims.append( - ( - AuxCoord( - blev, - long_name="sigma", - bounds=np.vstack((brlev, brsvd1)).T, - units="1", - ), - dim, - ) - ) - expect_factories = [ - ( - HybridPressureFactory, - [ - {"long_name": "level_pressure"}, - {"long_name": "sigma"}, - Reference("surface_air_pressure"), - ], - ) - ] - self.assertCoordsAndDimsListsMatch( - coords_and_dims, expect_coords_and_dims - ) - self.assertEqual(factories, expect_factories) - - def test_normal(self): - self._check() - - def test_normal__vector(self): - lblev = list(range(3)) - bhlev = [10, 20, 30] - bhrlev = [5, 15, 25] - brsvd2 = [15, 25, 35] - blev = [100, 200, 300] - brlev = [50, 150, 250] - brsvd1 = [150, 250, 350] - self._check( - lblev=lblev, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd2=brsvd2, - blev=blev, - brlev=brlev, - brsvd1=brsvd1, - dim=0, - ) - - -class TestLBVC065_HybridHeight(TestField): - def _check( - self, - lblev=37.0, - blev=9596.3, - brlev=9500.0, - brsvd1=9800.0, - bhlev=0.35, - bhrlev=0.31, - brsvd2=0.39, - dim=None, - ): - lbvc = 65 - lbcode = _lbcode(0) # unused - stash = STASH(1, 1, 1) # unused - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - dim=dim, - ) - expect_coords_and_dims = [ - ( - DimCoord( - lblev, - standard_name="model_level_number", - attributes={"positive": "up"}, - units="1", - ), - dim, - ) - ] - brlev = np.atleast_1d(brlev) - brsvd1 = np.atleast_1d(brsvd1) - expect_coords_and_dims.append( - ( - DimCoord( - blev, - long_name="level_height", - units="m", - bounds=np.vstack((brlev, brsvd1)).T, - attributes={"positive": "up"}, - ), - dim, - ) - ) - bhrlev = np.atleast_1d(bhrlev) - brsvd2 = np.atleast_1d(brsvd2) - expect_coords_and_dims.append( - ( - AuxCoord( - bhlev, - long_name="sigma", - bounds=np.vstack((bhrlev, brsvd2)).T, - units="1", - ), - dim, - ) - ) - expect_factories = [ - ( - HybridHeightFactory, - [ - {"long_name": "level_height"}, - {"long_name": "sigma"}, - Reference("orography"), - ], - ) - ] - self.assertCoordsAndDimsListsMatch( - coords_and_dims, expect_coords_and_dims - ) - self.assertEqual(factories, expect_factories) - - def test_normal(self): - self._check() - - def test_normal__vector(self): - npts = 5 - lblev = np.arange(npts) - blev = np.arange(npts) + 10 - brlev = np.arange(npts) + 5 - brsvd1 = np.arange(npts) + 15 - bhlev = np.arange(npts) + 12 - bhrlev = np.arange(npts) + 6 - brsvd2 = np.arange(npts) + 18 - self._check( - lblev=lblev, - blev=blev, - brlev=brlev, - brsvd1=brsvd1, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd2=brsvd2, - dim=1, - ) - - -class TestLBVCxxx_Unhandled(TestField): - def test_unknown_lbvc(self): - lbvc = 999 - blev, lblev, bhlev, bhrlev, brsvd1, brsvd2, brlev = ( - None, - None, - None, - None, - None, - None, - None, - ) - lbcode = _lbcode(0) # unused - stash = STASH(1, 1, 1) # unused - coords_and_dims, factories = _convert_vertical_coords( - lbcode=lbcode, - lbvc=lbvc, - blev=blev, - lblev=lblev, - stash=stash, - bhlev=bhlev, - bhrlev=bhrlev, - brsvd1=brsvd1, - brsvd2=brsvd2, - brlev=brlev, - ) - self.assertEqual(coords_and_dims, []) - self.assertEqual(factories, []) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py deleted file mode 100644 index 7769ca1de1..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__dim_or_aux.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :func:`iris.fileformats.pp_load_rules._dim_or_aux`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coords import AuxCoord, DimCoord -from iris.fileformats.pp_load_rules import _dim_or_aux - - -class Test(tests.IrisTest): - def setUp(self): - self.mono = list(range(5)) - self.non_mono = [0, 1, 3, 2, 4] - self.std_name = "depth" - self.units = "m" - self.attr = {"positive": "up", "wibble": "wobble"} - - def test_dim_monotonic(self): - result = _dim_or_aux( - self.mono, - standard_name=self.std_name, - units=self.units, - attributes=self.attr.copy(), - ) - expected = DimCoord( - self.mono, - standard_name=self.std_name, - units=self.units, - attributes=self.attr, - ) - self.assertEqual(result, expected) - - def test_dim_non_monotonic(self): - result = _dim_or_aux( - self.non_mono, - standard_name=self.std_name, - units=self.units, - attributes=self.attr.copy(), - ) - attr = self.attr.copy() - del attr["positive"] - expected = AuxCoord( - self.non_mono, - standard_name=self.std_name, - units=self.units, - attributes=attr, - ) - self.assertEqual(result, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py deleted file mode 100644 index 2c5d672e14..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__epoch_date_hours.py +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._epoch_date_hours`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cf_units -from cf_units import Unit -from cftime import datetime as nc_datetime - -from iris.fileformats.pp_load_rules import ( - _epoch_date_hours as epoch_hours_call, -) - -# -# Run tests for each of the possible calendars from PPfield.calendar(). -# Test year=0 and all=0 cases, plus "normal" dates, for each calendar. -# Result values are the same as from 'date2num' in cftime version <= 1.0.1. -# - - -class TestEpochHours__standard(tests.IrisTest): - def setUp(self): - self.calendar = cf_units.CALENDAR_STANDARD - self.hrs_unit = Unit("hours since epoch", calendar=self.calendar) - - def test_1970_1_1(self): - test_date = nc_datetime(1970, 1, 1, calendar=self.calendar) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, 0.0) - - def test_ymd_1_1_1(self): - test_date = nc_datetime(1, 1, 1, calendar=self.calendar) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17259936.0) - - def test_year_0(self): - test_date = nc_datetime( - 0, 1, 1, calendar=self.calendar, has_year_zero=True - ) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17268720.0) - - def test_ymd_0_0_0(self): - test_date = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17269488.0) - - def test_ymd_0_preserves_timeofday(self): - hrs, mins, secs, usecs = (7, 13, 24, 335772) - hours_in_day = ( - hrs + 1.0 / 60 * mins + 1.0 / 3600 * secs + (1.0e-6) / 3600 * usecs - ) - test_date = nc_datetime( - 0, - 0, - 0, - hour=hrs, - minute=mins, - second=secs, - microsecond=usecs, - calendar=None, - has_year_zero=True, - ) - result = epoch_hours_call(self.hrs_unit, test_date) - # NOTE: the calculation is only accurate to approx +/- 0.5 seconds - # in such a large number of hours -- even 0.1 seconds is too fine. - absolute_tolerance = 0.5 / 3600 - self.assertArrayAllClose( - result, -17269488.0 + hours_in_day, rtol=0, atol=absolute_tolerance - ) - - -class TestEpochHours__360day(tests.IrisTest): - def setUp(self): - self.calendar = cf_units.CALENDAR_360_DAY - self.hrs_unit = Unit("hours since epoch", calendar=self.calendar) - - def test_1970_1_1(self): - test_date = nc_datetime(1970, 1, 1, calendar=self.calendar) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, 0.0) - - def test_ymd_1_1_1(self): - test_date = nc_datetime(1, 1, 1, calendar=self.calendar) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17012160.0) - - def test_year_0(self): - test_date = nc_datetime( - 0, 1, 1, calendar=self.calendar, has_year_zero=True - ) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17020800.0) - - def test_ymd_0_0_0(self): - test_date = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17021544.0) - - -class TestEpochHours__365day(tests.IrisTest): - def setUp(self): - self.calendar = cf_units.CALENDAR_365_DAY - self.hrs_unit = Unit("hours since epoch", calendar=self.calendar) - - def test_1970_1_1(self): - test_date = nc_datetime(1970, 1, 1, calendar=self.calendar) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, 0.0) - - def test_ymd_1_1_1(self): - test_date = nc_datetime(1, 1, 1, calendar=self.calendar) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17248440.0) - - def test_year_0(self): - test_date = nc_datetime( - 0, 1, 1, calendar=self.calendar, has_year_zero=True - ) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17257200.0) - - def test_ymd_0_0_0(self): - test_date = nc_datetime(0, 0, 0, calendar=None, has_year_zero=True) - result = epoch_hours_call(self.hrs_unit, test_date) - self.assertEqual(result, -17257968.0) - - -class TestEpochHours__invalid_calendar(tests.IrisTest): - def test_bad_calendar(self): - self.calendar = cf_units.CALENDAR_ALL_LEAP - # Setup a unit with an unrecognised calendar - hrs_unit = Unit("hours since epoch", calendar=self.calendar) - # Test against a date with year=0, which requires calendar correction. - test_date = nc_datetime( - 0, 1, 1, calendar=self.calendar, has_year_zero=True - ) - # Check that this causes an error. - with self.assertRaisesRegex(ValueError, "unrecognised calendar"): - epoch_hours_call(hrs_unit, test_date) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py deleted file mode 100644 index fa381b91c1..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__model_level_number.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for :func:`iris.fileformats.pp_load_rules._model_level_number`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.fileformats.pp_load_rules import _model_level_number - - -class Test_9999(tests.IrisTest): - def test(self): - self.assertEqual(_model_level_number(9999), 0) - - -class Test_lblev(tests.IrisTest): - def test(self): - for val in range(9999): - self.assertEqual(_model_level_number(val), val) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py deleted file mode 100644 index fc30f66f7f..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reduced_points_and_bounds.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._reduce_points_and_bounds`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats.pp_load_rules import _reduce_points_and_bounds - - -class Test(tests.IrisTest): - def test_scalar(self): - array = np.array(1) - dims, result, bounds = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, array) - self.assertEqual(dims, None) - self.assertIsNone(bounds) - - def test_1d_nochange(self): - array = np.array([1, 2, 3]) - dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, array) - self.assertEqual(dims, (0,)) - - def test_1d_collapse(self): - array = np.array([1, 1, 1]) - dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array(1)) - self.assertEqual(dims, None) - - def test_2d_nochange(self): - array = np.array([[1, 2, 3], [4, 5, 6]]) - dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, array) - self.assertEqual(dims, (0, 1)) - - def test_2d_collapse_dim0(self): - array = np.array([[1, 2, 3], [1, 2, 3]]) - dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array([1, 2, 3])) - self.assertEqual(dims, (1,)) - - def test_2d_collapse_dim1(self): - array = np.array([[1, 1, 1], [2, 2, 2]]) - dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array([1, 2])) - self.assertEqual(dims, (0,)) - - def test_2d_collapse_both(self): - array = np.array([[3, 3, 3], [3, 3, 3]]) - dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array(3)) - self.assertEqual(dims, None) - - def test_3d(self): - array = np.array([[[3, 3, 3], [4, 4, 4]], [[3, 3, 3], [4, 4, 4]]]) - dims, result, _ = _reduce_points_and_bounds(array) - self.assertArrayEqual(result, np.array([3, 4])) - self.assertEqual(dims, (1,)) - - def test_bounds_collapse(self): - points = np.array([1, 1, 1]) - bounds = np.array([[0, 2], [0, 2], [0, 2]]) - result_dims, result_pts, result_bds = _reduce_points_and_bounds( - points, (bounds[..., 0], bounds[..., 1]) - ) - self.assertArrayEqual(result_pts, np.array(1)) - self.assertArrayEqual(result_bds, np.array([0, 2])) - self.assertEqual(result_dims, None) - - def test_bounds_no_collapse(self): - points = np.array([1, 2, 3]) - bounds = np.array([[0, 2], [1, 3], [2, 4]]) - result_dims, result_pts, result_bds = _reduce_points_and_bounds( - points, (bounds[..., 0], bounds[..., 1]) - ) - self.assertArrayEqual(result_pts, points) - self.assertArrayEqual(result_bds, bounds) - self.assertEqual(result_dims, (0,)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py deleted file mode 100644 index 4e6d50fea7..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test__reshape_vector_args.py +++ /dev/null @@ -1,139 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for -:func:`iris.fileformats.pp_load_rules._reshape_vector_args`. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats.pp_load_rules import _reshape_vector_args - - -class TestEmpty(tests.IrisTest): - def test(self): - result = _reshape_vector_args([]) - self.assertEqual(result, []) - - -class TestSingleArg(tests.IrisTest): - def _check(self, result, expected): - self.assertEqual(len(result), len(expected)) - for result_arr, expected_arr in zip(result, expected): - self.assertArrayEqual(result_arr, expected_arr) - - def test_nochange(self): - points = np.array([[1, 2, 3], [4, 5, 6]]) - result = _reshape_vector_args([(points, (0, 1))]) - expected = [points] - self._check(result, expected) - - def test_bad_dimensions(self): - points = np.array([[1, 2, 3], [4, 5, 6]]) - with self.assertRaisesRegex(ValueError, "Length"): - _reshape_vector_args([(points, (0, 1, 2))]) - - def test_scalar(self): - points = 5 - result = _reshape_vector_args([(points, ())]) - expected = [points] - self._check(result, expected) - - def test_nonarray(self): - points = [[1, 2, 3], [4, 5, 6]] - result = _reshape_vector_args([(points, (0, 1))]) - expected = [np.array(points)] - self._check(result, expected) - - def test_transpose(self): - points = np.array([[1, 2, 3], [4, 5, 6]]) - result = _reshape_vector_args([(points, (1, 0))]) - expected = [points.T] - self._check(result, expected) - - def test_extend(self): - points = np.array([[1, 2, 3, 4], [21, 22, 23, 24], [31, 32, 33, 34]]) - result = _reshape_vector_args([(points, (1, 3))]) - expected = [points.reshape(1, 3, 1, 4)] - self._check(result, expected) - - -class TestMultipleArgs(tests.IrisTest): - def _check(self, result, expected): - self.assertEqual(len(result), len(expected)) - for result_arr, expected_arr in zip(result, expected): - self.assertArrayEqual(result_arr, expected_arr) - - def test_nochange(self): - a1 = np.array([[1, 2, 3], [4, 5, 6]]) - a2 = np.array([[0, 2, 4], [7, 8, 9]]) - result = _reshape_vector_args([(a1, (0, 1)), (a2, (0, 1))]) - expected = [a1, a2] - self._check(result, expected) - - def test_array_and_scalar(self): - a1 = [[1, 2, 3], [3, 4, 5]] - a2 = 5 - result = _reshape_vector_args([(a1, (0, 1)), (a2, ())]) - expected = [a1, np.array([[5]])] - self._check(result, expected) - - def test_transpose(self): - a1 = np.array([[1, 2, 3], [4, 5, 6]]) - a2 = np.array([[0, 2, 4], [7, 8, 9]]) - result = _reshape_vector_args([(a1, (0, 1)), (a2, (1, 0))]) - expected = [a1, a2.T] - self._check(result, expected) - - def test_incompatible(self): - # Does not enforce compatibility of results. - a1 = np.array([1, 2]) - a2 = np.array([1, 2, 3]) - result = _reshape_vector_args([(a1, (0,)), (a2, (0,))]) - expected = [a1, a2] - self._check(result, expected) - - def test_extend(self): - a1 = np.array([[1, 2, 3], [4, 5, 6]]) - a2 = np.array([11, 12, 13]) - result = _reshape_vector_args([(a1, (0, 1)), (a2, (1,))]) - expected = [a1, a2.reshape(1, 3)] - self._check(result, expected) - - def test_extend_transpose(self): - a1 = np.array([[1, 2, 3], [4, 5, 6]]) - a2 = np.array([11, 12, 13]) - result = _reshape_vector_args([(a1, (1, 0)), (a2, (1,))]) - expected = [a1.T, a2.reshape(1, 3)] - self._check(result, expected) - - def test_double_extend(self): - a1 = np.array([[1, 2, 3], [4, 5, 6]]) - a2 = np.array(1) - result = _reshape_vector_args([(a1, (0, 2)), (a2, ())]) - expected = [a1.reshape(2, 1, 3), a2.reshape(1, 1, 1)] - self._check(result, expected) - - def test_triple(self): - a1 = np.array([[1, 2, 3, 4]]) - a2 = np.array([3, 4]) - a3 = np.array(7) - result = _reshape_vector_args([(a1, (0, 2)), (a2, (1,)), (a3, ())]) - expected = [ - a1.reshape(1, 1, 4), - a2.reshape(1, 2, 1), - a3.reshape(1, 1, 1), - ] - self._check(result, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py b/lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py deleted file mode 100644 index 569d676183..0000000000 --- a/lib/iris/tests/unit/fileformats/pp_load_rules/test_convert.py +++ /dev/null @@ -1,459 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :func:`iris.fileformats.pp_load_rules.convert`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from types import MethodType -from unittest import mock - -import cf_units -import cftime -import numpy as np - -from iris.fileformats.pp import STASH, PPField3, SplittableInt -from iris.fileformats.pp_load_rules import convert -import iris.tests.unit.fileformats -from iris.util import guess_coord_axis - - -def _mock_field(**kwargs): - # Generate a mock field, but ensure T1 and T2 viable for rules. - field = mock.MagicMock( - t1=mock.MagicMock(year=1990, month=3, day=7), - t2=mock.MagicMock(year=1990, month=3, day=7), - ) - field.configure_mock(**kwargs) - return field - - -class TestLBCODE(iris.tests.unit.fileformats.TestField): - @staticmethod - def _is_cross_section_height_coord(coord): - return ( - coord.standard_name == "height" - and coord.units == "km" - and coord.attributes["positive"] == "up" - ) - - def test_cross_section_height_bdy_zero(self): - lbcode = SplittableInt(19902, {"iy": slice(0, 2), "ix": slice(2, 4)}) - points = np.array([10, 20, 30, 40]) - bounds = np.array([[0, 15], [15, 25], [25, 35], [35, 45]]) - field = _mock_field(lbcode=lbcode, bdy=0, y=points, y_bounds=bounds) - self._test_for_coord( - field, - convert, - TestLBCODE._is_cross_section_height_coord, - expected_points=points, - expected_bounds=bounds, - ) - - def test_cross_section_height_bdy_bmdi(self): - lbcode = SplittableInt(19902, {"iy": slice(0, 2), "ix": slice(2, 4)}) - points = np.array([10, 20, 30, 40]) - bounds = np.array([[0, 15], [15, 25], [25, 35], [35, 45]]) - bmdi = -1.07374e09 - field = _mock_field( - lbcode=lbcode, bdy=bmdi, bmdi=bmdi, y=points, y_bounds=bounds - ) - self._test_for_coord( - field, - convert, - TestLBCODE._is_cross_section_height_coord, - expected_points=points, - expected_bounds=bounds, - ) - - -class TestLBVC(iris.tests.unit.fileformats.TestField): - @staticmethod - def _is_potm_level_coord(coord): - return ( - coord.standard_name == "air_potential_temperature" - and coord.attributes["positive"] == "up" - ) - - @staticmethod - def _is_model_level_number_coord(coord): - return ( - coord.standard_name == "model_level_number" - and coord.units.is_dimensionless() - and coord.attributes["positive"] == "up" - ) - - @staticmethod - def _is_level_pressure_coord(coord): - return coord.name() == "level_pressure" and coord.units == "Pa" - - @staticmethod - def _is_sigma_coord(coord): - return coord.name() == "sigma" and coord.units.is_dimensionless() - - @staticmethod - def _is_soil_model_level_number_coord(coord): - return ( - coord.long_name == "soil_model_level_number" - and coord.units.is_dimensionless() - and coord.attributes["positive"] == "down" - ) - - @staticmethod - def _is_soil_depth_coord(coord): - return ( - coord.standard_name == "depth" - and coord.units == "m" - and coord.attributes["positive"] == "down" - ) - - def test_soil_levels(self): - level = 1234 - field = _mock_field(lbvc=6, lblev=level, brsvd=[0, 0], brlev=0) - self._test_for_coord( - field, - convert, - self._is_soil_model_level_number_coord, - expected_points=[level], - expected_bounds=None, - ) - - def test_soil_depth(self): - lower, point, upper = 1.2, 3.4, 5.6 - field = _mock_field(lbvc=6, blev=point, brsvd=[lower, 0], brlev=upper) - self._test_for_coord( - field, - convert, - self._is_soil_depth_coord, - expected_points=[point], - expected_bounds=[[lower, upper]], - ) - - def test_hybrid_pressure_model_level_number(self): - level = 5678 - field = _mock_field( - lbvc=9, - lblev=level, - blev=20, - brlev=23, - bhlev=42, - bhrlev=45, - brsvd=[17, 40], - ) - self._test_for_coord( - field, - convert, - TestLBVC._is_model_level_number_coord, - expected_points=[level], - expected_bounds=None, - ) - - def test_hybrid_pressure_delta(self): - delta_point = 12.0 - delta_lower_bound = 11.0 - delta_upper_bound = 13.0 - field = _mock_field( - lbvc=9, - lblev=5678, - blev=20, - brlev=23, - bhlev=delta_point, - bhrlev=delta_lower_bound, - brsvd=[17, delta_upper_bound], - ) - self._test_for_coord( - field, - convert, - TestLBVC._is_level_pressure_coord, - expected_points=[delta_point], - expected_bounds=[[delta_lower_bound, delta_upper_bound]], - ) - - def test_hybrid_pressure_sigma(self): - sigma_point = 0.5 - sigma_lower_bound = 0.6 - sigma_upper_bound = 0.4 - field = _mock_field( - lbvc=9, - lblev=5678, - blev=sigma_point, - brlev=sigma_lower_bound, - bhlev=12, - bhrlev=11, - brsvd=[sigma_upper_bound, 13], - ) - self._test_for_coord( - field, - convert, - TestLBVC._is_sigma_coord, - expected_points=[sigma_point], - expected_bounds=[[sigma_lower_bound, sigma_upper_bound]], - ) - - def test_potential_temperature_levels(self): - potm_value = 27.32 - field = _mock_field(lbvc=19, blev=potm_value) - self._test_for_coord( - field, - convert, - TestLBVC._is_potm_level_coord, - expected_points=np.array([potm_value]), - expected_bounds=None, - ) - - -class TestLBTIM(iris.tests.unit.fileformats.TestField): - def test_365_calendar(self): - f = mock.MagicMock( - lbtim=SplittableInt(4, {"ia": 2, "ib": 1, "ic": 0}), - lbyr=2013, - lbmon=1, - lbdat=1, - lbhr=12, - lbmin=0, - lbsec=0, - t1=cftime.datetime(2013, 1, 1, 12, 0, 0), - t2=cftime.datetime(2013, 1, 2, 12, 0, 0), - spec=PPField3, - ) - f.time_unit = MethodType(PPField3.time_unit, f) - f.calendar = cf_units.CALENDAR_365_DAY - ( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) = convert(f) - - def is_t_coord(coord_and_dims): - coord, dims = coord_and_dims - return coord.standard_name == "time" - - coords_and_dims = list(filter(is_t_coord, aux_coords_and_dims)) - self.assertEqual(len(coords_and_dims), 1) - coord, dims = coords_and_dims[0] - self.assertEqual(guess_coord_axis(coord), "T") - self.assertEqual(coord.units.calendar, "365_day") - - def base_field(self): - field = PPField3(header=mock.MagicMock()) - field.lbfc = 0 - field.bdx = 1 - field.bdy = 1 - field.bmdi = 999 - field.lbproc = 0 - field.lbvc = 0 - field.lbuser = [0] * 7 - field.lbrsvd = [0] * 4 - field.brsvd = [0] * 4 - field.lbsrce = 0 - field.lbcode = 0 - return field - - @staticmethod - def is_forecast_period(coord): - return ( - coord.standard_name == "forecast_period" and coord.units == "hours" - ) - - @staticmethod - def is_time(coord): - return ( - coord.standard_name == "time" - and coord.units == "hours since epoch" - ) - - def test_time_mean_ib2(self): - field = self.base_field() - field.lbtim = 21 - # Implicit reference time: 1970-01-02 06:00 - field.lbft = 9 - # t1 - field.lbyr, field.lbmon, field.lbdat = 1970, 1, 2 - field.lbhr, field.lbmin, field.lbsec = 12, 0, 0 - # t2 - field.lbyrd, field.lbmond, field.lbdatd = 1970, 1, 2 - field.lbhrd, field.lbmind, field.lbsecd = 15, 0, 0 - - self._test_for_coord( - field, - convert, - self.is_forecast_period, - expected_points=[7.5], - expected_bounds=[[6, 9]], - ) - - self._test_for_coord( - field, - convert, - self.is_time, - expected_points=[24 + 13.5], - expected_bounds=[[36, 39]], - ) - - def test_time_mean_ib3(self): - field = self.base_field() - field.lbtim = 31 - # Implicit reference time: 1970-01-02 06:00 - field.lbft = lbft = ((365 + 1) * 24 + 15) - (24 + 6) - # t1 - field.lbyr, field.lbmon, field.lbdat = 1970, 1, 2 - field.lbhr, field.lbmin, field.lbsec = 12, 0, 0 - # t2 - field.lbyrd, field.lbmond, field.lbdatd = 1971, 1, 2 - field.lbhrd, field.lbmind, field.lbsecd = 15, 0, 0 - - self._test_for_coord( - field, - convert, - self.is_forecast_period, - expected_points=[lbft], - expected_bounds=[[36 - 30, lbft]], - ) - - self._test_for_coord( - field, - convert, - self.is_time, - expected_points=[lbft + 30], - expected_bounds=[[36, lbft + 30]], - ) - - -class TestLBRSVD(iris.tests.unit.fileformats.TestField): - @staticmethod - def _is_realization(coord): - return coord.standard_name == "realization" and coord.units == "1" - - def test_realization(self): - lbrsvd = [0] * 4 - lbrsvd[3] = 71 - points = np.array([71]) - bounds = None - field = _mock_field(lbrsvd=lbrsvd) - self._test_for_coord( - field, - convert, - TestLBRSVD._is_realization, - expected_points=points, - expected_bounds=bounds, - ) - - -class TestLBSRCE(iris.tests.IrisTest): - def check_um_source_attrs( - self, lbsrce, source_str=None, um_version_str=None - ): - field = _mock_field(lbsrce=lbsrce) - ( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) = convert(field) - if source_str is not None: - self.assertEqual(attributes["source"], source_str) - else: - self.assertNotIn("source", attributes) - if um_version_str is not None: - self.assertEqual(attributes["um_version"], um_version_str) - else: - self.assertNotIn("um_version", attributes) - - def test_none(self): - self.check_um_source_attrs( - lbsrce=8123, source_str=None, um_version_str=None - ) - - def test_no_um_version(self): - self.check_um_source_attrs( - lbsrce=1111, - source_str="Data from Met Office Unified Model", - um_version_str=None, - ) - - def test_um_version(self): - self.check_um_source_attrs( - lbsrce=12071111, - source_str="Data from Met Office Unified Model", - um_version_str="12.7", - ) - - -class Test_STASH_CF(iris.tests.unit.fileformats.TestField): - def test_stash_cf_air_temp(self): - lbuser = [1, 0, 0, 16203, 0, 0, 1] - lbfc = 16 - stash = STASH(lbuser[6], lbuser[3] // 1000, lbuser[3] % 1000) - field = _mock_field(lbuser=lbuser, lbfc=lbfc, stash=stash) - ( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) = convert(field) - self.assertEqual(standard_name, "air_temperature") - self.assertEqual(units, "K") - - def test_no_std_name(self): - lbuser = [1, 0, 0, 0, 0, 0, 0] - lbfc = 0 - stash = STASH(lbuser[6], lbuser[3] // 1000, lbuser[3] % 1000) - field = _mock_field(lbuser=lbuser, lbfc=lbfc, stash=stash) - ( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) = convert(field) - self.assertIsNone(standard_name) - self.assertIsNone(units) - - -class Test_LBFC_CF(iris.tests.unit.fileformats.TestField): - def test_fc_cf_air_temp(self): - lbuser = [1, 0, 0, 0, 0, 0, 0] - lbfc = 16 - stash = STASH(lbuser[6], lbuser[3] // 1000, lbuser[3] % 1000) - field = _mock_field(lbuser=lbuser, lbfc=lbfc, stash=stash) - ( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) = convert(field) - self.assertEqual(standard_name, "air_temperature") - self.assertEqual(units, "K") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/rules/__init__.py b/lib/iris/tests/unit/fileformats/rules/__init__.py deleted file mode 100644 index 55c9c7779e..0000000000 --- a/lib/iris/tests/unit/fileformats/rules/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.fileformats.rules` module.""" diff --git a/lib/iris/tests/unit/fileformats/rules/test_Loader.py b/lib/iris/tests/unit/fileformats/rules/test_Loader.py deleted file mode 100644 index be96f526d2..0000000000 --- a/lib/iris/tests/unit/fileformats/rules/test_Loader.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris.fileformats.rules.Loader`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats.rules import Loader - - -class Test___init__(tests.IrisTest): - def test_normal(self): - with mock.patch("warnings.warn") as warn: - loader = Loader( - mock.sentinel.GEN_FUNC, - mock.sentinel.GEN_FUNC_KWARGS, - mock.sentinel.CONVERTER, - ) - self.assertEqual(warn.call_count, 0) - self.assertIs(loader.field_generator, mock.sentinel.GEN_FUNC) - self.assertIs( - loader.field_generator_kwargs, mock.sentinel.GEN_FUNC_KWARGS - ) - self.assertIs(loader.converter, mock.sentinel.CONVERTER) - - def test_normal_with_explicit_none(self): - with mock.patch("warnings.warn") as warn: - loader = Loader( - mock.sentinel.GEN_FUNC, - mock.sentinel.GEN_FUNC_KWARGS, - mock.sentinel.CONVERTER, - ) - self.assertEqual(warn.call_count, 0) - self.assertIs(loader.field_generator, mock.sentinel.GEN_FUNC) - self.assertIs( - loader.field_generator_kwargs, mock.sentinel.GEN_FUNC_KWARGS - ) - self.assertIs(loader.converter, mock.sentinel.CONVERTER) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/rules/test__make_cube.py b/lib/iris/tests/unit/fileformats/rules/test__make_cube.py deleted file mode 100644 index b6c4528399..0000000000 --- a/lib/iris/tests/unit/fileformats/rules/test__make_cube.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :func:`iris.fileformats.rules._make_cube`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock -import warnings - -import numpy as np - -from iris.fileformats.rules import ConversionMetadata, _make_cube - - -class Test(tests.IrisTest): - def test_invalid_units(self): - # Mock converter() function that returns an invalid - # units string amongst the collection of other elements. - factories = None - references = None - standard_name = None - long_name = None - units = "wibble" # Invalid unit. - attributes = dict(source="test") - cell_methods = None - dim_coords_and_dims = None - aux_coords_and_dims = None - metadata = ConversionMetadata( - factories, - references, - standard_name, - long_name, - units, - attributes, - cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) - converter = mock.Mock(return_value=metadata) - - data = np.arange(3.0) - field = mock.Mock( - core_data=lambda: data, bmdi=9999.0, realised_dtype=data.dtype - ) - with warnings.catch_warnings(record=True) as warn: - warnings.simplefilter("always") - cube, factories, references = _make_cube(field, converter) - - # Check attributes dictionary is correctly populated. - expected_attributes = attributes.copy() - expected_attributes["invalid_units"] = units - self.assertEqual(cube.attributes, expected_attributes) - - # Check warning was raised. - self.assertEqual(len(warn), 1) - exp_emsg = "invalid units {!r}".format(units) - self.assertRegex(str(warn[0]), exp_emsg) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/structured_array_identification/__init__.py b/lib/iris/tests/unit/fileformats/structured_array_identification/__init__.py deleted file mode 100644 index c703284fc0..0000000000 --- a/lib/iris/tests/unit/fileformats/structured_array_identification/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -:mod:`iris.fileformats._structured_array_identification` module. - -""" diff --git a/lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py b/lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py deleted file mode 100644 index 871aab4f1e..0000000000 --- a/lib/iris/tests/unit/fileformats/structured_array_identification/test_ArrayStructure.py +++ /dev/null @@ -1,230 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -:mod:`iris.fileformats._structured_array_identification.ArrayStructure` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats._structured_array_identification import ( - ArrayStructure, - _UnstructuredArrayException, -) - - -def construct_nd(sub_array, sub_dim, shape): - # Given a 1D array, a shape, and the axis/dimension that the 1D array - # represents on the bigger array, construct a numpy array which is - # filled appropriately. - assert sub_array.ndim == 1 - sub_shape = [1 if dim != sub_dim else -1 for dim in range(len(shape))] - return sub_array.reshape(sub_shape) * np.ones(shape) - - -class TestArrayStructure_from_array(tests.IrisTest): - def struct_from_arr(self, nd_array): - return ArrayStructure.from_array(nd_array.flatten()) - - def test_1d_len_0(self): - a = np.arange(0) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, a)) - - def test_1d_len_1(self): - a = np.arange(1) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, a)) - - def test_1d(self): - a = np.array([-1, 3, 1, 2]) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, a)) - - def test_1d_ones(self): - a = np.ones(10) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, [1])) - - def test_1d_range(self): - a = np.arange(6) - self.assertEqual( - self.struct_from_arr(a), ArrayStructure(1, list(range(6))) - ) - - def test_3d_ones(self): - a = np.ones([10, 2, 1]) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, [1])) - - def test_1d_over_2d_first_dim_manual(self): - sub = np.array([10, 10, 20, 20]) - self.assertEqual( - self.struct_from_arr(sub), ArrayStructure(2, [10, 20]) - ) - - def test_3d_first_dimension(self): - flattened = np.array([1, 1, 1, 2, 2, 2]) - self.assertEqual( - ArrayStructure.from_array(flattened), ArrayStructure(3, [1, 2]) - ) - - def test_1d_over_2d_first_dim(self): - sub = np.array([-1, 3, 1, 2]) - a = construct_nd(sub, 0, (4, 2)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(2, sub)) - - def test_1d_over_2d_second_dim(self): - sub = np.array([-1, 3, 1, 2]) - a = construct_nd(sub, 1, (2, 4)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, sub)) - - def test_1d_over_3d_first_dim(self): - sub = np.array([-1, 3, 1, 2]) - a = construct_nd(sub, 0, (4, 2, 3)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(6, sub)) - - def test_1d_over_3d_second_dim(self): - sub = np.array([-1, 3, 1, 2]) - a = construct_nd(sub, 1, (2, 4, 3)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(3, sub)) - - def test_1d_over_3d_third_dim(self): - sub = np.array([-1, 3, 1, 2]) - a = construct_nd(sub, 2, (3, 2, 4)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, sub)) - - def test_irregular_3d(self): - sub = np.array([-1, 3, 1, 2]) - a = construct_nd(sub, 2, (3, 2, 4)) - a[0, 0, 0] = 5 - self.assertEqual(self.struct_from_arr(a), None) - - def test_repeated_3d(self): - sub = np.array([-1, 3, 1, 2]) - a = construct_nd(sub, 2, (3, 2, 4)) - a[:, 0, 0] = 1 - self.assertEqual(self.struct_from_arr(a), None) - - def test_rolled_3d(self): - # Shift the 3D array on by one, making the array 1d. - sub = np.arange(4) - a = construct_nd(sub, 0, (4, 2, 3)) - a = np.roll(a.flatten(), 1) - self.assertEqual(self.struct_from_arr(a), None) - - def test_len_1_3d(self): - # Setup a case which triggers an IndexError when identifying - # the stride, but the result should still be correct. - sub = np.arange(2) - a = construct_nd(sub, 1, (1, 1, 1)) - self.assertEqual(self.struct_from_arr(a), ArrayStructure(1, sub)) - - def test_not_an_array(self): - # Support lists as an argument. - self.assertEqual( - ArrayStructure.from_array([1, 2, 3]), ArrayStructure(1, [1, 2, 3]) - ) - - def test_multi_dim_array(self): - with self.assertRaises(ValueError): - ArrayStructure.from_array(np.arange(12).reshape(3, 4)) - - -class nd_array_and_dims_cases: - """ - Defines the test functionality for nd_array_and_dims. This class - isn't actually the test case - see the C order and F order subclasses - for those. - - """ - - def test_scalar_len1_first_dim(self): - struct = ArrayStructure(1, [1]) - orig = np.array([1, 1, 1]) - - array, dims = struct.nd_array_and_dims(orig, (1, 3), order=self.order) - self.assertArrayEqual(array, [1]) - self.assertEqual(dims, ()) - - def test_scalar_non_len1_first_dim(self): - struct = ArrayStructure(1, [1]) - orig = np.array([1, 1, 1]) - - array, dims = struct.nd_array_and_dims(orig, (3, 1), order=self.order) - self.assertArrayEqual(array, [1]) - self.assertEqual(dims, ()) - - def test_single_vector(self): - orig = construct_nd(np.array([1, 2]), 0, (2, 1, 3)) - flattened = orig.flatten(order=self.order) - struct = ArrayStructure.from_array(flattened) - array, dims = struct.nd_array_and_dims( - flattened, (2, 1, 3), order=self.order - ) - self.assertArrayEqual(array, [1, 2]) - self.assertEqual(dims, (0,)) - - def test_single_vector_3rd_dim(self): - orig = construct_nd(np.array([1, 2, 3]), 2, (4, 1, 3)) - flattened = orig.flatten(order=self.order) - - struct = ArrayStructure.from_array(flattened) - array, dims = struct.nd_array_and_dims( - flattened, (4, 1, 3), order=self.order - ) - self.assertArrayEqual(array, [1, 2, 3]) - self.assertEqual(dims, (2,)) - - def test_orig_array_and_target_shape_inconsistent(self): - # An array structure which has a length which is a product - # of potential dimensions should not result in an array - struct = ArrayStructure(2, [1, 2, 3]) - orig = np.array([1, 1, 2, 2, 3, 3]) - - msg = "Original array and target shape do not match up." - with self.assertRaisesRegex(ValueError, msg): - struct.nd_array_and_dims(orig, (2, 3, 2), order=self.order) - - def test_array_bigger_than_expected(self): - # An array structure which has a length which is a product - # of potential dimensions should not result in an array - struct = ArrayStructure(2, [1, 2, 3, 4, 5, 6]) - orig = np.array([1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6]) - - with self.assertRaises(_UnstructuredArrayException): - struct.nd_array_and_dims(orig, (2, 3, 2), order=self.order) - - def test_single_vector_extra_dimension(self): - orig = construct_nd(np.array([1, 2]), 1, (3, 2)) - flattened = orig.flatten(order=self.order) - - struct = ArrayStructure.from_array(flattened) - - # Add another dimension on flattened, making it a (6, 2). - input_array = np.vstack([flattened, flattened + 100]).T - - array, dims = struct.nd_array_and_dims( - input_array, (3, 1, 2, 1), order=self.order - ) - self.assertArrayEqual(array, [[1, 101], [2, 102]]) - self.assertEqual(dims, (2,)) - - -class TestArrayStructure_nd_array_and_dims_f_order( - tests.IrisTest, nd_array_and_dims_cases -): - order = "f" - - -class TestArrayStructure_nd_array_and_dims_c_order( - tests.IrisTest, nd_array_and_dims_cases -): - order = "c" - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py b/lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py deleted file mode 100644 index a7818ad802..0000000000 --- a/lib/iris/tests/unit/fileformats/structured_array_identification/test_GroupStructure.py +++ /dev/null @@ -1,198 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the -:mod:`iris.fileformats._structured_array_identification.GroupStructure` class. - -""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats._structured_array_identification import ( - ArrayStructure, - GroupStructure, -) - - -def regular_array_structures(shape, names="abcdefg"): - # Construct column major appropriate ArrayStructures for the given - # shape. - running_product = 1 - array_structures = {} - for name, dim_len in zip(names, shape): - array_structures[name] = ArrayStructure( - running_product, np.arange(dim_len) - ) - running_product *= dim_len - return array_structures - - -class TestGroupStructure_from_component_arrays(tests.IrisTest): - def test_different_sizes(self): - arrays = {"a": np.arange(6), "b": np.arange(5)} - msg = "All array elements must have the same size." - with self.assertRaisesRegex(ValueError, msg): - GroupStructure.from_component_arrays(arrays) - - def test_structure_creation(self): - # Test that the appropriate dictionary containing ArrayStructures is - # computed when constructing a GroupStructure from_component_arrays. - array = np.arange(6) - expected_structure = {"a": ArrayStructure.from_array(array)} - - grp = GroupStructure.from_component_arrays({"a": array}) - - self.assertEqual(grp.length, 6) - self.assertEqual(grp._cmpt_structure, expected_structure) - - -class TestGroupStructure_possible_structures(tests.IrisTest): - def test_simple_3d_structure(self): - # Construct a structure representing a (3, 2, 4) group and assert - # that the result is of the expected form. - array_structures = { - "a": ArrayStructure(1, [1, -1, 2]), - "b": ArrayStructure(3, [1, -1]), - "c": ArrayStructure(6, [1, -1, 2, 3]), - } - structure = GroupStructure(24, array_structures, array_order="f") - expected = ( - [ - ("a", array_structures["a"]), - ("b", array_structures["b"]), - ("c", array_structures["c"]), - ], - ) - self.assertEqual(structure.possible_structures(), expected) - - def assert_potentials(self, length, array_structures, expected): - structure = GroupStructure(length, array_structures, array_order="f") - allowed = structure.possible_structures() - names = [ - [name for (name, _) in allowed_structure] - for allowed_structure in allowed - ] - self.assertEqual(names, expected) - - def test_multiple_potentials(self): - # More than one potential dimension for dim 1. - array_structures = regular_array_structures((4, 2, 3)) - array_structures["shared b"] = ArrayStructure(4, [-10, 4]) - self.assert_potentials( - 24, array_structures, [["a", "b", "c"], ["a", "shared b", "c"]] - ) - - def test_alternate_potentials(self): - # More than one potential dimension for dim 1. - array_structures = regular_array_structures((4, 2, 3)) - array_structures.update(regular_array_structures((6, 4), names="xy")) - self.assert_potentials( - 24, array_structures, [["x", "y"], ["a", "b", "c"]] - ) - - def test_shared_first_dimension(self): - # One 2d potential as well as one 3d, using the same first dimension. - array_structures = regular_array_structures((4, 2, 3)) - array_structures["bc combined"] = ArrayStructure(4, np.arange(6)) - self.assert_potentials( - 24, array_structures, [["a", "bc combined"], ["a", "b", "c"]] - ) - - def test_non_viable_element(self): - # One 2d potential as well as one 3d, using the same first dimension. - array_structures = regular_array_structures((4, 2, 3)) - array_structures.pop("c") - array_structures["strange_length"] = ArrayStructure(4, np.arange(5)) - self.assert_potentials(24, array_structures, []) - - def test_completely_unstructured_element(self): - # One of the arrays is entirely unstructured. - array_structures = regular_array_structures((4, 2, 3)) - array_structures["unstructured"] = None - self.assert_potentials(24, array_structures, [["a", "b", "c"]]) - - -class TestGroupStructure_build_arrays(tests.IrisTest): - def assert_built_array(self, name, result, expected): - ex_arr, ex_dims = expected - re_arr, re_dims = result[name] - self.assertEqual(ex_dims, re_dims) - self.assertArrayEqual(ex_arr, re_arr) - - def test_build_arrays_regular_f_order(self): - # Construct simple orthogonal 1d array structures, adding a trailing - # dimension to the second, and assert the result of build_arrays - # produces the required result. - elements = regular_array_structures((2, 3)) - - a = elements["a"].construct_array(6) - b = elements["b"].construct_array(6) - # Make b 2 dimensional. - b = np.vstack([b, b + 100]).T - - grp = GroupStructure(6, elements, array_order="f") - - result = grp.build_arrays((2, 3), {"a": a, "b": b}) - self.assert_built_array("a", result, ([0, 1], (0,))) - self.assert_built_array( - "b", result, ([[0, 100], [1, 101], [2, 102]], (1,)) - ) - - def test_build_arrays_unstructured(self): - # Check that an unstructured array gets reshaped appropriately. - grp = GroupStructure(6, {"a": None}, array_order="c") - orig = np.array([1, 2, 3, 4, 5, 6]).reshape(2, 3) - r = grp.build_arrays((2, 3), {"a": orig.flatten(order="c")}) - self.assert_built_array("a", r, (orig, (0, 1))) - - def test_build_arrays_unstructured_ndim_f_order(self): - # Passing an unstructured array to build_arrays, should result in the - # appropriately shaped array, plus any trailing dimensions. - grp = GroupStructure(6, {"a": None}, array_order="f") - orig = np.array([1, 2, 3, 4, 5, 6]).reshape(2, 3) - orig = np.dstack([orig, orig + 10]) - r = grp.build_arrays((2, 3), {"a": orig.reshape((-1, 2), order="f")}) - self.assert_built_array("a", r, (orig, (0, 1))) - - def test_build_arrays_unstructured_ndim_c_order(self): - # Passing an unstructured array to build_arrays, should result in the - # appropriately shaped array, plus any trailing dimensions. - grp = GroupStructure(6, {"a": None}, array_order="c") - orig = np.array([1, 2, 3, 4, 5, 6]).reshape(2, 3) - orig = np.dstack([orig, orig + 10]) - r = grp.build_arrays((2, 3), {"a": orig.reshape((-1, 2), order="c")}) - self.assert_built_array("a", r, (orig, (0, 1))) - - def test_structured_array_not_applicable(self): - # Just because an array has a possible structure, does not mean it - # gets used. Check that 'd' which would make a good 1D array, doesn't - # get used in a specific shape. - elements = regular_array_structures((2, 2, 3)) - elements["d"] = ArrayStructure(3, np.arange(4)) - grp = GroupStructure(12, elements, array_order="f") - - d = np.array([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3]).reshape( - (3, 4), order="f" - ) - expected = np.array([[[0, 1, 2], [0, 2, 3]], [[0, 1, 3], [1, 2, 3]]]) - r = grp.build_arrays( - (2, 2, 3), - { - "a": np.arange(12), - "b": np.arange(12), - "c": np.arange(12), - "d": d.flatten(order="f"), - }, - ) - self.assert_built_array("d", r, (expected, (0, 1, 2))) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/test_rules.py b/lib/iris/tests/unit/fileformats/test_rules.py deleted file mode 100644 index c243a374cb..0000000000 --- a/lib/iris/tests/unit/fileformats/test_rules.py +++ /dev/null @@ -1,269 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test iris.fileformats.rules.py - metadata translation rules. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import types -from unittest import mock - -import numpy as np - -from iris.aux_factory import HybridHeightFactory -from iris.coords import CellMethod -from iris.cube import Cube -from iris.fileformats.rules import ( - ConcreteReferenceTarget, - ConversionMetadata, - Factory, - Loader, - Reference, - ReferenceTarget, - load_cubes, - scalar_cell_method, -) -import iris.tests.stock as stock - - -class TestConcreteReferenceTarget(tests.IrisTest): - def test_attributes(self): - with self.assertRaises(TypeError): - target = ConcreteReferenceTarget() - - target = ConcreteReferenceTarget("foo") - self.assertEqual(target.name, "foo") - self.assertIsNone(target.transform) - - def transform(_): - return _ - - target = ConcreteReferenceTarget("foo", transform) - self.assertEqual(target.name, "foo") - self.assertIs(target.transform, transform) - - def test_single_cube_no_transform(self): - target = ConcreteReferenceTarget("foo") - src = stock.simple_2d() - target.add_cube(src) - self.assertIs(target.as_cube(), src) - - def test_single_cube_with_transform(self): - def transform(cube): - return {"long_name": "wibble"} - - target = ConcreteReferenceTarget("foo", transform) - src = stock.simple_2d() - target.add_cube(src) - dest = target.as_cube() - self.assertEqual(dest.long_name, "wibble") - self.assertNotEqual(dest, src) - dest.long_name = src.long_name - self.assertEqual(dest, src) - - @tests.skip_data - def test_multiple_cubes_no_transform(self): - target = ConcreteReferenceTarget("foo") - src = stock.realistic_4d() - for i in range(src.shape[0]): - target.add_cube(src[i]) - dest = target.as_cube() - self.assertIsNot(dest, src) - self.assertEqual(dest, src) - - @tests.skip_data - def test_multiple_cubes_with_transform(self): - def transform(cube): - return {"long_name": "wibble"} - - target = ConcreteReferenceTarget("foo", transform) - src = stock.realistic_4d() - for i in range(src.shape[0]): - target.add_cube(src[i]) - dest = target.as_cube() - self.assertEqual(dest.long_name, "wibble") - self.assertNotEqual(dest, src) - dest.long_name = src.long_name - self.assertEqual(dest, src) - - -class TestLoadCubes(tests.IrisTest): - def test_simple_factory(self): - # Test the creation process for a factory definition which only - # uses simple dict arguments. - - # Make a minimal fake data object that passes as lazy data. - core_data_array = mock.Mock(compute=None, dtype=np.dtype("f4")) - # Make a fake PPField which will be supplied to our converter. - field = mock.Mock( - core_data=mock.Mock(return_value=core_data_array), - realised_dtype=np.dtype("f4"), - bmdi=None, - ) - - def field_generator(filename): - return [field] - - # A fake conversion function returning: - # 1) A parameter cube needing a simple factory construction. - aux_factory = mock.Mock() - factory = mock.Mock() - factory.args = [{"name": "foo"}] - factory.factory_class = ( - lambda *args: setattr(aux_factory, "fake_args", args) - or aux_factory - ) - - def converter(field): - return ConversionMetadata( - [factory], [], "", "", "", {}, [], [], [] - ) - - # Finish by making a fake Loader - fake_loader = Loader(field_generator, {}, converter) - cubes = load_cubes(["fake_filename"], None, fake_loader) - - # Check the result is a generator with a single entry. - self.assertIsInstance(cubes, types.GeneratorType) - try: - # Suppress the normal Cube.coord() and Cube.add_aux_factory() - # methods. - coord_method = Cube.coord - add_aux_factory_method = Cube.add_aux_factory - Cube.coord = lambda self, **args: args - Cube.add_aux_factory = lambda self, aux_factory: setattr( - self, "fake_aux_factory", aux_factory - ) - - cubes = list(cubes) - finally: - Cube.coord = coord_method - Cube.add_aux_factory = add_aux_factory_method - self.assertEqual(len(cubes), 1) - # Check the "cube" has an "aux_factory" added, which itself - # must have been created with the correct arguments. - self.assertTrue(hasattr(cubes[0], "fake_aux_factory")) - self.assertIs(cubes[0].fake_aux_factory, aux_factory) - self.assertTrue(hasattr(aux_factory, "fake_args")) - self.assertEqual(aux_factory.fake_args, ({"name": "foo"},)) - - @tests.skip_data - def test_cross_reference(self): - # Test the creation process for a factory definition which uses - # a cross-reference. - - param_cube = stock.realistic_4d_no_derived() - orog_coord = param_cube.coord("surface_altitude") - param_cube.remove_coord(orog_coord) - - orog_cube = param_cube[0, 0, :, :] - orog_cube.data = orog_coord.points - orog_cube.rename("surface_altitude") - orog_cube.units = orog_coord.units - orog_cube.attributes = orog_coord.attributes - - # We're going to test for the presence of the hybrid height - # stuff later, so let's make sure it's not already there! - assert len(param_cube.aux_factories) == 0 - assert not param_cube.coords("surface_altitude") - - # The fake PPFields which will be supplied to our converter. - press_field = mock.Mock( - core_data=mock.Mock(return_value=param_cube.data), - bmdi=-1e20, - realised_dtype=param_cube.dtype, - ) - - orog_field = mock.Mock( - core_data=mock.Mock(return_value=orog_cube.data), - bmdi=-1e20, - realised_dtype=orog_cube.dtype, - ) - - def field_generator(filename): - return [press_field, orog_field] - - # A fake rule set returning: - # 1) A parameter cube needing an "orography" reference - # 2) An "orography" cube - - def converter(field): - if field is press_field: - src = param_cube - factories = [ - Factory(HybridHeightFactory, [Reference("orography")]) - ] - references = [] - else: - src = orog_cube - factories = [] - references = [ReferenceTarget("orography", None)] - dim_coords_and_dims = [ - (coord, src.coord_dims(coord)[0]) for coord in src.dim_coords - ] - aux_coords_and_dims = [ - (coord, src.coord_dims(coord)) for coord in src.aux_coords - ] - return ConversionMetadata( - factories, - references, - src.standard_name, - src.long_name, - src.units, - src.attributes, - src.cell_methods, - dim_coords_and_dims, - aux_coords_and_dims, - ) - - # Finish by making a fake Loader - fake_loader = Loader(field_generator, {}, converter) - cubes = load_cubes(["fake_filename"], None, fake_loader) - - # Check the result is a generator containing two Cubes. - self.assertIsInstance(cubes, types.GeneratorType) - cubes = list(cubes) - self.assertEqual(len(cubes), 2) - # Check the "cube" has an "aux_factory" added, which itself - # must have been created with the correct arguments. - self.assertEqual(len(cubes[1].aux_factories), 1) - self.assertEqual(len(cubes[1].coords("surface_altitude")), 1) - - -class Test_scalar_cell_method(tests.IrisTest): - """Tests for iris.fileformats.rules.scalar_cell_method() function""" - - def setUp(self): - self.cube = stock.simple_2d() - self.cm = CellMethod("mean", "foo", "1 hour") - self.cube.cell_methods = (self.cm,) - - def test_cell_method_found(self): - actual = scalar_cell_method(self.cube, "mean", "foo") - self.assertEqual(actual, self.cm) - - def test_method_different(self): - actual = scalar_cell_method(self.cube, "average", "foo") - self.assertIsNone(actual) - - def test_coord_name_different(self): - actual = scalar_cell_method(self.cube, "average", "bar") - self.assertIsNone(actual) - - def test_double_coord_fails(self): - self.cube.cell_methods = ( - CellMethod("mean", ("foo", "bar"), ("1 hour", "1 hour")), - ) - actual = scalar_cell_method(self.cube, "mean", "foo") - self.assertIsNone(actual) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/um/__init__.py b/lib/iris/tests/unit/fileformats/um/__init__.py deleted file mode 100644 index 6b4abc61bb..0000000000 --- a/lib/iris/tests/unit/fileformats/um/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.fileformats.um` package.""" diff --git a/lib/iris/tests/unit/fileformats/um/fast_load/__init__.py b/lib/iris/tests/unit/fileformats/um/fast_load/__init__.py deleted file mode 100644 index b5eb259e5b..0000000000 --- a/lib/iris/tests/unit/fileformats/um/fast_load/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the module :mod:`iris.fileformats.um._fast_load`. - -""" diff --git a/lib/iris/tests/unit/fileformats/um/fast_load/test_FieldCollation.py b/lib/iris/tests/unit/fileformats/um/fast_load/test_FieldCollation.py deleted file mode 100644 index 0c15e5e839..0000000000 --- a/lib/iris/tests/unit/fileformats/um/fast_load/test_FieldCollation.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the class -:class:`iris.fileformats.um._fast_load.FieldCollation`. - -This only tests the additional functionality for recording file locations of -PPFields that make loaded cubes. -The original class is the baseclass of this, now renamed 'BasicFieldCollation'. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -import iris -from iris.tests.integration.fast_load.test_fast_load import Mixin_FieldTest - - -class TestFastCallbackLocationInfo(Mixin_FieldTest, tests.IrisTest): - do_fast_loads = True - - def setUp(self): - # Call parent setup. - super().setUp() - - # Create a basic load test case. - self.callback_collations = [] - self.callback_filepaths = [] - - def fast_load_callback(cube, collation, filename): - self.callback_collations.append(collation) - self.callback_filepaths.append(filename) - - flds = self.fields(c_t="11112222", c_h="11221122", phn="01010101") - self.test_filepath = self.save_fieldcubes(flds) - iris.load(self.test_filepath, callback=fast_load_callback) - - def test_callback_collations_filepaths(self): - self.assertEqual(len(self.callback_collations), 2) - self.assertEqual( - self.callback_collations[0].data_filepath, self.test_filepath - ) - self.assertEqual( - self.callback_collations[1].data_filepath, self.test_filepath - ) - - def test_callback_collations_field_indices(self): - self.assertEqual( - self.callback_collations[0].data_field_indices.dtype, np.int64 - ) - self.assertArrayEqual( - self.callback_collations[0].data_field_indices, [[1, 3], [5, 7]] - ) - - self.assertEqual( - self.callback_collations[1].data_field_indices.dtype, np.int64 - ) - self.assertArrayEqual( - self.callback_collations[1].data_field_indices, [[0, 2], [4, 6]] - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/um/fast_load/test__convert_collation.py b/lib/iris/tests/unit/fileformats/um/fast_load/test__convert_collation.py deleted file mode 100644 index 90c411b41d..0000000000 --- a/lib/iris/tests/unit/fileformats/um/fast_load/test__convert_collation.py +++ /dev/null @@ -1,450 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :func:`iris.fileformats.um._fast_load._convert_collation`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import cf_units -import cftime -import numpy as np - -import iris.aux_factory -import iris.coord_systems -import iris.coords -import iris.fileformats.pp -import iris.fileformats.rules -from iris.fileformats.um._fast_load import ( - _convert_collation as convert_collation, -) - -COORD_SYSTEM = iris.coord_systems.GeogCS(6371229.0) -LATITUDE = iris.coords.DimCoord( - [15, 0, -15], "latitude", units="degrees", coord_system=COORD_SYSTEM -) -LONGITUDE = iris.coords.DimCoord( - [0, 20, 40, 60], - "longitude", - units="degrees", - coord_system=COORD_SYSTEM, - circular=True, -) - - -class Test(tests.IrisTest): - def _field(self): - # Create PP field for X wind on a regular lat-lon grid. - header = [0] * 64 - # Define the regular lat-lon grid. - header[15] = 1 # LBCODE - header[17] = 3 # LBROW - header[18] = 4 # LBNPT - header[55] = 90 # BPLAT - header[58] = 30 # BZY - header[59] = -15 # BDY - header[60] = -20 # BZX - header[61] = 20 # BDX - # Define the STASH code m01s00i002. - header[41] = 2 # LBUSER(4) - header[44] = 1 # LBUSER(7) - field = iris.fileformats.pp.PPField3(header) - return field - - def _check_phenomenon(self, metadata, factory=None): - if factory is None: - self.assertEqual(metadata.factories, []) - else: - self.assertEqual(metadata.factories, [factory]) - self.assertEqual(metadata.references, []) - self.assertEqual(metadata.standard_name, "x_wind") - self.assertIsNone(metadata.long_name) - self.assertEqual(metadata.units, cf_units.Unit("m s-1")) - self.assertEqual(metadata.attributes, {"STASH": (1, 0, 2)}) - self.assertEqual(metadata.cell_methods, []) - - def test_all_scalar(self): - field = self._field() - field.lbtim = 11 - field.t1 = cftime.datetime(1970, 1, 1, 18) - field.t2 = cftime.datetime(1970, 1, 1, 12) - collation = mock.Mock( - fields=[field], vector_dims_shape=(), element_arrays_and_dims={} - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - coords_and_dims = [(LONGITUDE, 1), (LATITUDE, 0)] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [ - ( - iris.coords.DimCoord(18, "time", units="hours since epoch"), - None, - ), - ( - iris.coords.DimCoord( - 12, "forecast_reference_time", units="hours since epoch" - ), - None, - ), - (iris.coords.DimCoord(6, "forecast_period", units="hours"), None), - ] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_vector_t1(self): - field = self._field() - field.lbtim = 11 - field.t2 = cftime.datetime(1970, 1, 1, 12) - t1 = ( - [ - cftime.datetime(1970, 1, 1, 18), - cftime.datetime(1970, 1, 2, 0), - cftime.datetime(1970, 1, 2, 6), - ], - [0], - ) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(3,), - element_arrays_and_dims={"t1": t1}, - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - coords_and_dims = [ - (LONGITUDE, 2), - (LATITUDE, 1), - ( - iris.coords.DimCoord( - [18, 24, 30], "time", units="hours since epoch" - ), - (0,), - ), - ] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [ - ( - iris.coords.DimCoord( - 12, "forecast_reference_time", units="hours since epoch" - ), - None, - ), - ( - iris.coords.DimCoord( - [6, 12, 18], "forecast_period", units="hours" - ), - (0,), - ), - ] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_vector_t2(self): - field = self._field() - field.lbtim = 11 - field.t1 = cftime.datetime(1970, 1, 1, 18) - t2 = ( - [ - cftime.datetime(1970, 1, 1, 12), - cftime.datetime(1970, 1, 1, 15), - cftime.datetime(1970, 1, 1, 18), - ], - [0], - ) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(3,), - element_arrays_and_dims={"t2": t2}, - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - coords_and_dims = [ - (LONGITUDE, 2), - (LATITUDE, 1), - ( - iris.coords.DimCoord( - [12, 15, 18], - "forecast_reference_time", - units="hours since epoch", - ), - (0,), - ), - ] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [ - ( - iris.coords.DimCoord(18, "time", units="hours since epoch"), - None, - ), - ( - iris.coords.DimCoord( - [6, 3, 0.0], "forecast_period", units="hours" - ), - (0,), - ), - ] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_vector_lbft(self): - field = self._field() - field.lbtim = 21 - field.t1 = cftime.datetime(1970, 1, 1, 12) - field.t2 = cftime.datetime(1970, 1, 1, 18) - lbft = ([18, 15, 12], [0]) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(3,), - element_arrays_and_dims={"lbft": lbft}, - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - coords_and_dims = [ - (LONGITUDE, 2), - (LATITUDE, 1), - ( - iris.coords.DimCoord( - [0, 3, 6], - "forecast_reference_time", - units="hours since epoch", - ), - (0,), - ), - ] - coords_and_dims = [ - ( - iris.coords.DimCoord( - 15, "time", units="hours since epoch", bounds=[[12, 18]] - ), - None, - ), - ( - iris.coords.DimCoord( - [15, 12, 9], - "forecast_period", - units="hours", - bounds=[[12, 18], [9, 15], [6, 12]], - ), - (0,), - ), - ] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_vector_t1_and_t2(self): - field = self._field() - field.lbtim = 11 - t1 = ( - [ - cftime.datetime(1970, 1, 2, 6), - cftime.datetime(1970, 1, 2, 9), - cftime.datetime(1970, 1, 2, 12), - ], - [1], - ) - t2 = ( - [cftime.datetime(1970, 1, 1, 12), cftime.datetime(1970, 1, 2, 0)], - [0], - ) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(2, 3), - element_arrays_and_dims={"t1": t1, "t2": t2}, - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - coords_and_dims = [ - (LONGITUDE, 3), - (LATITUDE, 2), - ( - iris.coords.DimCoord( - [30, 33, 36], "time", units="hours since epoch" - ), - (1,), - ), - ( - iris.coords.DimCoord( - [12, 24], - "forecast_reference_time", - units="hours since epoch", - ), - (0,), - ), - ] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [ - ( - iris.coords.AuxCoord( - [[18, 21, 24], [6, 9, 12]], - "forecast_period", - units="hours", - ), - (0, 1), - ) - ] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_vertical_pressure(self): - field = self._field() - field.lbvc = 8 - blev = ([1000, 850, 700], (0,)) - lblev = ([1000, 850, 700], (0,)) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(3,), - element_arrays_and_dims={"blev": blev, "lblev": lblev}, - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - coords_and_dims = [ - (LONGITUDE, 2), - (LATITUDE, 1), - ( - iris.coords.DimCoord( - [1000, 850, 700], long_name="pressure", units="hPa" - ), - (0,), - ), - ] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_soil_level(self): - field = self._field() - field.lbvc = 6 - points = [10, 20, 30] - lower = [0] * 3 - upper = [0] * 3 - lblev = (points, (0,)) - brsvd1 = (lower, (0,)) - brlev = (upper, (0,)) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(3,), - element_arrays_and_dims={ - "lblev": lblev, - "brsvd1": brsvd1, - "brlev": brlev, - }, - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - level = iris.coords.DimCoord( - points, - long_name="soil_model_level_number", - attributes={"positive": "down"}, - units="1", - ) - coords_and_dims = [(LONGITUDE, 2), (LATITUDE, 1), (level, (0,))] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_soil_depth(self): - field = self._field() - field.lbvc = 6 - points = [10, 20, 30] - lower = [0, 15, 25] - upper = [15, 25, 35] - blev = (points, (0,)) - brsvd1 = (lower, (0,)) - brlev = (upper, (0,)) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(3,), - element_arrays_and_dims={ - "blev": blev, - "brsvd1": brsvd1, - "brlev": brlev, - }, - ) - metadata = convert_collation(collation) - self._check_phenomenon(metadata) - depth = iris.coords.DimCoord( - points, - standard_name="depth", - bounds=np.vstack((lower, upper)).T, - units="m", - attributes={"positive": "down"}, - ) - coords_and_dims = [(LONGITUDE, 2), (LATITUDE, 1), (depth, (0,))] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - def test_vertical_hybrid_height(self): - field = self._field() - field.lbvc = 65 - blev = ([5, 18, 38], (0,)) - lblev = ([1000, 850, 700], (0,)) - brsvd1 = ([10, 26, 50], (0,)) - brsvd2 = ([0.9989, 0.9970, 0.9944], (0,)) - brlev = ([0, 10, 26], (0,)) - bhrlev = ([1, 0.9989, 0.9970], (0,)) - lblev = ([1, 2, 3], (0,)) - bhlev = ([0.9994, 0.9979, 0.9957], (0,)) - collation = mock.Mock( - fields=[field], - vector_dims_shape=(3,), - element_arrays_and_dims={ - "blev": blev, - "lblev": lblev, - "brsvd1": brsvd1, - "brsvd2": brsvd2, - "brlev": brlev, - "bhrlev": bhrlev, - "lblev": lblev, - "bhlev": bhlev, - }, - ) - metadata = convert_collation(collation) - factory = iris.fileformats.rules.Factory( - iris.aux_factory.HybridHeightFactory, - [ - {"long_name": "level_height"}, - {"long_name": "sigma"}, - iris.fileformats.rules.Reference("orography"), - ], - ) - self._check_phenomenon(metadata, factory) - coords_and_dims = [ - (LONGITUDE, 2), - (LATITUDE, 1), - ( - iris.coords.DimCoord( - [1, 2, 3], - "model_level_number", - attributes={"positive": "up"}, - units="1", - ), - (0,), - ), - ] - self.assertEqual(metadata.dim_coords_and_dims, coords_and_dims) - coords_and_dims = [ - ( - iris.coords.DimCoord( - [5, 18, 38], - long_name="level_height", - units="m", - bounds=[[0, 10], [10, 26], [26, 50]], - attributes={"positive": "up"}, - ), - (0,), - ), - ( - iris.coords.AuxCoord( - [0.9994, 0.9979, 0.9957], - long_name="sigma", - bounds=[[1, 0.9989], [0.9989, 0.9970], [0.9970, 0.9944]], - units="1", - ), - (0,), - ), - ] - self.assertEqual(metadata.aux_coords_and_dims, coords_and_dims) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/__init__.py b/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/__init__.py deleted file mode 100644 index f0932c3ac8..0000000000 --- a/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the module -:mod:`iris.fileformats.um._fast_load_structured_fields`. - -""" diff --git a/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_BasicFieldCollation.py b/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_BasicFieldCollation.py deleted file mode 100644 index 57100c79af..0000000000 --- a/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_BasicFieldCollation.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the class -:class:`iris.fileformats.um._fast_load_structured_fields.BasicFieldCollation`. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -from cftime import datetime -import numpy as np - -from iris._lazy_data import as_lazy_data -import iris.fileformats.pp -from iris.fileformats.um._fast_load_structured_fields import ( - BasicFieldCollation, -) - - -class Test___init__(tests.IrisTest): - def test_no_fields(self): - with self.assertRaises(AssertionError): - BasicFieldCollation([]) - - -class Test_fields(tests.IrisTest): - def test_preserve_members(self): - fields = ("foo", "bar", "wibble") - collation = BasicFieldCollation(fields) - self.assertEqual(collation.fields, fields) - - -def _make_field( - lbyr=None, lbyrd=None, lbft=None, blev=None, bhlev=None, data=None -): - header = [0] * 64 - if lbyr is not None: - header[0] = lbyr - header[1] = 1 - header[2] = 1 - if lbyrd is not None: - header[6] = lbyrd - header[7] = 1 - header[8] = 1 - if lbft is not None: - header[13] = lbft - if blev is not None: - header[51] = blev - if bhlev is not None: - header[53] = bhlev - field = iris.fileformats.pp.PPField3(header) - if data is not None: - _data = _make_data(data) - field.data = _data - return field - - -def _make_data(fill_value): - shape = (10, 10) - return as_lazy_data(np.ones(shape) * fill_value) - - -class Test_data(tests.IrisTest): - # Test order of the data attribute when fastest-varying element is changed. - def test_t1_varies_faster(self): - collation = BasicFieldCollation( - [ - _make_field(lbyr=2013, lbyrd=2000, data=0), - _make_field(lbyr=2014, lbyrd=2000, data=1), - _make_field(lbyr=2015, lbyrd=2000, data=2), - _make_field(lbyr=2013, lbyrd=2001, data=3), - _make_field(lbyr=2014, lbyrd=2001, data=4), - _make_field(lbyr=2015, lbyrd=2001, data=5), - ] - ) - result = collation.data[:, :, 0, 0] - expected = [[0, 1, 2], [3, 4, 5]] - self.assertArrayEqual(result, expected) - - def test_t2_varies_faster(self): - collation = BasicFieldCollation( - [ - _make_field(lbyr=2013, lbyrd=2000, data=0), - _make_field(lbyr=2013, lbyrd=2001, data=1), - _make_field(lbyr=2013, lbyrd=2002, data=2), - _make_field(lbyr=2014, lbyrd=2000, data=3), - _make_field(lbyr=2014, lbyrd=2001, data=4), - _make_field(lbyr=2014, lbyrd=2002, data=5), - ] - ) - result = collation.data[:, :, 0, 0] - expected = [[0, 1, 2], [3, 4, 5]] - self.assertArrayEqual(result, expected) - - -class Test_element_arrays_and_dims(tests.IrisTest): - def test_single_field(self): - field = _make_field(2013) - collation = BasicFieldCollation([field]) - self.assertEqual(collation.element_arrays_and_dims, {}) - - def test_t1(self): - collation = BasicFieldCollation( - [_make_field(lbyr=2013), _make_field(lbyr=2014)] - ) - result = collation.element_arrays_and_dims - self.assertEqual(list(result.keys()), ["t1"]) - values, dims = result["t1"] - self.assertArrayEqual( - values, [datetime(2013, 1, 1), datetime(2014, 1, 1)] - ) - self.assertEqual(dims, (0,)) - - def test_t1_and_t2(self): - collation = BasicFieldCollation( - [ - _make_field(lbyr=2013, lbyrd=2000), - _make_field(lbyr=2014, lbyrd=2001), - _make_field(lbyr=2015, lbyrd=2002), - ] - ) - result = collation.element_arrays_and_dims - self.assertEqual(set(result.keys()), set(["t1", "t2"])) - values, dims = result["t1"] - self.assertArrayEqual( - values, - [datetime(2013, 1, 1), datetime(2014, 1, 1), datetime(2015, 1, 1)], - ) - self.assertEqual(dims, (0,)) - values, dims = result["t2"] - self.assertArrayEqual( - values, - [datetime(2000, 1, 1), datetime(2001, 1, 1), datetime(2002, 1, 1)], - ) - self.assertEqual(dims, (0,)) - - def test_t1_and_t2_and_lbft(self): - collation = BasicFieldCollation( - [ - _make_field(lbyr=1, lbyrd=15, lbft=6), - _make_field(lbyr=1, lbyrd=16, lbft=9), - _make_field(lbyr=11, lbyrd=25, lbft=6), - _make_field(lbyr=11, lbyrd=26, lbft=9), - ] - ) - result = collation.element_arrays_and_dims - self.assertEqual(set(result.keys()), set(["t1", "t2", "lbft"])) - values, dims = result["t1"] - self.assertArrayEqual(values, [datetime(1, 1, 1), datetime(11, 1, 1)]) - self.assertEqual(dims, (0,)) - values, dims = result["t2"] - self.assertArrayEqual( - values, - [ - [datetime(15, 1, 1), datetime(16, 1, 1)], - [datetime(25, 1, 1), datetime(26, 1, 1)], - ], - ) - self.assertEqual(dims, (0, 1)) - values, dims = result["lbft"] - self.assertArrayEqual(values, [6, 9]) - self.assertEqual(dims, (1,)) - - def test_blev(self): - collation = BasicFieldCollation( - [_make_field(blev=1), _make_field(blev=2)] - ) - result = collation.element_arrays_and_dims - keys = set( - ["blev", "brsvd1", "brsvd2", "brlev", "bhrlev", "lblev", "bhlev"] - ) - self.assertEqual(set(result.keys()), keys) - values, dims = result["blev"] - self.assertArrayEqual(values, [1, 2]) - self.assertEqual(dims, (0,)) - - def test_bhlev(self): - collation = BasicFieldCollation( - [_make_field(blev=0, bhlev=1), _make_field(blev=1, bhlev=2)] - ) - result = collation.element_arrays_and_dims - keys = set( - ["blev", "brsvd1", "brsvd2", "brlev", "bhrlev", "lblev", "bhlev"] - ) - self.assertEqual(set(result.keys()), keys) - values, dims = result["bhlev"] - self.assertArrayEqual(values, [1, 2]) - self.assertEqual(dims, (0,)) - - -class Test__time_comparable_int(tests.IrisTest): - def test(self): - # Define a list of date-time tuples, which should remain both all - # distinct and in ascending order when converted... - test_date_tuples = [ - # Increment each component in turn to check that all are handled. - (2004, 1, 1, 0, 0, 0), - (2004, 1, 1, 0, 0, 1), - (2004, 1, 1, 0, 1, 0), - (2004, 1, 1, 1, 0, 0), - (2004, 1, 2, 0, 0, 0), - (2004, 2, 1, 0, 0, 0), - # Go across 2004-02-29 leap-day, and on to "Feb 31 .. Mar 1". - (2004, 2, 27, 0, 0, 0), - (2004, 2, 28, 0, 0, 0), - (2004, 2, 29, 0, 0, 0), - (2004, 2, 30, 0, 0, 0), - (2004, 2, 31, 0, 0, 0), - (2004, 3, 1, 0, 0, 0), - (2005, 1, 1, 0, 0, 0), - ] - - collation = BasicFieldCollation(["foo", "bar"]) - test_date_ints = [ - collation._time_comparable_int(*test_tuple) - for test_tuple in test_date_tuples - ] - # Check all values are distinct. - self.assertEqual(len(test_date_ints), len(set(test_date_ints))) - # Check all values are in order. - self.assertEqual(test_date_ints, sorted(test_date_ints)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_group_structured_fields.py b/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_group_structured_fields.py deleted file mode 100644 index b7ef9a62a3..0000000000 --- a/lib/iris/tests/unit/fileformats/um/fast_load_structured_fields/test_group_structured_fields.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function :func:\ -`iris.fileformats.um._fast_load_structured_fields.group_structured_fields`. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats.um._fast_load_structured_fields import ( - group_structured_fields, -) - - -def _convert_to_vector(value, length, default): - """ - Return argument (or default) in a list of length 'length'. - - The 'value' arg must either be scalar, or a list of length 'length'. - A value of None is replaced by the default. - If scalar, the value is replicated to the required length. - - """ - if value is None: - value = default - if hasattr(value, "__len__"): - assert len(value) == length - else: - value = [value] * length - return value - - -class Test__grouping(tests.IrisTest): - def _dummy_fields_iter(self, stashes=None, models=None, lbprocs=None): - # Make a group of test fields, and return an iterator over it. - a_vec = [vec for vec in (stashes, models, lbprocs) if vec is not None] - number = len(a_vec[0]) - stashes = _convert_to_vector(stashes, number, default=31) - models = _convert_to_vector(models, number, default=71) - lbprocs = _convert_to_vector(lbprocs, number, default=91) - self.test_fields = [ - mock.MagicMock( - lbuser=[0, 0, 0, x_stash, 0, 0, x_model], - lbproc=x_lbproc, - i_field=ind + 1001, - ) - for ind, x_stash, x_model, x_lbproc in zip( - range(number), stashes, models, lbprocs - ) - ] - return (fld for fld in self.test_fields) - - def _group_result(self, fields): - # Run the testee, but returning just the groups (not FieldCollations). - result = list(group_structured_fields(fields, collation_class=tuple)) - return result - - def _test_fields(self, item): - # Convert nested tuples/lists of field-numbers into fields. - if isinstance(item, int): - result = self.test_fields[item - 1001] - else: - result = type(item)(self._test_fields(el) for el in item) - return result - - def test_none(self): - null_iter = (x for x in []) - result = self._group_result(null_iter) - self.assertEqual(result, []) - - def test_one(self): - fields_iter = self._dummy_fields_iter(stashes=[1]) - result = self._group_result(fields_iter) - self.assertEqual(result, self._test_fields([(1001,)])) - - def test_allsame(self): - fields_iter = self._dummy_fields_iter(stashes=[1, 1, 1]) - result = self._group_result(fields_iter) - self.assertEqual(result, self._test_fields([(1001, 1002, 1003)])) - - def test_stashes_different(self): - fields_iter = self._dummy_fields_iter(stashes=[1, 1, 22, 1, 22, 333]) - result = self._group_result(fields_iter) - self.assertEqual( - result, - self._test_fields([(1001, 1002, 1004), (1003, 1005), (1006,)]), - ) - - def test_models_different(self): - fields_iter = self._dummy_fields_iter(models=[10, 21, 10]) - result = self._group_result(fields_iter) - self.assertEqual(result, self._test_fields([(1001, 1003), (1002,)])) - - def test_lbprocs_different(self): - fields_iter = self._dummy_fields_iter(lbprocs=[991, 995, 991]) - result = self._group_result(fields_iter) - self.assertEqual(result, self._test_fields([(1001, 1003), (1002,)])) - - def test_2d_combines(self): - fields_iter = self._dummy_fields_iter( - stashes=[11, 11, 15, 11], lbprocs=[31, 42, 31, 42] - ) - result = self._group_result(fields_iter) - self.assertEqual( - result, self._test_fields([(1001,), (1002, 1004), (1003,)]) - ) - - def test_sortorder(self): - fields_iter = self._dummy_fields_iter(stashes=[11, 7, 12]) - result = self._group_result(fields_iter) - self.assertEqual( - result, self._test_fields([(1002,), (1001,), (1003,)]) - ) - - def test_sortorder_2d(self): - fields_iter = self._dummy_fields_iter( - stashes=[11, 11, 12], lbprocs=[31, 9, 1] - ) - result = self._group_result(fields_iter) - self.assertEqual( - result, self._test_fields([(1002,), (1001,), (1003,)]) - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/um/optimal_array_structuring/__init__.py b/lib/iris/tests/unit/fileformats/um/optimal_array_structuring/__init__.py deleted file mode 100644 index 8070719de8..0000000000 --- a/lib/iris/tests/unit/fileformats/um/optimal_array_structuring/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the module -:mod:`iris.fileformats.um._optimal_array_structuring`. - -""" diff --git a/lib/iris/tests/unit/fileformats/um/optimal_array_structuring/test_optimal_array_structure.py b/lib/iris/tests/unit/fileformats/um/optimal_array_structuring/test_optimal_array_structure.py deleted file mode 100644 index 96566f3c80..0000000000 --- a/lib/iris/tests/unit/fileformats/um/optimal_array_structuring/test_optimal_array_structure.py +++ /dev/null @@ -1,260 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function -:func:`iris.fileformats.um._optimal_array_structuring.optimal_array_structure`. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.fileformats.um._optimal_array_structuring import ( - optimal_array_structure, -) - - -class Test__optimal_dimensioning_structure: - pass - - -class Test_optimal_array_structure(tests.IrisTest): - def _check_arrays_and_dims(self, result, spec): - self.assertEqual(set(result.keys()), set(spec.keys())) - for keyname in spec.keys(): - result_array, result_dims = result[keyname] - spec_array, spec_dims = spec[keyname] - self.assertEqual( - result_dims, - spec_dims, - 'element dims differ for "{}": ' - "result={!r}, expected {!r}".format( - keyname, result_dims, spec_dims - ), - ) - self.assertArrayEqual( - result_array, - spec_array, - 'element arrays differ for "{}": ' - "result={!r}, expected {!r}".format( - keyname, result_array, spec_array - ), - ) - - def test_none(self): - with self.assertRaises(IndexError): - _ = optimal_array_structure([], []) - - def test_one(self): - # A single value does not make a dimension (no length-1 dims). - elements = [("a", np.array([1]))] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, ()) - self.assertEqual(primaries, set()) - self.assertEqual(elems_and_dims, {}) - - def test_1d(self): - elements = [("a", np.array([1, 2, 4]))] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (3,)) - self.assertEqual(primaries, set("a")) - self._check_arrays_and_dims( - elems_and_dims, {"a": (np.array([1, 2, 4]), (0,))} - ) - - def test_1d_actuals(self): - # Test use of alternate element values for array construction. - elements = [("a", np.array([1, 2, 4]))] - actual_values = [("a", np.array([7, 3, 9]))] - shape, primaries, elems_and_dims = optimal_array_structure( - elements, actual_values - ) - self.assertEqual(shape, (3,)) - self.assertEqual(primaries, set("a")) - self._check_arrays_and_dims( - elems_and_dims, {"a": (np.array([7, 3, 9]), (0,))} - ) - - def test_actuals_mismatch_fail(self): - elements = [("a", np.array([1, 2, 4]))] - actual_values = [("b", np.array([7, 3, 9]))] - with self.assertRaisesRegex(ValueError, "Names.* do not match.*"): - shape, primaries, elems_and_dims = optimal_array_structure( - elements, actual_values - ) - - def test_2d(self): - elements = [ - ("a", np.array([2, 2, 2, 3, 3, 3])), - ("b", np.array([7, 8, 9, 7, 8, 9])), - ] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (2, 3)) - self.assertEqual(primaries, set(["a", "b"])) - self._check_arrays_and_dims( - elems_and_dims, - {"a": (np.array([2, 3]), (0,)), "b": (np.array([7, 8, 9]), (1,))}, - ) - - def test_2d_with_element_values(self): - # Confirm that elements values are used in the output when supplied. - elements = [ - ("a", np.array([2, 2, 2, 3, 3, 3])), - ("b", np.array([7, 8, 9, 7, 8, 9])), - ] - elements_values = [ - ("a", np.array([6, 6, 6, 8, 8, 8])), - ("b", np.array([3, 4, 5, 3, 4, 5])), - ] - shape, primaries, elems_and_dims = optimal_array_structure( - elements, elements_values - ) - self.assertEqual(shape, (2, 3)) - self.assertEqual(primaries, set(["a", "b"])) - self._check_arrays_and_dims( - elems_and_dims, - {"a": (np.array([6, 8]), (0,)), "b": (np.array([3, 4, 5]), (1,))}, - ) - - def test_non_2d(self): - # An incomplete 2d expansion just becomes 1d - elements = [ - ("a", np.array([2, 2, 2, 3, 3])), - ("b", np.array([7, 8, 9, 7, 8])), - ] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (5,)) - self.assertEqual(primaries, set()) - self._check_arrays_and_dims( - elems_and_dims, - { - "a": (np.array([2, 2, 2, 3, 3]), (0,)), - "b": (np.array([7, 8, 9, 7, 8]), (0,)), - }, - ) - - def test_degenerate(self): - # A all-same vector does not appear in the output. - elements = [("a", np.array([1, 2, 3])), ("b", np.array([4, 4, 4]))] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (3,)) - self.assertEqual(primaries, set(["a"])) - self._check_arrays_and_dims( - elems_and_dims, {"a": (np.array([1, 2, 3]), (0,))} - ) - - def test_1d_duplicates(self): - # When two have the same structure, the first is 'the dimension'. - elements = [("a", np.array([1, 3, 4])), ("b", np.array([6, 7, 9]))] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (3,)) - self.assertEqual(primaries, set("a")) - self._check_arrays_and_dims( - elems_and_dims, - { - "a": (np.array([1, 3, 4]), (0,)), - "b": (np.array([6, 7, 9]), (0,)), - }, - ) - - def test_1d_duplicates_order(self): - # Same as previous but reverse passed order of elements 'a' and 'b'. - elements = [("b", np.array([6, 7, 9])), ("a", np.array([1, 3, 4]))] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (3,)) - # The only difference is the one chosen as 'principal' - self.assertEqual(primaries, set("b")) - self._check_arrays_and_dims( - elems_and_dims, - { - "a": (np.array([1, 3, 4]), (0,)), - "b": (np.array([6, 7, 9]), (0,)), - }, - ) - - def test_3_way(self): - elements = [ - ("t1", np.array([2, 3, 4])), - ("t2", np.array([4, 5, 6])), - ("period", np.array([9, 8, 7])), - ] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (3,)) - self.assertEqual(primaries, set(["t1"])) - self._check_arrays_and_dims( - elems_and_dims, - { - "t1": (np.array([2, 3, 4]), (0,)), - "t2": (np.array([4, 5, 6]), (0,)), - "period": (np.array([9, 8, 7]), (0,)), - }, - ) - - def test_mixed_dims(self): - elements = [ - ("t1", np.array([1, 1, 11, 11])), - ("t2", np.array([15, 16, 25, 26])), - ("ft", np.array([15, 16, 15, 16])), - ] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (2, 2)) - self.assertEqual(primaries, set(["t1", "ft"])) - self._check_arrays_and_dims( - elems_and_dims, - { - "t1": (np.array([1, 11]), (0,)), - "t2": (np.array([[15, 16], [25, 26]]), (0, 1)), - "ft": (np.array([15, 16]), (1,)), - }, - ) - - def test_missing_dim(self): - # Case with no dimension element for dimension 1. - elements = [ - ("t1", np.array([1, 1, 11, 11])), - ("t2", np.array([15, 16, 25, 26])), - ] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (4,)) - # The potential 2d nature can not be recognised. - # 't1' is auxiliary, as it has duplicate values over the dimension. - self.assertEqual(primaries, set(["t2"])) - self._check_arrays_and_dims( - elems_and_dims, - { - "t1": (np.array([1, 1, 11, 11]), (0,)), - "t2": (np.array([15, 16, 25, 26]), (0,)), - }, - ) - - def test_optimal_structure_decision(self): - # Checks the optimal structure decision logic is working correctly: - # given the arrays we have here we would expect 'a' to be the primary - # dimension, as it has higher priority for being supplied first. - elements = [ - ("a", np.array([1, 1, 1, 2, 2, 2])), - ("b", np.array([0, 1, 2, 0, 1, 2])), - ("c", np.array([11, 11, 11, 14, 14, 14])), - ("d", np.array([10, 10, 10, 10, 10, 10])), - ] - shape, primaries, elems_and_dims = optimal_array_structure(elements) - self.assertEqual(shape, (2, 3)) - self.assertEqual(primaries, set(["a", "b"])) - self._check_arrays_and_dims( - elems_and_dims, - { - "a": (np.array([1, 2]), (0,)), - "c": (np.array([11, 14]), (0,)), - "b": (np.array([0, 1, 2]), (1,)), - }, - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/fileformats/um/test_um_to_pp.py b/lib/iris/tests/unit/fileformats/um/test_um_to_pp.py deleted file mode 100644 index ef6369f638..0000000000 --- a/lib/iris/tests/unit/fileformats/um/test_um_to_pp.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the function -:func:`iris.fileformats.um.um_to_pp`. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -from iris.fileformats.um import um_to_pp - - -class Test_call(tests.IrisTest): - def test__call(self): - # Check that the function creates an FF2PP and returns the result - # of iterating over it. - - # Make a real (test) iterator object, as otherwise iter() complains... - mock_iterator = (1 for x in ()) - # Make a mock for the iter() call of an FF2PP object. - mock_iter_call = mock.MagicMock(return_value=mock_iterator) - # Make a mock FF2PP object instance. - mock_ff2pp_instance = mock.MagicMock(__iter__=mock_iter_call) - # Make the mock FF2PP class. - mock_ff2pp_class = mock.MagicMock(return_value=mock_ff2pp_instance) - - # Call um_to_pp while patching the um._ff_replacement.FF2PP class. - test_path = "/any/old/file.name" - with mock.patch( - "iris.fileformats.um._ff_replacement.FF2PP", mock_ff2pp_class - ): - result = um_to_pp(test_path) - - # Check that it called FF2PP in the expected way. - self.assertEqual( - mock_ff2pp_class.call_args_list, - [mock.call("/any/old/file.name", read_data=False)], - ) - self.assertEqual( - mock_ff2pp_instance.__iter__.call_args_list, [mock.call()] - ) - - # Check that it returned the expected result. - self.assertIs(result, mock_iterator) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/io/__init__.py b/lib/iris/tests/unit/io/__init__.py deleted file mode 100644 index 5e347c9ebc..0000000000 --- a/lib/iris/tests/unit/io/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.io` package.""" diff --git a/lib/iris/tests/unit/io/test__generate_cubes.py b/lib/iris/tests/unit/io/test__generate_cubes.py deleted file mode 100755 index 3a896a111c..0000000000 --- a/lib/iris/tests/unit/io/test__generate_cubes.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.io._generate_cubes` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from pathlib import Path - -import iris - - -class TestGenerateCubes(tests.IrisTest): - def test_pathlib_paths(self): - test_variants = [ - ("string", "string"), - (["string"], "string"), - (Path("string"), Path("string")), - ] - - decode_uri_mock = self.patch( - "iris.iris.io.decode_uri", return_value=("file", None) - ) - self.patch("iris.iris.io.load_files") - - for gc_arg, du_arg in test_variants: - decode_uri_mock.reset_mock() - list(iris._generate_cubes(gc_arg, None, None)) - decode_uri_mock.assert_called_with(du_arg) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/io/test_expand_filespecs.py b/lib/iris/tests/unit/io/test_expand_filespecs.py deleted file mode 100644 index 8720478153..0000000000 --- a/lib/iris/tests/unit/io/test_expand_filespecs.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.io.expand_filespecs` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import os -from pathlib import Path -import shutil -import tempfile -import textwrap - -import iris.io as iio - - -class TestExpandFilespecs(tests.IrisTest): - def setUp(self): - tests.IrisTest.setUp(self) - self.tmpdir = os.path.realpath(tempfile.mkdtemp()) - self.fnames = ["a.foo", "b.txt"] - for fname in self.fnames: - with open(os.path.join(self.tmpdir, fname), "w") as fh: - fh.write("anything") - - def tearDown(self): - shutil.rmtree(self.tmpdir) - - def test_absolute_path(self): - result = iio.expand_filespecs([os.path.join(self.tmpdir, "*")]) - expected = [os.path.join(self.tmpdir, fname) for fname in self.fnames] - self.assertEqual(result, expected) - - def test_double_slash(self): - product = iio.expand_filespecs(["//" + os.path.join(self.tmpdir, "*")]) - predicted = [os.path.join(self.tmpdir, fname) for fname in self.fnames] - self.assertEqual(product, predicted) - - def test_relative_path(self): - cwd = os.getcwd() - try: - os.chdir(self.tmpdir) - item_out = iio.expand_filespecs(["*"]) - item_in = [ - os.path.join(self.tmpdir, fname) for fname in self.fnames - ] - self.assertEqual(item_out, item_in) - finally: - os.chdir(cwd) - - def test_return_order(self): - # It is really quite important what order we return the - # files. They should be in the order that was provided, - # so that we can control the order of load (for instance, - # this can be used with PP files to ensure that there is - # a surface reference). - patterns = [ - os.path.join(self.tmpdir, "a.*"), - os.path.join(self.tmpdir, "b.*"), - ] - expected = [ - os.path.join(self.tmpdir, fname) for fname in ["a.foo", "b.txt"] - ] - result = iio.expand_filespecs(patterns) - self.assertEqual(result, expected) - result = iio.expand_filespecs(patterns[::-1]) - self.assertEqual(result, expected[::-1]) - - def test_no_files_found(self): - msg = r"\/no_exist.txt\" didn\'t match any files" - with self.assertRaisesRegex(IOError, msg): - iio.expand_filespecs([os.path.join(self.tmpdir, "no_exist.txt")]) - - def test_files_and_none(self): - with self.assertRaises(IOError) as err: - iio.expand_filespecs( - [ - os.path.join(self.tmpdir, "does_not_exist.txt"), - os.path.join(self.tmpdir, "*"), - ] - ) - expected = ( - textwrap.dedent( - """ - One or more of the files specified did not exist: - * "{0}/does_not_exist.txt" didn\'t match any files - - "{0}/*" matched 2 file(s) - """ - ) - .strip() - .format(self.tmpdir) - ) - - self.assertMultiLineEqual(str(err.exception), expected) - - def test_false_bool_absolute(self): - tempdir = self.tmpdir - msg = os.path.join(tempdir, "no_exist.txt") - (result,) = iio.expand_filespecs([msg], False) - self.assertEqual(result, msg) - - def test_false_bool_home(self): - # ensure that not only does files_expected not error, - # but that the path is still expanded from a ~ - msg = str(Path().home() / "no_exist.txt") - (result,) = iio.expand_filespecs(["~/no_exist.txt"], False) - self.assertEqual(result, msg) - - def test_false_bool_relative(self): - cwd = os.getcwd() - try: - os.chdir(self.tmpdir) - item_out = iio.expand_filespecs(["no_exist.txt"], False) - item_in = [os.path.join(self.tmpdir, "no_exist.txt")] - self.assertEqual(item_out, item_in) - finally: - os.chdir(cwd) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/io/test_run_callback.py b/lib/iris/tests/unit/io/test_run_callback.py deleted file mode 100644 index 94ae7ac09d..0000000000 --- a/lib/iris/tests/unit/io/test_run_callback.py +++ /dev/null @@ -1,83 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.io.run_callback` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import iris.exceptions -import iris.io - - -class Test_run_callback(tests.IrisTest): - def setUp(self): - tests.IrisTest.setUp(self) - self.cube = mock.sentinel.cube - - def test_no_callback(self): - # No callback results in the cube being returned. - self.assertEqual( - iris.io.run_callback(None, self.cube, None, None), self.cube - ) - - def test_ignore_cube(self): - # Ignore cube should result in None being returned. - def callback(cube, field, fname): - raise iris.exceptions.IgnoreCubeException() - - cube = self.cube - self.assertEqual( - iris.io.run_callback(callback, cube, None, None), None - ) - - def test_callback_no_return(self): - # Check that a callback not returning anything still results in the - # cube being passed back from "run_callback". - def callback(cube, field, fname): - pass - - cube = self.cube - self.assertEqual( - iris.io.run_callback(callback, cube, None, None), cube - ) - - def test_bad_callback_return_type(self): - # Check that a TypeError is raised with a bad callback return value. - def callback(cube, field, fname): - return iris.cube.CubeList() - - with self.assertRaisesRegex( - TypeError, "Callback function returned an " "unhandled data type." - ): - iris.io.run_callback(callback, None, None, None) - - def test_bad_signature(self): - # Check that a TypeError is raised with a bad callback function - # signature. - def callback(cube): - pass - - with self.assertRaisesRegex(TypeError, "takes 1 positional argument "): - iris.io.run_callback(callback, None, None, None) - - def test_callback_args(self): - # Check that the appropriate args are passed through to the callback. - self.field = mock.sentinel.field - self.fname = mock.sentinel.fname - - def callback(cube, field, fname): - self.assertEqual(cube, self.cube) - self.assertEqual(field, self.field) - self.assertEqual(fname, self.fname) - - iris.io.run_callback(callback, self.cube, self.field, self.fname) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/io/test_save.py b/lib/iris/tests/unit/io/test_save.py deleted file mode 100755 index 623cf417f2..0000000000 --- a/lib/iris/tests/unit/io/test_save.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.io.save` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from pathlib import Path -from unittest import mock - -import iris -from iris.cube import Cube - - -class TestSave(tests.IrisTest): - def test_pathlib_save(self): - file_mock = mock.Mock() - # Have to configure after creation because "name" is special - file_mock.configure_mock(name="string") - - find_saver_mock = self.patch( - "iris.io.find_saver", return_value=(lambda *args, **kwargs: None) - ) - - def replace_expand(file_specs, files_expected=True): - return file_specs - - # does not expand filepaths due to patch - self.patch("iris.io.expand_filespecs", replace_expand) - - test_variants = [ - ("string", "string"), - (Path("string/string"), "string/string"), - (file_mock, "string"), - ] - - for target, fs_val in test_variants: - try: - iris.save(Cube([]), target) - except ValueError: - print("ValueError") - pass - find_saver_mock.assert_called_with(fs_val) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/__init__.py b/lib/iris/tests/unit/lazy_data/__init__.py deleted file mode 100644 index b463897c50..0000000000 --- a/lib/iris/tests/unit/lazy_data/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._lazy_data` module.""" diff --git a/lib/iris/tests/unit/lazy_data/test_as_concrete_data.py b/lib/iris/tests/unit/lazy_data/test_as_concrete_data.py deleted file mode 100644 index 1a98c81fac..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_as_concrete_data.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._lazy data.as_concrete_data`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import as_concrete_data, as_lazy_data, is_lazy_data - - -class MyProxy: - def __init__(self, a): - self.shape = a.shape - self.dtype = a.dtype - self.ndim = a.ndim - self.a = a - - def __getitem__(self, keys): - return self.a[keys] - - -class Test_as_concrete_data(tests.IrisTest): - def test_concrete_input_data(self): - data = np.arange(24).reshape((4, 6)) - result = as_concrete_data(data) - self.assertIs(data, result) - self.assertFalse(is_lazy_data(result)) - - def test_concrete_masked_input_data(self): - data = ma.masked_array([10, 12, 8, 2], mask=[True, True, False, True]) - result = as_concrete_data(data) - self.assertIs(data, result) - self.assertFalse(is_lazy_data(result)) - - def test_lazy_data(self): - data = np.arange(24).reshape((2, 12)) - lazy_array = as_lazy_data(data) - self.assertTrue(is_lazy_data(lazy_array)) - result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, data) - - def test_lazy_mask_data(self): - data = np.arange(24).reshape((2, 12)) - fill_value = 1234 - mask_data = ma.masked_array(data, fill_value=fill_value) - lazy_array = as_lazy_data(mask_data) - self.assertTrue(is_lazy_data(lazy_array)) - result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertMaskedArrayEqual(result, mask_data) - self.assertEqual(result.fill_value, fill_value) - - def test_lazy_scalar_proxy(self): - a = np.array(5) - proxy = MyProxy(a) - lazy_array = as_lazy_data(proxy) - self.assertTrue(is_lazy_data(lazy_array)) - result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertEqual(result, a) - - def test_lazy_scalar_proxy_masked(self): - a = np.ma.masked_array(5, True) - proxy = MyProxy(a) - lazy_array = as_lazy_data(proxy) - self.assertTrue(is_lazy_data(lazy_array)) - result = as_concrete_data(lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertMaskedArrayEqual(result, a) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py b/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py deleted file mode 100644 index 5f9dece153..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_as_lazy_data.py +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test the function :func:`iris._lazy data.as_lazy_data`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import dask.array as da -import dask.config -import numpy as np -import numpy.ma as ma - -from iris._lazy_data import _optimum_chunksize, as_lazy_data - - -class Test_as_lazy_data(tests.IrisTest): - def test_lazy(self): - data = da.from_array(np.arange(24).reshape((2, 3, 4)), chunks="auto") - result = as_lazy_data(data) - self.assertIsInstance(result, da.core.Array) - - def test_real(self): - data = np.arange(24).reshape((2, 3, 4)) - result = as_lazy_data(data) - self.assertIsInstance(result, da.core.Array) - - def test_masked(self): - data = np.ma.masked_greater(np.arange(24), 10) - result = as_lazy_data(data) - self.assertIsInstance(result, da.core.Array) - - def test_non_default_chunks(self): - data = np.arange(24) - chunks = (12,) - lazy_data = as_lazy_data(data, chunks=chunks) - (result,) = np.unique(lazy_data.chunks) - self.assertEqual(result, 24) - - def test_with_masked_constant(self): - masked_data = ma.masked_array([8], mask=True) - masked_constant = masked_data[0] - result = as_lazy_data(masked_constant) - self.assertIsInstance(result, da.core.Array) - - -class Test__optimised_chunks(tests.IrisTest): - # Stable, known chunksize for testing. - FIXED_CHUNKSIZE_LIMIT = 1024 * 1024 * 64 - - @staticmethod - def _dummydata(shape): - return mock.Mock(spec=da.core.Array, dtype=np.dtype("f4"), shape=shape) - - def test_chunk_size_limiting(self): - # Check default chunksizes for large data (with a known size limit). - given_shapes_and_resulting_chunks = [ - ((16, 1024, 1024), (16, 1024, 1024)), # largest unmodified - ((17, 1011, 1022), (8, 1011, 1022)), - ((16, 1024, 1025), (8, 1024, 1025)), - ((1, 17, 1011, 1022), (1, 8, 1011, 1022)), - ((17, 1, 1011, 1022), (8, 1, 1011, 1022)), - ((11, 2, 1011, 1022), (5, 2, 1011, 1022)), - ] - err_fmt = "Result of optimising chunks {} was {}, expected {}" - for shape, expected in given_shapes_and_resulting_chunks: - chunks = _optimum_chunksize( - shape, shape, limit=self.FIXED_CHUNKSIZE_LIMIT - ) - msg = err_fmt.format(shape, chunks, expected) - self.assertEqual(chunks, expected, msg) - - def test_chunk_size_expanding(self): - # Check the expansion of small chunks, (with a known size limit). - given_shapes_and_resulting_chunks = [ - ((1, 100, 100), (16, 100, 100), (16, 100, 100)), - ((1, 100, 100), (5000, 100, 100), (1667, 100, 100)), - ((3, 300, 200), (10000, 3000, 2000), (3, 1500, 2000)), - ((3, 300, 200), (10000, 300, 2000), (27, 300, 2000)), - ((3, 300, 200), (8, 300, 2000), (8, 300, 2000)), - ((3, 300, 200), (117, 300, 1000), (39, 300, 1000)), - ] - err_fmt = "Result of optimising shape={};chunks={} was {}, expected {}" - for shape, fullshape, expected in given_shapes_and_resulting_chunks: - chunks = _optimum_chunksize( - chunks=shape, shape=fullshape, limit=self.FIXED_CHUNKSIZE_LIMIT - ) - msg = err_fmt.format(fullshape, shape, chunks, expected) - self.assertEqual(chunks, expected, msg) - - def test_chunk_expanding_equal_division(self): - # Check that expansion chooses equal chunk sizes as far as possible. - - # Table of test cases: - # (input-chunkshape, full-shape, size-limit, result-chunkshape) - testcases_chunksin_fullshape_limit_result = [ - ((4,), (12,), 15, (12,)), # gives a single chunk, of size 12 - ((4,), (13,), 15, (8,)), # chooses chunks of 8+5, better than 12+1 - ((4,), (16,), 15, (8,)), # 8+8 is better than 12+4; 16 is too big. - ((4,), (96,), 15, (12,)), # 12 is largest 'allowed' - ((4,), (96,), 31, (24,)), # 28 doesn't divide 96 so neatly, - # A multi-dimensional case, where trailing dims are 'filled'. - ((4, 5, 100), (25, 10, 200), 16 * 2000, (16, 10, 200)), - # Equivalent case with additional initial dimensions. - ( - (1, 1, 4, 5, 100), - (3, 5, 25, 10, 200), - 16 * 2000, - (1, 1, 16, 10, 200), - ), # effectively the same as the previous. - ] - err_fmt_main = ( - "Main chunks result of optimising " - "chunks={},shape={},limit={} " - "was {}, expected {}" - ) - for ( - chunks, - shape, - limit, - expected_result, - ) in testcases_chunksin_fullshape_limit_result: - result = _optimum_chunksize( - chunks=chunks, shape=shape, limit=limit, dtype=np.dtype("b1") - ) - msg = err_fmt_main.format( - chunks, shape, limit, result, expected_result - ) - self.assertEqual(result, expected_result, msg) - - def test_default_chunksize(self): - # Check that the "ideal" chunksize is taken from the dask config. - with dask.config.set({"array.chunk-size": "20b"}): - chunks = _optimum_chunksize( - (1, 8), shape=(400, 20), dtype=np.dtype("f4") - ) - self.assertEqual(chunks, (1, 4)) - - def test_default_chunks_limiting(self): - # Check that chunking is still controlled when no specific 'chunks' - # is passed. - limitcall_patch = self.patch("iris._lazy_data._optimum_chunksize") - test_shape = (3, 2, 4) - data = self._dummydata(test_shape) - as_lazy_data(data) - self.assertEqual( - limitcall_patch.call_args_list, - [ - mock.call( - list(test_shape), shape=test_shape, dtype=np.dtype("f4") - ) - ], - ) - - def test_shapeless_data(self): - # Check that chunk optimisation is skipped if shape contains a zero. - limitcall_patch = self.patch("iris._lazy_data._optimum_chunksize") - test_shape = (2, 1, 0, 2) - data = self._dummydata(test_shape) - as_lazy_data(data, chunks=test_shape) - self.assertFalse(limitcall_patch.called) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py b/lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py deleted file mode 100644 index 0c10d69c16..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_co_realise_cubes.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._lazy data.co_realise_cubes`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris._lazy_data import as_lazy_data, co_realise_cubes -from iris.cube import Cube - - -class ArrayAccessCounter: - def __init__(self, array): - self.dtype = array.dtype - self.shape = array.shape - self.ndim = array.ndim - self._array = array - self.access_count = 0 - - def __getitem__(self, keys): - self.access_count += 1 - return self._array[keys] - - -class Test_co_realise_cubes(tests.IrisTest): - def test_empty(self): - # Ensure that 'no args' case does not raise an error. - co_realise_cubes() - - def test_basic(self): - real_data = np.arange(3.0) - cube = Cube(as_lazy_data(real_data)) - co_realise_cubes(cube) - self.assertFalse(cube.has_lazy_data()) - self.assertArrayAllClose(cube.core_data(), real_data) - - def test_multi(self): - real_data = np.arange(3.0) - cube_base = Cube(as_lazy_data(real_data)) - cube_inner = cube_base + 1 - result_a = cube_base + 1 - result_b = cube_inner + 1 - co_realise_cubes(result_a, result_b) - # Check that target cubes were realised. - self.assertFalse(result_a.has_lazy_data()) - self.assertFalse(result_b.has_lazy_data()) - # Check that other cubes referenced remain lazy. - self.assertTrue(cube_base.has_lazy_data()) - self.assertTrue(cube_inner.has_lazy_data()) - - def test_combined_access(self): - wrapped_array = ArrayAccessCounter(np.arange(3.0)) - lazy_array = as_lazy_data(wrapped_array) - derived_a = lazy_array + 1 - derived_b = lazy_array + 2 - derived_c = lazy_array + 3 - derived_d = lazy_array + 4 - derived_e = lazy_array + 5 - cube_a = Cube(derived_a) - cube_b = Cube(derived_b) - cube_c = Cube(derived_c) - cube_d = Cube(derived_d) - cube_e = Cube(derived_e) - co_realise_cubes(cube_a, cube_b, cube_c, cube_d, cube_e) - # Though used more than once, the source data should only get fetched - # once by dask, when the whole data is accessed. - # This also ensures that dask does *not* perform an initial data - # access with no data payload to ascertain the metadata associated with - # the dask.array (this access is specific to dask 2+, - # see dask.array.utils.meta_from_array). - self.assertEqual(wrapped_array.access_count, 1) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_is_lazy_data.py b/lib/iris/tests/unit/lazy_data/test_is_lazy_data.py deleted file mode 100644 index 45b3194f32..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_is_lazy_data.py +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._lazy data.is_lazy_data`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import dask.array as da -import numpy as np - -from iris._lazy_data import is_lazy_data - - -class Test_is_lazy_data(tests.IrisTest): - def test_lazy(self): - values = np.arange(30).reshape((2, 5, 3)) - lazy_array = da.from_array(values, chunks="auto") - self.assertTrue(is_lazy_data(lazy_array)) - - def test_real(self): - real_array = np.arange(24).reshape((2, 3, 4)) - self.assertFalse(is_lazy_data(real_array)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py b/lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py deleted file mode 100644 index 49fd6ad70b..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_lazy_elementwise.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._lazy data.lazy_elementwise`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris._lazy_data import as_lazy_data, is_lazy_data, lazy_elementwise - - -def _test_elementwise_op(array): - # Promotes the type of a bool argument, but not a float. - return array + 1 - - -class Test_lazy_elementwise(tests.IrisTest): - def test_basic(self): - concrete_array = np.arange(30).reshape((2, 5, 3)) - lazy_array = as_lazy_data(concrete_array) - wrapped = lazy_elementwise(lazy_array, _test_elementwise_op) - self.assertTrue(is_lazy_data(wrapped)) - self.assertArrayAllClose( - wrapped.compute(), _test_elementwise_op(concrete_array) - ) - - def test_dtype_same(self): - concrete_array = np.array([3.0], dtype=np.float16) - lazy_array = as_lazy_data(concrete_array) - wrapped = lazy_elementwise(lazy_array, _test_elementwise_op) - self.assertTrue(is_lazy_data(wrapped)) - self.assertEqual(wrapped.dtype, np.float16) - self.assertEqual(wrapped.compute().dtype, np.float16) - - def test_dtype_change(self): - concrete_array = np.array([True, False]) - lazy_array = as_lazy_data(concrete_array) - wrapped = lazy_elementwise(lazy_array, _test_elementwise_op) - self.assertTrue(is_lazy_data(wrapped)) - self.assertEqual(wrapped.dtype, np.int_) - self.assertEqual(wrapped.compute().dtype, wrapped.dtype) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py b/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py deleted file mode 100644 index 66c03d04c8..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_map_complete_blocks.py +++ /dev/null @@ -1,104 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._lazy data.map_complete_blocks`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest - -import dask.array as da -import numpy as np - -from iris._lazy_data import is_lazy_data, map_complete_blocks - - -def create_mock_cube(array): - cube = unittest.mock.Mock() - cube_data = unittest.mock.PropertyMock(return_value=array) - type(cube).data = cube_data - cube.dtype = array.dtype - cube.has_lazy_data = unittest.mock.Mock(return_value=is_lazy_data(array)) - cube.lazy_data = unittest.mock.Mock(return_value=array) - cube.shape = array.shape - # Remove compute so cube is not interpreted as dask array. - del cube.compute - return cube, cube_data - - -class Test_map_complete_blocks(tests.IrisTest): - def setUp(self): - self.array = np.arange(8).reshape(2, 4) - self.func = lambda chunk: chunk + 1 - self.func_result = self.array + 1 - - def test_non_lazy_input(self): - # Check that a non-lazy input doesn't trip up the functionality. - cube, cube_data = create_mock_cube(self.array) - result = map_complete_blocks( - cube, self.func, dims=(1,), out_sizes=(4,) - ) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, self.func_result) - # check correct data was accessed - cube.lazy_data.assert_not_called() - cube_data.assert_called_once() - - def test_lazy_input(self): - lazy_array = da.asarray(self.array, chunks=((1, 1), (4,))) - cube, cube_data = create_mock_cube(lazy_array) - result = map_complete_blocks( - cube, self.func, dims=(1,), out_sizes=(4,) - ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), self.func_result) - # check correct data was accessed - cube.lazy_data.assert_called_once() - cube_data.assert_not_called() - - def test_dask_array_input(self): - lazy_array = da.asarray(self.array, chunks=((1, 1), (4,))) - result = map_complete_blocks( - lazy_array, self.func, dims=(1,), out_sizes=(4,) - ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), self.func_result) - - def test_rechunk(self): - lazy_array = da.asarray(self.array, chunks=((1, 1), (2, 2))) - cube, _ = create_mock_cube(lazy_array) - result = map_complete_blocks( - cube, self.func, dims=(1,), out_sizes=(4,) - ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), self.func_result) - - def test_different_out_shape(self): - lazy_array = da.asarray(self.array, chunks=((1, 1), (4,))) - cube, _ = create_mock_cube(lazy_array) - - def func(_): - return np.arange(2).reshape(1, 2) - - func_result = [[0, 1], [0, 1]] - result = map_complete_blocks(cube, func, dims=(1,), out_sizes=(2,)) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), func_result) - - def test_multidimensional_input(self): - array = np.arange(2 * 3 * 4).reshape(2, 3, 4) - lazy_array = da.asarray(array, chunks=((1, 1), (1, 2), (4,))) - cube, _ = create_mock_cube(lazy_array) - result = map_complete_blocks( - cube, self.func, dims=(1, 2), out_sizes=(3, 4) - ) - self.assertTrue(is_lazy_data(result)) - self.assertArrayEqual(result.compute(), array + 1) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py b/lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py deleted file mode 100644 index 9fe79a0d4c..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_multidim_lazy_stack.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._lazy data.multidim_lazy_stack`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import dask.array as da -import numpy as np - -from iris._lazy_data import as_concrete_data, as_lazy_data, multidim_lazy_stack - - -class Test_multidim_lazy_stack(tests.IrisTest): - def _check(self, stack_shape): - vals = np.arange(np.prod(stack_shape)).reshape(stack_shape) - stack = np.empty(stack_shape, "object") - # Define the shape of each element in the stack. - stack_element_shape = (4, 5) - expected = np.empty(stack_shape + stack_element_shape, dtype=int) - for index, val in np.ndenumerate(vals): - stack[index] = as_lazy_data(val * np.ones(stack_element_shape)) - - expected[index] = val - result = multidim_lazy_stack(stack) - self.assertEqual(result.shape, stack_shape + stack_element_shape) - self.assertIsInstance(result, da.core.Array) - result = as_concrete_data(result) - self.assertArrayAllClose(result, expected) - - def test_0d_lazy_stack(self): - shape = () - self._check(shape) - - def test_1d_lazy_stack(self): - shape = (2,) - self._check(shape) - - def test_2d_lazy_stack(self): - shape = (3, 2) - self._check(shape) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/lazy_data/test_non_lazy.py b/lib/iris/tests/unit/lazy_data/test_non_lazy.py deleted file mode 100644 index cc4ed33ea3..0000000000 --- a/lib/iris/tests/unit/lazy_data/test_non_lazy.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris._lazy data.non_lazy`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris._lazy_data import as_lazy_data, is_lazy_data, non_lazy - - -class Test_non_lazy(tests.IrisTest): - def setUp(self): - self.array = np.arange(8).reshape(2, 4) - self.lazy_array = as_lazy_data(self.array) - self.func = non_lazy(lambda array: array.sum(axis=0)) - self.func_result = [4, 6, 8, 10] - - def test_lazy_input(self): - result = self.func(self.lazy_array) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, self.func_result) - - def test_non_lazy_input(self): - # Check that a non-lazy input doesn't trip up the functionality. - result = self.func(self.array) - self.assertFalse(is_lazy_data(result)) - self.assertArrayEqual(result, self.func_result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/merge/__init__.py b/lib/iris/tests/unit/merge/__init__.py deleted file mode 100644 index c3ead61576..0000000000 --- a/lib/iris/tests/unit/merge/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._merge` module.""" diff --git a/lib/iris/tests/unit/merge/test_ProtoCube.py b/lib/iris/tests/unit/merge/test_ProtoCube.py deleted file mode 100644 index 0fca726b28..0000000000 --- a/lib/iris/tests/unit/merge/test_ProtoCube.py +++ /dev/null @@ -1,538 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris._merge.ProtoCube` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from abc import ABCMeta, abstractmethod -from unittest import mock - -import numpy as np -import numpy.ma as ma - -import iris -from iris._merge import ProtoCube -from iris.aux_factory import HybridHeightFactory, HybridPressureFactory -from iris.coords import AuxCoord, DimCoord -from iris.exceptions import MergeError - - -def example_cube(): - return iris.cube.Cube( - np.array([1, 2, 3], dtype="i4"), - standard_name="air_temperature", - long_name="screen_air_temp", - var_name="airtemp", - units="K", - attributes={"mint": "thin"}, - ) - - -class Mixin_register(metaclass=ABCMeta): - @property - def cube1(self): - return example_cube() - - @property - @abstractmethod - def cube2(self): - pass - - @property - @abstractmethod - def fragments(self): - pass - - def test_default(self): - # Test what happens when we call: - # ProtoCube.register(cube) - proto_cube = ProtoCube(self.cube1) - result = proto_cube.register(self.cube2) - self.assertEqual(result, not self.fragments) - - def test_no_error(self): - # Test what happens when we call: - # ProtoCube.register(cube, error_on_mismatch=False) - proto_cube = ProtoCube(self.cube1) - result = proto_cube.register(self.cube2, error_on_mismatch=False) - self.assertEqual(result, not self.fragments) - - def test_error(self): - # Test what happens when we call: - # ProtoCube.register(cube, error_on_mismatch=True) - proto_cube = ProtoCube(self.cube1) - if self.fragments: - with self.assertRaises(iris.exceptions.MergeError) as cm: - proto_cube.register(self.cube2, error_on_mismatch=True) - error_message = str(cm.exception) - for substr in self.fragments: - self.assertIn(substr, error_message) - else: - result = proto_cube.register(self.cube2, error_on_mismatch=True) - self.assertTrue(result) - - -class Test_register__match(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return [] - - @property - def cube2(self): - return example_cube() - - -class Test_register__standard_name(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.standard_name", "air_temperature", "air_density"] - - @property - def cube2(self): - cube = example_cube() - cube.standard_name = "air_density" - return cube - - -class Test_register__long_name(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.long_name", "screen_air_temp", "Belling"] - - @property - def cube2(self): - cube = example_cube() - cube.long_name = "Belling" - return cube - - -class Test_register__var_name(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.var_name", "'airtemp'", "'airtemp2'"] - - @property - def cube2(self): - cube = example_cube() - cube.var_name = "airtemp2" - return cube - - -class Test_register__units(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.units", "'K'", "'C'"] - - @property - def cube2(self): - cube = example_cube() - cube.units = "C" - return cube - - -class Test_register__attributes_unequal(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.attributes", "'mint'"] - - @property - def cube2(self): - cube = example_cube() - cube.attributes["mint"] = "waffer-thin" - return cube - - -class Test_register__attributes_unequal_array(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.attributes", "'mint'"] - - @property - def cube1(self): - cube = example_cube() - cube.attributes["mint"] = np.arange(3) - return cube - - @property - def cube2(self): - cube = example_cube() - cube.attributes["mint"] = np.arange(3) + 1 - return cube - - -class Test_register__attributes_superset(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.attributes", "'stuffed'"] - - @property - def cube2(self): - cube = example_cube() - cube.attributes["stuffed"] = "yes" - return cube - - -class Test_register__attributes_multi_diff(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.attributes", "'sam'", "'mint'"] - - @property - def cube1(self): - cube = example_cube() - cube.attributes["ralph"] = 1 - cube.attributes["sam"] = 2 - cube.attributes["tom"] = 3 - return cube - - @property - def cube2(self): - cube = example_cube() - cube.attributes["ralph"] = 1 - cube.attributes["sam"] = "mug" - cube.attributes["tom"] = 3 - cube.attributes["mint"] = "humbug" - return cube - - -class Test_register__cell_method(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.cell_methods"] - - @property - def cube2(self): - cube = example_cube() - cube.add_cell_method(iris.coords.CellMethod("monty", ("python",))) - return cube - - -class Test_register__data_shape(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube.shape", "(2,)", "(3,)"] - - @property - def cube2(self): - cube = example_cube() - cube = cube[1:] - return cube - - -class Test_register__data_dtype(Mixin_register, tests.IrisTest): - @property - def fragments(self): - return ["cube data dtype", "int32", "int8"] - - @property - def cube2(self): - cube = example_cube() - cube.data = cube.data.astype(np.int8) - return cube - - -class _MergeTest: - # A mixin test class for common test methods implementation. - - # used by check routine: inheritors must implement it - _mergetest_type = NotImplementedError - - def check_merge_fails_with_message(self): - proto_cube = iris._merge.ProtoCube(self.cube1) - with self.assertRaises(MergeError) as arc: - proto_cube.register(self.cube2, error_on_mismatch=True) - return str(arc.exception) - - def check_fail(self, *substrs): - if isinstance(substrs, str): - substrs = [substrs] - msg = self.check_merge_fails_with_message() - for substr in substrs: - self.assertIn(substr, msg) - - -class Test_register__CubeSig(_MergeTest, tests.IrisTest): - # Test potential registration failures. - - _mergetest_type = "cube" - - def setUp(self): - self.cube1 = iris.cube.Cube( - [1, 2, 3], - standard_name="air_temperature", - units="K", - attributes={"mint": "thin"}, - ) - self.cube2 = self.cube1.copy() - - def test_noise(self): - # Test a massive set of all defn diffs to make sure it's not noise. - self.cube1.var_name = "Arthur" - cube2 = self.cube1[1:] - cube2.data = cube2.data.astype(np.int8) - cube2.data = ma.array(cube2.data) - cube2.standard_name = "air_pressure" - cube2.var_name = "Nudge" - cube2.attributes["stuffed"] = "yes" - cube2.attributes["mint"] = "waffer-thin" - cube2.add_cell_method(iris.coords.CellMethod("monty", ("python",))) - - # Check the actual message, so we've got a readable reference text. - self.cube2 = cube2 - msg = self.check_merge_fails_with_message() - self.assertString(msg, self.result_path(ext="txt")) - - -class Test_register__CoordSig_general(_MergeTest, tests.IrisTest): - _mergetest_type = "coord" - - def setUp(self): - self.cube1 = iris.cube.Cube(np.zeros((3, 3, 3))) - self.cube2 = self.cube1.copy() - - def test_scalar_defns_one_extra(self): - self.cube2.add_aux_coord(DimCoord([1], standard_name="latitude")) - self.check_fail("aux_coords (scalar)", "latitude") - - def test_scalar_defns_both_extra(self): - self.cube2.add_aux_coord(DimCoord([1], standard_name="latitude")) - self.cube1.add_aux_coord(DimCoord([1], standard_name="longitude")) - self.check_fail("aux_coords (scalar)", "latitude", "longitude") - - def test_vector_dim_coords_and_dims_one_extra(self): - self.cube2.add_dim_coord( - DimCoord([1, 2, 3], standard_name="latitude"), 0 - ) - self.check_fail("dim_coords", "latitude") - - def test_vector_dim_coords_and_dims_both_extra(self): - self.cube2.add_dim_coord( - DimCoord([1, 2, 3], standard_name="latitude"), 0 - ) - self.cube1.add_dim_coord( - DimCoord([1, 2, 3], standard_name="longitude"), 0 - ) - self.check_fail("dim_coords", "latitude", "longitude") - - def test_vector_aux_coords_and_dims_one_extra(self): - self.cube2.add_aux_coord( - DimCoord([1, 2, 3], standard_name="latitude"), 0 - ) - self.check_fail("aux_coords (non-scalar)", "latitude") - - def test_vector_aux_coords_and_dims_both_extra(self): - self.cube2.add_aux_coord( - DimCoord([1, 2, 3], standard_name="latitude"), 0 - ) - self.cube1.add_aux_coord( - DimCoord([1, 2, 3], standard_name="longitude"), 0 - ) - self.check_fail("aux_coords (non-scalar)", "latitude", "longitude") - - def test_factory_defns_one_extra(self): - self.cube2.add_aux_factory(mock.MagicMock(spec=HybridHeightFactory)) - self.check_fail("cube.aux_factories", "differ") - - def test_factory_defns_both_extra(self): - self.cube2.add_aux_factory(mock.MagicMock(spec=HybridHeightFactory)) - self.cube1.add_aux_factory(mock.MagicMock(spec=HybridPressureFactory)) - self.check_fail("cube.aux_factories", "differ") - - def test_factory_defns_one_missing_term(self): - self.cube1.add_aux_factory(mock.MagicMock(spec=HybridPressureFactory)) - no_delta_factory = mock.MagicMock(spec=HybridPressureFactory) - no_delta_factory.delta = None - self.cube2.add_aux_factory(no_delta_factory) - - self.check_fail("cube.aux_factories", "differ") - - def test_noise(self): - cube2 = self.cube2 - - # scalar - cube2.add_aux_coord(DimCoord([1], long_name="liff")) - cube2.add_aux_coord(DimCoord([1], long_name="life")) - cube2.add_aux_coord(DimCoord([1], long_name="like")) - - self.cube1.add_aux_coord(DimCoord([1], var_name="ming")) - self.cube1.add_aux_coord(DimCoord([1], var_name="mong")) - self.cube1.add_aux_coord(DimCoord([1], var_name="moog")) - - # aux - cube2.add_dim_coord(DimCoord([1, 2, 3], standard_name="latitude"), 0) - cube2.add_dim_coord(DimCoord([1, 2, 3], standard_name="longitude"), 1) - cube2.add_dim_coord(DimCoord([1, 2, 3], standard_name="altitude"), 2) - - self.cube1.add_dim_coord( - DimCoord([1, 2, 3], long_name="equinimity"), 0 - ) - self.cube1.add_dim_coord( - DimCoord([1, 2, 3], long_name="equinomity"), 1 - ) - self.cube1.add_dim_coord( - DimCoord([1, 2, 3], long_name="equinumity"), 2 - ) - - # dim - cube2.add_aux_coord(DimCoord([1, 2, 3], var_name="one"), 0) - cube2.add_aux_coord(DimCoord([1, 2, 3], var_name="two"), 1) - cube2.add_aux_coord(DimCoord([1, 2, 3], var_name="three"), 2) - - self.cube1.add_aux_coord(DimCoord([1, 2, 3], long_name="ay"), 0) - self.cube1.add_aux_coord(DimCoord([1, 2, 3], long_name="bee"), 1) - self.cube1.add_aux_coord(DimCoord([1, 2, 3], long_name="cee"), 2) - - # factory - cube2.add_aux_factory(mock.MagicMock(spec=HybridHeightFactory)) - self.cube1.add_aux_factory(mock.MagicMock(spec=HybridPressureFactory)) - - # Check the actual message, so we've got a readable reference text. - self.cube2 = cube2 - msg = self.check_merge_fails_with_message() - self.assertString(msg, self.result_path(ext="txt")) - - -class _MergeTest_coordprops(_MergeTest): - # A mixin test class for common coordinate properties tests. - - # This must be implemented by inheritors. - _mergetest_type = NotImplementedError - - def test_nochange(self): - # This should simply succeed. - proto_cube = iris._merge.ProtoCube(self.cube1) - proto_cube.register(self.cube2, error_on_mismatch=True) - - def _props_fail(self, *terms): - self.check_fail( - self._mergetest_type, self.coord_to_change.name(), *terms - ) - - def test_standard_name(self): - self.coord_to_change.standard_name = "soil_temperature" - self._props_fail("air_temperature", "soil_temperature") - - def test_long_name(self): - self.coord_to_change.long_name = "alternate_name" - self._props_fail("air_temperature") - - def test_var_name(self): - self.coord_to_change.var_name = "alternate_name" - self._props_fail("air_temperature") - - def test_units(self): - self.coord_to_change.units = "m" - self._props_fail("air_temperature") - - def test_attrs_unequal(self): - self.coord_to_change.attributes["att_a"] = 99 - self._props_fail("air_temperature") - - def test_attrs_set(self): - self.coord_to_change.attributes["att_extra"] = 101 - self._props_fail("air_temperature") - - def test_coord_system(self): - self.coord_to_change.coord_system = mock.Mock() - self._props_fail("air_temperature") - - -class Test_register__CoordSig_scalar(_MergeTest_coordprops, tests.IrisTest): - _mergetest_type = "aux_coords (scalar)" - - def setUp(self): - self.cube1 = iris.cube.Cube(np.zeros((3, 3, 3))) - self.cube1.add_aux_coord( - DimCoord( - [1], - standard_name="air_temperature", - long_name="eg_scalar", - var_name="t1", - units="K", - attributes={"att_a": 1, "att_b": 2}, - coord_system=None, - ) - ) - self.coord_to_change = self.cube1.coord("air_temperature") - self.cube2 = self.cube1.copy() - - -class _MergeTest_coordprops_vect(_MergeTest_coordprops): - # A derived mixin test class. - # Adds extra props test for aux+dim coords (test points, bounds + dims) - _mergetest_type = NotImplementedError - _coord_typename = NotImplementedError - - def test_points(self): - self.coord_to_change.points = self.coord_to_change.points + 1.0 - self.check_fail(self._mergetest_type, "air_temperature") - - def test_bounds(self): - self.coord_to_change.bounds = self.coord_to_change.bounds + 1.0 - self.check_fail(self._mergetest_type, "air_temperature") - - def test_dims(self): - self.cube2.remove_coord(self.coord_to_change) - cube2_add_method = getattr(self.cube2, "add_" + self._coord_typename) - cube2_add_method(self.coord_to_change, (1,)) - self.check_fail(self._mergetest_type, "mapping") - - -class Test_register__CoordSig_dim(_MergeTest_coordprops_vect, tests.IrisTest): - _mergetest_type = "dim_coords" - _coord_typename = "dim_coord" - - def setUp(self): - self.cube1 = iris.cube.Cube(np.zeros((3, 3))) - self.cube1.add_dim_coord( - DimCoord( - [15, 25, 35], - bounds=[[10, 20], [20, 30], [30, 40]], - standard_name="air_temperature", - long_name="eg_scalar", - var_name="t1", - units="K", - attributes={"att_a": 1, "att_b": 2}, - coord_system=None, - ), - (0,), - ) - self.coord_to_change = self.cube1.coord("air_temperature") - self.cube2 = self.cube1.copy() - - def test_circular(self): - # Extra failure mode that only applies to dim coords - self.coord_to_change.circular = True - self.check_fail(self._mergetest_type, "air_temperature") - - -class Test_register__CoordSig_aux(_MergeTest_coordprops_vect, tests.IrisTest): - _mergetest_type = "aux_coords (non-scalar)" - _coord_typename = "aux_coord" - - def setUp(self): - self.cube1 = iris.cube.Cube(np.zeros((3, 3))) - self.cube1.add_aux_coord( - AuxCoord( - [65, 45, 85], - bounds=[[60, 70], [40, 50], [80, 90]], - standard_name="air_temperature", - long_name="eg_scalar", - var_name="t1", - units="K", - attributes={"att_a": 1, "att_b": 2}, - coord_system=None, - ), - (0,), - ) - self.coord_to_change = self.cube1.coord("air_temperature") - self.cube2 = self.cube1.copy() - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/pandas/__init__.py b/lib/iris/tests/unit/pandas/__init__.py deleted file mode 100644 index 103a264839..0000000000 --- a/lib/iris/tests/unit/pandas/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.pandas` module.""" diff --git a/lib/iris/tests/unit/pandas/test_pandas.py b/lib/iris/tests/unit/pandas/test_pandas.py deleted file mode 100644 index fd716bd7c9..0000000000 --- a/lib/iris/tests/unit/pandas/test_pandas.py +++ /dev/null @@ -1,1357 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""All unit tests for the :mod:`iris.pandas` module.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import copy -import datetime -from termios import IXOFF # noqa: F401 -import warnings - -import cf_units -import cftime -import matplotlib.units -import numpy as np -import pytest - -import iris -from iris._deprecation import IrisDeprecation - -# Importing pandas has the side-effect of messing with the formatters -# used by matplotlib for handling dates. -default_units_registry = copy.copy(matplotlib.units.registry) -try: - import pandas -except ImportError: - # Disable all these tests if pandas is not installed. - pandas = None -matplotlib.units.registry = default_units_registry - -skip_pandas = pytest.mark.skipif( - pandas is None, - reason='Test(s) require "pandas", ' "which is not available.", -) - -if pandas is not None: - from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord - from iris.cube import Cube, CubeList - import iris.pandas - - -@pytest.fixture -def activate_pandas_ndim(): - iris.FUTURE.pandas_ndim = True - yield None - iris.FUTURE.pandas_ndim = False - - -@skip_pandas -@pytest.mark.filterwarnings( - "ignore:.*as_series has been deprecated.*:iris._deprecation.IrisDeprecation" -) -class TestAsSeries(tests.IrisTest): - """Test conversion of 1D cubes to Pandas using as_series()""" - - def test_no_dim_coord(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") - series = iris.pandas.as_series(cube) - expected_index = np.array([0, 1, 2, 3, 4]) - self.assertArrayEqual(series, cube.data) - self.assertArrayEqual(series.index, expected_index) - - def test_simple(self): - cube = Cube(np.array([0, 1, 2, 3, 4.4]), long_name="foo") - dim_coord = DimCoord([5, 6, 7, 8, 9], long_name="bar") - cube.add_dim_coord(dim_coord, 0) - expected_index = np.array([5, 6, 7, 8, 9]) - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data) - self.assertArrayEqual(series.index, expected_index) - - def test_masked(self): - data = np.ma.MaskedArray([0, 1, 2, 3, 4.4], mask=[0, 1, 0, 1, 0]) - cube = Cube(data, long_name="foo") - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data.astype("f").filled(np.nan)) - - def test_time_standard(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") - time_coord = DimCoord( - [0, 100.1, 200.2, 300.3, 400.4], - long_name="time", - units="days since 2000-01-01 00:00", - ) - cube.add_dim_coord(time_coord, 0) - expected_index = [ - datetime.datetime(2000, 1, 1, 0, 0), - datetime.datetime(2000, 4, 10, 2, 24), - datetime.datetime(2000, 7, 19, 4, 48), - datetime.datetime(2000, 10, 27, 7, 12), - datetime.datetime(2001, 2, 4, 9, 36), - ] - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data) - assert list(series.index) == expected_index - - def test_time_360(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="ts") - time_unit = cf_units.Unit( - "days since 2000-01-01 00:00", calendar=cf_units.CALENDAR_360_DAY - ) - time_coord = DimCoord( - [0, 100.1, 200.2, 300.3, 400.4], long_name="time", units=time_unit - ) - cube.add_dim_coord(time_coord, 0) - expected_index = [ - cftime.Datetime360Day(2000, 1, 1, 0, 0), - cftime.Datetime360Day(2000, 4, 11, 2, 24), - cftime.Datetime360Day(2000, 7, 21, 4, 48), - cftime.Datetime360Day(2000, 11, 1, 7, 12), - cftime.Datetime360Day(2001, 2, 11, 9, 36), - ] - - series = iris.pandas.as_series(cube) - self.assertArrayEqual(series, cube.data) - self.assertArrayEqual(series.index, expected_index) - - def test_copy_true(self): - cube = Cube(np.array([0, 1, 2, 3, 4]), long_name="foo") - series = iris.pandas.as_series(cube) - series[0] = 99 - assert cube.data[0] == 0 - - def test_copy_int32_false(self): - cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int32), long_name="foo") - series = iris.pandas.as_series(cube, copy=False) - series[0] = 99 - assert cube.data[0] == 99 - - def test_copy_int64_false(self): - cube = Cube(np.array([0, 1, 2, 3, 4], dtype=np.int64), long_name="foo") - series = iris.pandas.as_series(cube, copy=False) - series[0] = 99 - assert cube.data[0] == 99 - - def test_copy_float_false(self): - cube = Cube(np.array([0, 1, 2, 3.3, 4]), long_name="foo") - series = iris.pandas.as_series(cube, copy=False) - series[0] = 99 - assert cube.data[0] == 99 - - def test_copy_masked_true(self): - data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) - cube = Cube(data, long_name="foo") - series = iris.pandas.as_series(cube) - series[0] = 99 - assert cube.data[0] == 0 - - def test_copy_masked_false(self): - data = np.ma.MaskedArray([0, 1, 2, 3, 4], mask=[0, 1, 0, 1, 0]) - cube = Cube(data, long_name="foo") - with pytest.raises(ValueError): - _ = iris.pandas.as_series(cube, copy=False) - - -@skip_pandas -@pytest.mark.filterwarnings( - "ignore:You are using legacy 2-dimensional behaviour.*:FutureWarning" -) -class TestAsDataFrame(tests.IrisTest): - """Test conversion of 2D cubes to Pandas using as_data_frame()""" - - def test_no_dim_coords(self): - cube = Cube( - np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" - ) - expected_index = [0, 1] - expected_columns = [0, 1, 2, 3, 4] - data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) - - def test_no_x_coord(self): - cube = Cube( - np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" - ) - y_coord = DimCoord([10, 11], long_name="bar") - cube.add_dim_coord(y_coord, 0) - expected_index = [10, 11] - expected_columns = [0, 1, 2, 3, 4] - data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) - - def test_no_y_coord(self): - cube = Cube( - np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" - ) - x_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar") - cube.add_dim_coord(x_coord, 1) - expected_index = [0, 1] - expected_columns = [10, 11, 12, 13, 14] - data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) - - def test_simple(self): - cube = Cube( - np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="foo" - ) - x_coord = DimCoord([10, 11, 12, 13, 14], long_name="bar") - y_coord = DimCoord([15, 16], long_name="milk") - cube.add_dim_coord(x_coord, 1) - cube.add_dim_coord(y_coord, 0) - expected_index = [15, 16] - expected_columns = [10, 11, 12, 13, 14] - data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) - - def test_masked(self): - data = np.ma.MaskedArray( - [[0, 1, 2, 3, 4.4], [5, 6, 7, 8, 9]], - mask=[[0, 1, 0, 1, 0], [1, 0, 1, 0, 1]], - ) - cube = Cube(data, long_name="foo") - expected_index = [0, 1] - expected_columns = [0, 1, 2, 3, 4] - data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data.astype("f").filled(np.nan)) - self.assertArrayEqual(data_frame.index, expected_index) - self.assertArrayEqual(data_frame.columns, expected_columns) - - def test_time_standard(self): - cube = Cube( - np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]), long_name="ts" - ) - day_offsets = [0, 100.1, 200.2, 300.3, 400.4] - time_coord = DimCoord( - day_offsets, long_name="time", units="days since 2000-01-01 00:00" - ) - cube.add_dim_coord(time_coord, 1) - data_frame = iris.pandas.as_data_frame(cube) - self.assertArrayEqual(data_frame, cube.data) - nanoseconds_per_day = 24 * 60 * 60 * 1000000000 - days_to_2000 = 365 * 30 + 7 - # pandas Timestamp class cannot handle floats in pandas 1: - index = pandas.MultiIndex.from_product( - index_values, names=index_names - ) - data_length = index.nunique() - else: - index = None - data_length = index_length - - data = np.arange(data_length) * 10 - - if is_series: - class_ = pandas.Series - else: - class_ = pandas.DataFrame - - return class_(data, index=index) - - def test_1d_no_index(self): - df = self._create_pandas() - result = iris.pandas.as_cubes(df) - - expected_coord = DimCoord(df.index.values) - expected_cube = Cube( - data=df[0].values, - long_name=str(df[0].name), - dim_coords_and_dims=[(expected_coord, 0)], - ) - assert result == [expected_cube] - - def test_1d_with_index(self): - df = self._create_pandas(index_levels=1) - result = iris.pandas.as_cubes(df) - - expected_coord = DimCoord(df.index.values, long_name=df.index.name) - (result_cube,) = result - assert result_cube.dim_coords == (expected_coord,) - - def test_1d_series_no_index(self): - series = self._create_pandas(is_series=True) - result = iris.pandas.as_cubes(series) - - expected_coord = DimCoord(series.index.values) - expected_cube = Cube( - data=series.values, dim_coords_and_dims=[(expected_coord, 0)] - ) - assert result == [expected_cube] - - def test_1d_series_with_index(self): - series = self._create_pandas(index_levels=1, is_series=True) - result = iris.pandas.as_cubes(series) - - expected_coord = DimCoord( - series.index.values, long_name=series.index.name - ) - (result_cube,) = result - assert result_cube.dim_coords == (expected_coord,) - - def test_3d(self): - df = self._create_pandas(index_levels=3) - result = iris.pandas.as_cubes(df) - - expected_coords = [ - DimCoord(level.values, long_name=level.name) - for level in df.index.levels - ] - (result_cube,) = result - assert result_cube.dim_coords == tuple(expected_coords) - - def test_3d_series(self): - series = self._create_pandas(index_levels=3, is_series=True) - result = iris.pandas.as_cubes(series) - - expected_coords = [ - DimCoord(level.values, long_name=level.name) - for level in series.index.levels - ] - (result_cube,) = result - assert result_cube.dim_coords == tuple(expected_coords) - - def test_non_unique_index(self): - df = self._create_pandas(index_levels=1) - new_index = df.index.values - new_index[1] = new_index[0] - df.set_index(new_index) - - with pytest.raises(ValueError, match="not unique per row"): - _ = iris.pandas.as_cubes(df) - - def test_non_monotonic_index(self): - df = self._create_pandas(index_levels=1) - new_index = df.index.values - new_index[:2] = new_index[1::-1] - df.set_index(new_index) - - with pytest.raises(ValueError, match="not monotonic"): - _ = iris.pandas.as_cubes(df) - - def test_missing_rows(self): - df = self._create_pandas(index_levels=2) - df = df[:-1] - - with pytest.raises( - ValueError, match="Not all index values have a corresponding row" - ): - _ = iris.pandas.as_cubes(df) - - def test_aux_coord(self): - df = self._create_pandas() - coord_name = "foo" - df[coord_name] = df.index.values - result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) - - expected_aux_coord = AuxCoord( - df[coord_name].values, long_name=coord_name - ) - (result_cube,) = result - assert result_cube.aux_coords == (expected_aux_coord,) - - def test_cell_measure(self): - df = self._create_pandas() - coord_name = "foo" - df[coord_name] = df.index.values - result = iris.pandas.as_cubes(df, cell_measure_cols=[coord_name]) - - expected_cm = CellMeasure(df[coord_name].values, long_name=coord_name) - (result_cube,) = result - assert result_cube.cell_measures() == [expected_cm] - - def test_ancillary_variable(self): - df = self._create_pandas() - coord_name = "foo" - df[coord_name] = df.index.values - result = iris.pandas.as_cubes(df, ancillary_variable_cols=[coord_name]) - - expected_av = AncillaryVariable( - df[coord_name].values, long_name=coord_name - ) - (result_cube,) = result - assert result_cube.ancillary_variables() == [expected_av] - - def test_3d_with_2d_coord(self): - df = self._create_pandas(index_levels=3) - coord_shape = df.index.levshape[:2] - coord_values = np.arange(np.product(coord_shape)) - coord_name = "foo" - df[coord_name] = coord_values.repeat(df.index.levshape[-1]) - result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) - - expected_points = coord_values.reshape(coord_shape) - (result_cube,) = result - result_coord = result_cube.coord(coord_name) - self.assertArrayEqual(result_coord.points, expected_points) - assert result_coord.cube_dims(result_cube) == (0, 1) - - def test_coord_varies_all_indices(self): - df = self._create_pandas(index_levels=3) - coord_shape = df.index.levshape - coord_values = np.arange(np.product(coord_shape)) - coord_name = "foo" - df[coord_name] = coord_values - result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) - - expected_points = coord_values.reshape(coord_shape) - (result_cube,) = result - result_coord = result_cube.coord(coord_name) - self.assertArrayEqual(result_coord.points, expected_points) - assert result_coord.cube_dims(result_cube) == (0, 1, 2) - - def test_category_coord(self): - # Something that varies on a dimension, but doesn't change with every - # increment. - df = self._create_pandas(index_levels=2) - coord_shape = df.index.levshape - coord_values = np.arange(np.product(coord_shape)) - coord_name = "foo" - - # Create a repeating value along a dimension. - step = coord_shape[-1] - coord_values[1::step] = coord_values[::step] - - df[coord_name] = coord_values - result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) - - expected_points = coord_values.reshape(coord_shape) - (result_cube,) = result - result_coord = result_cube.coord(coord_name) - self.assertArrayEqual(result_coord.points, expected_points) - assert result_coord.cube_dims(result_cube) == (0, 1) - - def test_scalar_coord(self): - df = self._create_pandas() - coord_values = np.ones(len(df)) - coord_name = "foo" - df[coord_name] = coord_values - result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) - - expected_points = np.unique(coord_values) - (result_cube,) = result - result_coord = result_cube.coord(coord_name) - self.assertArrayEqual(result_coord.points, expected_points) - assert result_coord.cube_dims(result_cube) == tuple() - - def test_multi_phenom(self): - df = self._create_pandas() - new_name = "new_phenom" - df[new_name] = df[0] - result = iris.pandas.as_cubes(df) - - # Note the shared coord object between both Cubes. - expected_coord = DimCoord(df.index.values) - expected_cube_kwargs = dict(dim_coords_and_dims=[(expected_coord, 0)]) - - expected_cube_0 = Cube( - data=df[0].values, - long_name=str(df[0].name), - **expected_cube_kwargs, - ) - expected_cube_1 = Cube( - data=df[new_name].values, - long_name=new_name, - **expected_cube_kwargs, - ) - assert result == [expected_cube_0, expected_cube_1] - - def test_empty_series(self): - series = pandas.Series(dtype=object) - result = iris.pandas.as_cubes(series) - - assert result == CubeList() - - def test_empty_dataframe(self): - df = pandas.DataFrame() - result = iris.pandas.as_cubes(df) - - assert result == CubeList() - - def test_no_phenom(self): - df = self._create_pandas() - # Specify the only column as an AuxCoord. - result = iris.pandas.as_cubes(df, aux_coord_cols=[0]) - - assert result == CubeList() - - def test_standard_name_phenom(self): - # long_name behaviour is tested in test_1d_no_index. - df = self._create_pandas() - new_name = "air_temperature" - df = df.rename(columns={0: new_name}) - result = iris.pandas.as_cubes(df) - - (result_cube,) = result - assert result_cube.standard_name == new_name - - def test_standard_name_coord(self): - # long_name behaviour is tested in test_1d_with_index. - df = self._create_pandas() - new_name = "longitude" - df.index.names = [new_name] - result = iris.pandas.as_cubes(df) - - (result_cube,) = result - result_coord = result_cube.coord(dim_coords=True) - assert result_coord.standard_name == new_name - - def test_dtype_preserved_phenom(self): - df = self._create_pandas() - df = df.astype("int32") - result = iris.pandas.as_cubes(df) - - (result_cube,) = result - assert result_cube.dtype == np.int32 - - def test_preserve_dim_order(self): - new_order = ["index_1", "index_0", "index_2"] - - df = self._create_pandas(index_levels=3) - df = df.reset_index() - df = df.set_index(new_order) - df = df.sort_index() - result = iris.pandas.as_cubes(df) - - (result_cube,) = result - dim_order = [c.name() for c in result_cube.dim_coords] - assert dim_order == new_order - - def test_dtype_preserved_coord(self): - df = self._create_pandas() - new_index = df.index.astype("float64") - df.index = new_index - result = iris.pandas.as_cubes(df) - - (result_cube,) = result - result_coord = result_cube.coord(dim_coords=True) - assert result_coord.dtype == np.float64 - - def test_string_phenom(self): - # Strings can be uniquely troublesome. - df = self._create_pandas() - new_values = [str(v) for v in df[0]] - df[0] = new_values - result = iris.pandas.as_cubes(df) - - (result_cube,) = result - self.assertArrayEqual(result_cube.data, new_values) - - def test_string_coord(self): - # Strings can be uniquely troublesome. - # Must test using an AuxCoord since strings cannot be DimCoords. - df = self._create_pandas() - new_points = [str(v) for v in df.index.values] - coord_name = "foo" - df[coord_name] = new_points - result = iris.pandas.as_cubes(df, aux_coord_cols=[coord_name]) - - (result_cube,) = result - result_coord = result_cube.coord(coord_name) - self.assertArrayEqual(result_coord.points, new_points) - - def test_series_with_col_args(self): - series = self._create_pandas(is_series=True) - with pytest.warns(Warning, match="is a Series; ignoring"): - _ = iris.pandas.as_cubes(series, aux_coord_cols=["some_column"]) - - def test_phenom_view(self): - df = self._create_pandas() - result = iris.pandas.as_cubes(df, copy=False) - - # Modify AFTER creating the Cube(s). - df[0][0] += 1 - - (result_cube,) = result - assert result_cube.data[0] == df[0][0] - - def test_phenom_copy(self): - df = self._create_pandas() - result = iris.pandas.as_cubes(df) - - # Modify AFTER creating the Cube(s). - df[0][0] += 1 - - (result_cube,) = result - assert result_cube.data[0] != df[0][0] - - def test_coord_never_view(self): - # Using AuxCoord - DimCoords and Pandas indices are immutable. - df = self._create_pandas() - coord_name = "foo" - df[coord_name] = df.index.values - result = iris.pandas.as_cubes( - df, copy=False, aux_coord_cols=[coord_name] - ) - - # Modify AFTER creating the Cube(s). - df[coord_name][0] += 1 - - (result_cube,) = result - result_coord = result_cube.coord(coord_name) - assert result_coord.points[0] != df[coord_name][0] - - def _test_dates_common(self, mode=None, alt_calendar=False): - df = self._create_pandas() - kwargs = dict(pandas_structure=df) - coord_name = "dates" - - if alt_calendar: - calendar = cf_units.CALENDAR_360_DAY - # Only pass this when non-default. - kwargs["calendars"] = {coord_name: calendar} - expected_points = [8640, 8641, 8642] - else: - calendar = cf_units.CALENDAR_STANDARD - expected_points = [8760, 8761, 8762] - expected_units = cf_units.Unit( - "hours since 1970-01-01 00:00:00", calendar=calendar - ) - - datetime_args = [(1971, 1, 1, i, 0, 0) for i in df.index.values] - if mode == "index": - values = [datetime.datetime(*a) for a in datetime_args] - df.index = pandas.Index(values, name=coord_name) - elif mode == "numpy": - values = [datetime.datetime(*a) for a in datetime_args] - df[coord_name] = values - kwargs["aux_coord_cols"] = [coord_name] - elif mode == "cftime": - values = [ - cftime.datetime(*a, calendar=calendar) for a in datetime_args - ] - df[coord_name] = values - kwargs["aux_coord_cols"] = [coord_name] - else: - raise ValueError("mode needs to be set") - - result = iris.pandas.as_cubes(**kwargs) - - (result_cube,) = result - result_coord = result_cube.coord(coord_name) - assert result_coord.units == expected_units - self.assertArrayEqual(result_coord.points, expected_points) - - def test_datetime_index(self): - self._test_dates_common(mode="index") - - def test_datetime_index_calendar(self): - self._test_dates_common(mode="index", alt_calendar=True) - - def test_numpy_datetime_coord(self): - # NumPy format is what happens if a Python datetime is assigned to a - # Pandas column. - self._test_dates_common(mode="numpy") - - def test_numpy_datetime_coord_calendar(self): - self._test_dates_common(mode="numpy", alt_calendar=True) - - def test_cftime_coord(self): - self._test_dates_common(mode="cftime") - - def test_cftime_coord_calendar(self): - self._test_dates_common(mode="cftime", alt_calendar=True) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/__init__.py b/lib/iris/tests/unit/plot/__init__.py deleted file mode 100644 index f589a29e0d..0000000000 --- a/lib/iris/tests/unit/plot/__init__.py +++ /dev/null @@ -1,118 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.plot` module.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coords import AuxCoord -from iris.plot import _broadcast_2d as broadcast -from iris.tests.stock import lat_lon_cube, simple_2d - - -@tests.skip_plot -class TestGraphicStringCoord(tests.GraphicsTest): - def setUp(self): - super().setUp() - self.cube = simple_2d(with_bounds=True) - self.cube.add_aux_coord( - AuxCoord(list("abcd"), long_name="str_coord"), 1 - ) - self.lat_lon_cube = lat_lon_cube() - - def tick_loc_and_label(self, axis_name, axes=None): - # Intentional lazy import so that subclasses can have an opportunity - # to change the backend. - import matplotlib.pyplot as plt - - # Draw the plot to 'fix' the ticks. - if axes: - axes.figure.canvas.draw() - else: - axes = plt.gca() - plt.draw() - axis = getattr(axes, axis_name) - - locations = axis.get_majorticklocs() - labels = [tick.get_text() for tick in axis.get_ticklabels()] - return list(zip(locations, labels)) - - def assertBoundsTickLabels(self, axis, axes=None): - actual = self.tick_loc_and_label(axis, axes) - expected = [ - (-1.0, ""), - (0.0, "a"), - (1.0, "b"), - (2.0, "c"), - (3.0, "d"), - (4.0, ""), - ] - self.assertEqual(expected, actual) - - def assertPointsTickLabels(self, axis, axes=None): - actual = self.tick_loc_and_label(axis, axes) - expected = [(0.0, "a"), (1.0, "b"), (2.0, "c"), (3.0, "d")] - self.assertEqual(expected, actual) - - -@tests.skip_plot -class MixinCoords: - """ - Mixin class of common plotting tests providing 2-dimensional - permutations of coordinates and anonymous dimensions. - - """ - - def _check(self, u, v, data=None): - self.assertEqual(self.mpl_patch.call_count, 1) - if data is not None: - (actual_u, actual_v, actual_data), _ = self.mpl_patch.call_args - self.assertArrayEqual(actual_data, data) - else: - (actual_u, actual_v), _ = self.mpl_patch.call_args - self.assertArrayEqual(actual_u, u) - self.assertArrayEqual(actual_v, v) - - def test_foo_bar(self): - self.draw_func(self.cube, coords=("foo", "bar")) - u, v = broadcast(self.foo, self.bar) - self._check(u, v, self.data) - - def test_bar_foo(self): - self.draw_func(self.cube, coords=("bar", "foo")) - u, v = broadcast(self.bar, self.foo) - self._check(u, v, self.dataT) - - def test_foo_0(self): - self.draw_func(self.cube, coords=("foo", 0)) - u, v = broadcast(self.foo, self.bar_index) - self._check(u, v, self.data) - - def test_1_bar(self): - self.draw_func(self.cube, coords=(1, "bar")) - u, v = broadcast(self.foo_index, self.bar) - self._check(u, v, self.data) - - def test_1_0(self): - self.draw_func(self.cube, coords=(1, 0)) - u, v = broadcast(self.foo_index, self.bar_index) - self._check(u, v, self.data) - - def test_0_foo(self): - self.draw_func(self.cube, coords=(0, "foo")) - u, v = broadcast(self.bar_index, self.foo) - self._check(u, v, self.dataT) - - def test_bar_1(self): - self.draw_func(self.cube, coords=("bar", 1)) - u, v = broadcast(self.bar, self.foo_index) - self._check(u, v, self.dataT) - - def test_0_1(self): - self.draw_func(self.cube, coords=(0, 1)) - u, v = broadcast(self.bar_index, self.foo_index) - self._check(u, v, self.dataT) diff --git a/lib/iris/tests/unit/plot/_blockplot_common.py b/lib/iris/tests/unit/plot/_blockplot_common.py deleted file mode 100644 index 455b416164..0000000000 --- a/lib/iris/tests/unit/plot/_blockplot_common.py +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Common test code for `iris.plot.pcolor` and `iris.plot.pcolormesh`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords - - -class MixinStringCoordPlot: - # Mixin for common string-coord tests on pcolor/pcolormesh. - # To use, make a class that inherits from this *and* - # :class:`iris.tests.unit.plot.TestGraphicStringCoord`, - # and defines "self.blockplot_func()", to return the `iris.plot` function. - def test_yaxis_labels(self): - self.blockplot_func()(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - self.blockplot_func()(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") - - def test_xaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_xlim(0, 3) - self.blockplot_func()(self.cube, coords=("str_coord", "bar"), axes=ax) - plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) - - def test_yaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_ylim(0, 3) - self.blockplot_func()(self.cube, axes=ax, coords=("bar", "str_coord")) - plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) - - def test_geoaxes_exception(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - self.assertRaises( - TypeError, self.blockplot_func(), self.lat_lon_cube, axes=ax - ) - plt.close(fig) - - -class Mixin2dCoordsPlot(MixinCoords): - # Mixin for common coordinate tests on pcolor/pcolormesh. - # To use, make a class that inherits from this *and* - # :class:`iris.tests.IrisTest`, - # and defines "self.blockplot_func()", to return the `iris.plot` function. - def blockplot_setup(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=True) - coord = self.cube.coord("foo") - self.foo = coord.contiguous_bounds() - self.foo_index = np.arange(coord.points.size + 1) - coord = self.cube.coord("bar") - self.bar = coord.contiguous_bounds() - self.bar_index = np.arange(coord.points.size + 1) - self.data = self.cube.data - self.dataT = self.data.T - self.draw_func = self.blockplot_func() - patch_target_name = "matplotlib.pyplot." + self.draw_func.__name__ - self.mpl_patch = self.patch(patch_target_name) - - -class Mixin2dCoordsContigTol: - # Mixin for contiguity tolerance argument to pcolor/pcolormesh. - # To use, make a class that inherits from this *and* - # :class:`iris.tests.IrisTest`, - # and defines "self.blockplot_func()", to return the `iris.plot` function, - # and defines "self.additional_kwargs" for expected extra call args. - def test_contig_tol(self): - # Patch the inner call to ensure contiguity_tolerance is passed. - cube_argument = mock.sentinel.passed_arg - expected_result = mock.sentinel.returned_value - blockplot_patch = self.patch( - "iris.plot._draw_2d_from_bounds", - mock.Mock(return_value=expected_result), - ) - # Make the call - draw_func = self.blockplot_func() - other_kwargs = self.additional_kwargs - result = draw_func(cube_argument, contiguity_tolerance=0.0123) - drawfunc_name = draw_func.__name__ - # Check details of the call that was made. - self.assertEqual( - blockplot_patch.call_args_list, - [ - mock.call( - drawfunc_name, - cube_argument, - contiguity_tolerance=0.0123, - **other_kwargs, - ) - ], - ) - self.assertEqual(result, expected_result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py b/lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py deleted file mode 100644 index 4dfc6d7f68..0000000000 --- a/lib/iris/tests/unit/plot/test__check_bounds_contiguity_and_mask.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot._check_bounds_contiguity_and_mask` -function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np -import numpy.ma as ma - -from iris.coords import DimCoord -from iris.plot import _check_bounds_contiguity_and_mask -from iris.tests.stock import ( - make_bounds_discontiguous_at_point, - sample_2d_latlons, -) - - -@tests.skip_plot -class Test_check_bounds_contiguity_and_mask(tests.IrisTest): - def test_1d_not_checked(self): - # Test a 1D coordinate, which is not checked as atol is not set. - coord = DimCoord([1, 3, 5], bounds=[[0, 2], [2, 4], [5, 6]]) - data = np.array([278, 300, 282]) - # Make sure contiguity checking doesn't throw an error - _check_bounds_contiguity_and_mask(coord, data) - - def test_1d_contiguous(self): - # Test that a 1D coordinate which is contiguous does not fail. - coord = DimCoord([1, 3, 5], bounds=[[0, 2], [2, 4], [4, 6]]) - data = np.array([278, 300, 282]) - _check_bounds_contiguity_and_mask(coord, data, atol=1e-3) - - def test_1d_discontigous_masked(self): - # Test a 1D coordinate which is discontiguous but masked at - # discontiguities. - coord = DimCoord([1, 3, 5], bounds=[[0, 2], [2, 4], [5, 6]]) - data = ma.array(np.array([278, 300, 282]), mask=[0, 1, 0]) - _check_bounds_contiguity_and_mask(coord, data, atol=1e-3) - - def test_1d_discontigous_unmasked(self): - # Test a 1D coordinate which is discontiguous and unmasked at - # discontiguities. - coord = DimCoord([1, 3, 5], bounds=[[0, 2], [2, 4], [5, 6]]) - data = ma.array(np.array([278, 300, 282]), mask=[1, 0, 0]) - msg = ( - "coordinate are not contiguous and data is not masked where " - "the discontiguity occurs" - ) - with self.assertRaisesRegex(ValueError, msg): - _check_bounds_contiguity_and_mask(coord, data, atol=1e-3) - - def test_2d_contiguous(self): - # Test that a 2D coordinate which is contiguous does not throw - # an error. - cube = sample_2d_latlons() - _check_bounds_contiguity_and_mask(cube.coord("longitude"), cube.data) - - def test_2d_contiguous_atol(self): - # Check the atol is passed correctly. - cube = sample_2d_latlons() - with mock.patch( - "iris.coords.Coord._discontiguity_in_bounds" - ) as discontiguity_check: - # Discontiguity returns two objects that are unpacked in - # `_check_bounds_contiguity_and_mask`. - discontiguity_check.return_value = [True, None] - _check_bounds_contiguity_and_mask( - cube.coord("longitude"), cube.data, atol=1e-3 - ) - discontiguity_check.assert_called_with(atol=1e-3) - - def test_2d_discontigous_masked(self): - # Test that a 2D coordinate which is discontiguous but masked at - # discontiguities doesn't error. - cube = sample_2d_latlons() - make_bounds_discontiguous_at_point(cube, 3, 4) - _check_bounds_contiguity_and_mask(cube.coord("longitude"), cube.data) - - def test_2d_discontigous_unmasked(self): - # Test a 2D coordinate which is discontiguous and unmasked at - # discontiguities. - cube = sample_2d_latlons() - make_bounds_discontiguous_at_point(cube, 3, 4) - msg = "coordinate are not contiguous" - cube.data[3, 4] = ma.nomask - with self.assertRaisesRegex(ValueError, msg): - _check_bounds_contiguity_and_mask( - cube.coord("longitude"), cube.data - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py b/lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py deleted file mode 100644 index 633dea85c4..0000000000 --- a/lib/iris/tests/unit/plot/test__check_geostationary_coords_and_convert.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot._check_geostationary_coords_and_convert -function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest.mock import Mock - -from cartopy.crs import Geostationary, NearsidePerspective -import numpy as np - -from iris.plot import _check_geostationary_coords_and_convert - - -class Test__check_geostationary_coords_and_convert(tests.IrisTest): - def setUp(self): - geostationary_altitude = 35785831.0 - # proj4_params is the one attribute of the Geostationary class that - # is needed for the function. - self.proj4_params = {"h": geostationary_altitude} - - # Simulate the maximum-dimension array that could be processed. - a = np.linspace(0, 2, 6) - b = np.linspace(2, 3, 5) - self.x_original, self.y_original = np.meshgrid(a, b) - - # Expected arrays if conversion takes place. - self.x_converted, self.y_converted = ( - i * geostationary_altitude - for i in (self.x_original, self.y_original) - ) - - def _test(self, geostationary=True): - # Re-usable test for when Geostationary is present OR absent. - if geostationary: - # A Geostationary projection WILL be processed. - projection_spec = Geostationary - target_tuple = (self.x_converted, self.y_converted) - else: - # A non-Geostationary projection WILL NOT be processed. - projection_spec = NearsidePerspective - target_tuple = (self.x_original, self.y_original) - - projection = Mock(spec=projection_spec) - projection.proj4_params = self.proj4_params - # Projection is looked for within a dictionary called kwargs. - kwargs = {"transform": projection} - - x, y = _check_geostationary_coords_and_convert( - self.x_original, self.y_original, kwargs - ) - self.assertArrayEqual((x, y), target_tuple) - - def test_geostationary_present(self): - self._test(geostationary=True) - - def test_geostationary_absent(self): - self._test(geostationary=False) diff --git a/lib/iris/tests/unit/plot/test__fixup_dates.py b/lib/iris/tests/unit/plot/test__fixup_dates.py deleted file mode 100644 index 0abef01e41..0000000000 --- a/lib/iris/tests/unit/plot/test__fixup_dates.py +++ /dev/null @@ -1,82 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot._fixup_dates` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import datetime - -from cf_units import Unit -import cftime - -from iris.coords import AuxCoord -from iris.plot import _fixup_dates - - -class Test(tests.IrisTest): - def test_standard_calendar(self): - unit = Unit("hours since 2000-04-13 00:00:00", calendar="standard") - coord = AuxCoord([1, 3, 6], "time", units=unit) - result = _fixup_dates(coord, coord.points) - self.assertIsInstance(result[0], datetime.datetime) - expected = [ - datetime.datetime(2000, 4, 13, 1), - datetime.datetime(2000, 4, 13, 3), - datetime.datetime(2000, 4, 13, 6), - ] - self.assertArrayEqual(result, expected) - - def test_standard_calendar_sub_second(self): - unit = Unit("seconds since 2000-04-13 00:00:00", calendar="standard") - coord = AuxCoord([1, 1.25, 1.5], "time", units=unit) - result = _fixup_dates(coord, coord.points) - self.assertIsInstance(result[0], datetime.datetime) - expected = [ - datetime.datetime(2000, 4, 13, 0, 0, 1), - datetime.datetime(2000, 4, 13, 0, 0, 1), - datetime.datetime(2000, 4, 13, 0, 0, 2), - ] - self.assertArrayEqual(result, expected) - - @tests.skip_nc_time_axis - def test_360_day_calendar(self): - calendar = "360_day" - unit = Unit("days since 2000-02-25 00:00:00", calendar=calendar) - coord = AuxCoord([3, 4, 5], "time", units=unit) - result = _fixup_dates(coord, coord.points) - expected_datetimes = [ - cftime.datetime(2000, 2, 28, calendar=calendar), - cftime.datetime(2000, 2, 29, calendar=calendar), - cftime.datetime(2000, 2, 30, calendar=calendar), - ] - self.assertArrayEqual(result, expected_datetimes) - - @tests.skip_nc_time_axis - def test_365_day_calendar(self): - calendar = "365_day" - unit = Unit("minutes since 2000-02-25 00:00:00", calendar=calendar) - coord = AuxCoord([30, 60, 150], "time", units=unit) - result = _fixup_dates(coord, coord.points) - expected_datetimes = [ - cftime.datetime(2000, 2, 25, 0, 30, calendar=calendar), - cftime.datetime(2000, 2, 25, 1, 0, calendar=calendar), - cftime.datetime(2000, 2, 25, 2, 30, calendar=calendar), - ] - self.assertArrayEqual(result, expected_datetimes) - - @tests.skip_nc_time_axis - def test_360_day_calendar_attribute(self): - calendar = "360_day" - unit = Unit("days since 2000-02-01 00:00:00", calendar=calendar) - coord = AuxCoord([0, 3, 6], "time", units=unit) - result = _fixup_dates(coord, coord.points) - self.assertEqual(result[0].calendar, calendar) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__get_plot_defn.py b/lib/iris/tests/unit/plot/test__get_plot_defn.py deleted file mode 100644 index c69173dc70..0000000000 --- a/lib/iris/tests/unit/plot/test__get_plot_defn.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot._get_plot_defn` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris.coords -from iris.tests.stock import simple_2d, simple_2d_w_multidim_coords - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class Test_get_plot_defn(tests.IrisTest): - def test_axis_order_xy(self): - cube_xy = simple_2d() - defn = iplt._get_plot_defn(cube_xy, iris.coords.POINT_MODE) - self.assertEqual( - [coord.name() for coord in defn.coords], ["bar", "foo"] - ) - - def test_axis_order_yx(self): - cube_yx = simple_2d() - cube_yx.transpose() - defn = iplt._get_plot_defn(cube_yx, iris.coords.POINT_MODE) - self.assertEqual( - [coord.name() for coord in defn.coords], ["foo", "bar"] - ) - - def test_2d_coords(self): - cube = simple_2d_w_multidim_coords() - defn = iplt._get_plot_defn(cube, iris.coords.BOUND_MODE) - self.assertEqual( - [coord.name() for coord in defn.coords], ["bar", "foo"] - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py b/lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py deleted file mode 100644 index 631f9bd24e..0000000000 --- a/lib/iris/tests/unit/plot/test__get_plot_defn_custom_coords_picked.py +++ /dev/null @@ -1,90 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot._get_plot_defn_custom_coords_picked` -function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.coords import BOUND_MODE, POINT_MODE -from iris.tests.stock import ( - hybrid_height, - simple_2d, - simple_2d_w_multidim_coords, -) - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class Test_get_plot_defn_custom_coords_picked(tests.IrisTest): - def test_1d_coords(self): - cube = simple_2d() - defn = iplt._get_plot_defn_custom_coords_picked( - cube, ("foo", "bar"), POINT_MODE - ) - self.assertEqual( - [coord.name() for coord in defn.coords], ["bar", "foo"] - ) - self.assertFalse(defn.transpose) - - def test_1d_coords_swapped(self): - cube = simple_2d() - defn = iplt._get_plot_defn_custom_coords_picked( - cube, ("bar", "foo"), POINT_MODE - ) - self.assertEqual( - [coord.name() for coord in defn.coords], ["foo", "bar"] - ) - self.assertTrue(defn.transpose) - - def test_1d_coords_as_integers(self): - cube = simple_2d() - defn = iplt._get_plot_defn_custom_coords_picked( - cube, (1, 0), POINT_MODE - ) - self.assertEqual([coord for coord in defn.coords], [0, 1]) - self.assertFalse(defn.transpose) - - def test_1d_coords_as_integers_swapped(self): - cube = simple_2d() - defn = iplt._get_plot_defn_custom_coords_picked( - cube, (0, 1), POINT_MODE - ) - self.assertEqual([coord for coord in defn.coords], [1, 0]) - self.assertTrue(defn.transpose) - - def test_2d_coords(self): - cube = simple_2d_w_multidim_coords() - defn = iplt._get_plot_defn_custom_coords_picked( - cube, ("foo", "bar"), BOUND_MODE - ) - self.assertEqual( - [coord.name() for coord in defn.coords], ["bar", "foo"] - ) - self.assertFalse(defn.transpose) - - def test_2d_coords_as_integers(self): - cube = simple_2d_w_multidim_coords() - defn = iplt._get_plot_defn_custom_coords_picked( - cube, (0, 1), BOUND_MODE - ) - self.assertEqual([coord for coord in defn.coords], [1, 0]) - self.assertTrue(defn.transpose) - - def test_span_check(self): - cube = hybrid_height() - emsg = "don't span the 2 data dimensions" - with self.assertRaisesRegex(ValueError, emsg): - iplt._get_plot_defn_custom_coords_picked( - cube, ("sigma", "level_height"), POINT_MODE - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__get_plot_objects.py b/lib/iris/tests/unit/plot/test__get_plot_objects.py deleted file mode 100644 index 8586faa756..0000000000 --- a/lib/iris/tests/unit/plot/test__get_plot_objects.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot._get_plot_objects` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import iris.cube - -if tests.MPL_AVAILABLE: - from iris.plot import _get_plot_objects - - -@tests.skip_plot -class Test__get_plot_objects(tests.IrisTest): - def test_scalar(self): - cube1 = iris.cube.Cube(1) - cube2 = iris.cube.Cube(1) - expected = (cube1, cube2, 1, 1, ()) - result = _get_plot_objects((cube1, cube2)) - self.assertTupleEqual(expected, result) - - def test_mismatched_size_first_scalar(self): - cube1 = iris.cube.Cube(1) - cube2 = iris.cube.Cube([1, 42]) - with self.assertRaisesRegex( - ValueError, "x and y-axis objects are not compatible" - ): - _get_plot_objects((cube1, cube2)) - - def test_mismatched_size_second_scalar(self): - cube1 = iris.cube.Cube(1) - cube2 = iris.cube.Cube([1, 42]) - with self.assertRaisesRegex( - ValueError, "x and y-axis objects are not compatible" - ): - _get_plot_objects((cube2, cube1)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py b/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py deleted file mode 100644 index c4416c587d..0000000000 --- a/lib/iris/tests/unit/plot/test__replace_axes_with_cartopy_axes.py +++ /dev/null @@ -1,45 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.__replace_axes_with_cartopy_axes` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import cartopy.crs as ccrs -import matplotlib.pyplot as plt - -from iris.plot import _replace_axes_with_cartopy_axes - - -@tests.skip_plot -class Test_replace_axes_with_cartopy_axes(tests.IrisTest): - def setUp(self): - self.fig = plt.figure() - - def test_preserve_position(self): - position = [0.17, 0.65, 0.2, 0.2] - projection = ccrs.PlateCarree() - - plt.axes(position) - _replace_axes_with_cartopy_axes(projection) - result = plt.gca() - - # result should be the same as an axes created directly with the projection. - expected = plt.axes(position, projection=projection) - - # get_position returns mpl.transforms.Bbox object, for which equality does - # not appear to be implemented. Compare the bounds (tuple) instead. - self.assertEqual( - expected.get_position().bounds, result.get_position().bounds - ) - - def tearDown(self): - plt.close(self.fig) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_contour.py b/lib/iris/tests/unit/plot/test_contour.py deleted file mode 100644 index 823b3270d0..0000000000 --- a/lib/iris/tests/unit/plot/test_contour.py +++ /dev/null @@ -1,74 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.contour` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - iplt.contour(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") - - def test_xaxis_labels(self): - iplt.contour(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") - - def test_yaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.contour(self.cube, axes=ax, coords=("bar", "str_coord")) - plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) - - def test_xaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.contour(self.cube, axes=ax, coords=("str_coord", "bar")) - plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) - - def test_geoaxes_exception(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.contour, self.lat_lon_cube, axes=ax) - plt.close(fig) - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=False) - self.foo = self.cube.coord("foo").points - self.foo_index = np.arange(self.foo.size) - self.bar = self.cube.coord("bar").points - self.bar_index = np.arange(self.bar.size) - self.data = self.cube.data - self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.contour") - self.draw_func = iplt.contour - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_contourf.py b/lib/iris/tests/unit/plot/test_contourf.py deleted file mode 100644 index 0247fb5a91..0000000000 --- a/lib/iris/tests/unit/plot/test_contourf.py +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.contourf` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import matplotlib.pyplot as plt -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - iplt.contourf(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") - - def test_xaxis_labels(self): - iplt.contourf(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") - - def test_yaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.contourf(self.cube, axes=ax, coords=("bar", "str_coord")) - plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) - - def test_xaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.contourf(self.cube, axes=ax, coords=("str_coord", "bar")) - plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) - - def test_geoaxes_exception(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.contourf, self.lat_lon_cube, axes=ax) - plt.close(fig) - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=False) - self.foo = self.cube.coord("foo").points - self.foo_index = np.arange(self.foo.size) - self.bar = self.cube.coord("bar").points - self.bar_index = np.arange(self.bar.size) - self.data = self.cube.data - self.dataT = self.data.T - mocker = mock.Mock(alpha=0, antialiased=False) - self.mpl_patch = self.patch( - "matplotlib.pyplot.contourf", return_value=mocker - ) - self.draw_func = iplt.contourf - - -@tests.skip_plot -class TestAntialias(tests.IrisTest): - def setUp(self): - self.fig = plt.figure() - - def test_skip_contour(self): - # Contours should not be added if data is all below second level. See #4086. - cube = simple_2d() - - levels = [5, 15, 20, 200] - colors = ["b", "r", "y"] - - with mock.patch("matplotlib.pyplot.contour") as mocked_contour: - iplt.contourf(cube, levels=levels, colors=colors, antialiased=True) - - mocked_contour.assert_not_called() - - def test_apply_contour_nans(self): - # Presence of nans should not prevent contours being added. - cube = simple_2d() - cube.data = cube.data.astype(np.float_) - cube.data[0, 0] = np.nan - - levels = [2, 4, 6, 8] - colors = ["b", "r", "y"] - - with mock.patch("matplotlib.pyplot.contour") as mocked_contour: - iplt.contourf(cube, levels=levels, colors=colors, antialiased=True) - - mocked_contour.assert_called_once() - - def tearDown(self): - plt.close(self.fig) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_outline.py b/lib/iris/tests/unit/plot/test_outline.py deleted file mode 100644 index de59287362..0000000000 --- a/lib/iris/tests/unit/plot/test_outline.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.outline` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - iplt.outline(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - iplt.outline(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") - - def test_xaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_xlim(0, 3) - iplt.outline(self.cube, coords=("str_coord", "bar"), axes=ax) - plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) - - def test_yaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_ylim(0, 3) - iplt.outline(self.cube, axes=ax, coords=("bar", "str_coord")) - plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) - - def test_geoaxes_exception(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.outline, self.lat_lon_cube, axes=ax) - plt.close(fig) - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=True) - coord = self.cube.coord("foo") - self.foo = coord.contiguous_bounds() - self.foo_index = np.arange(coord.points.size + 1) - coord = self.cube.coord("bar") - self.bar = coord.contiguous_bounds() - self.bar_index = np.arange(coord.points.size + 1) - self.data = self.cube.data - self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.pcolormesh") - self.draw_func = iplt.outline - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_pcolor.py b/lib/iris/tests/unit/plot/test_pcolor.py deleted file mode 100644 index 1cde9e8822..0000000000 --- a/lib/iris/tests/unit/plot/test_pcolor.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.pcolor` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.tests.unit.plot import TestGraphicStringCoord -from iris.tests.unit.plot._blockplot_common import ( - Mixin2dCoordsContigTol, - Mixin2dCoordsPlot, - MixinStringCoordPlot, -) - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - PLOT_FUNCTION_TO_TEST = iplt.pcolor - - -@tests.skip_plot -class TestStringCoordPlot(MixinStringCoordPlot, TestGraphicStringCoord): - def blockplot_func(self): - return PLOT_FUNCTION_TO_TEST - - -@tests.skip_plot -class Test2dCoords(tests.IrisTest, Mixin2dCoordsPlot): - def setUp(self): - self.blockplot_setup() - - def blockplot_func(self): - return PLOT_FUNCTION_TO_TEST - - -@tests.skip_plot -class Test2dContigTol(tests.IrisTest, Mixin2dCoordsContigTol): - # Extra call kwargs expected. - additional_kwargs = dict(antialiased=True, snap=False) - - def blockplot_func(self): - return PLOT_FUNCTION_TO_TEST - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_pcolormesh.py b/lib/iris/tests/unit/plot/test_pcolormesh.py deleted file mode 100644 index f4e84e5765..0000000000 --- a/lib/iris/tests/unit/plot/test_pcolormesh.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.pcolormesh` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from iris.tests.unit.plot import TestGraphicStringCoord -from iris.tests.unit.plot._blockplot_common import ( - Mixin2dCoordsContigTol, - Mixin2dCoordsPlot, - MixinStringCoordPlot, -) - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - PLOT_FUNCTION_TO_TEST = iplt.pcolormesh - - -@tests.skip_plot -class TestStringCoordPlot(MixinStringCoordPlot, TestGraphicStringCoord): - def blockplot_func(self): - return PLOT_FUNCTION_TO_TEST - - -@tests.skip_plot -class Test2dCoords(tests.IrisTest, Mixin2dCoordsPlot): - def setUp(self): - self.blockplot_setup() - - def blockplot_func(self): - return PLOT_FUNCTION_TO_TEST - - -@tests.skip_plot -class Test2dContigTol(tests.IrisTest, Mixin2dCoordsContigTol): - # Extra call kwargs expected -- unlike 'pcolor', there are none. - additional_kwargs = {} - - def blockplot_func(self): - return PLOT_FUNCTION_TO_TEST - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_plot.py b/lib/iris/tests/unit/plot/test_plot.py deleted file mode 100644 index edbef3934a..0000000000 --- a/lib/iris/tests/unit/plot/test_plot.py +++ /dev/null @@ -1,272 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.plot` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -import iris.coord_systems as ics -import iris.coords as coords -from iris.tests.unit.plot import TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import cartopy.crs as ccrs - import cartopy.mpl.geoaxes - from matplotlib.path import Path - import matplotlib.pyplot as plt - - import iris.plot as iplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() - self.cube = self.cube[0, :] - self.lat_lon_cube = self.lat_lon_cube[0, :] - - def test_yaxis_labels(self): - iplt.plot(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - iplt.plot(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") - - def test_yaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.plot(self.cube, self.cube.coord("str_coord"), axes=ax) - plt.close(fig) - self.assertBoundsTickLabels("yaxis", ax) - - def test_xaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.plot(self.cube.coord("str_coord"), self.cube, axes=ax) - plt.close(fig) - self.assertBoundsTickLabels("xaxis", ax) - - def test_plot_longitude(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.plot( - self.lat_lon_cube.coord("longitude"), self.lat_lon_cube, axes=ax - ) - plt.close(fig) - - -@tests.skip_plot -class TestTrajectoryWrap(tests.IrisTest): - """ - Test that a line plot of geographic coordinates wraps around the end of the - coordinates rather than plotting accross the map. - - """ - - def setUp(self): - plt.figure() - self.geog_cs = ics.GeogCS(6371229.0) - self.plate_carree = self.geog_cs.as_cartopy_projection() - - def lon_lat_coords(self, lons, lats, cs=None): - if cs is None: - cs = self.geog_cs - return ( - coords.AuxCoord( - lons, "longitude", units="degrees", coord_system=cs - ), - coords.AuxCoord( - lats, "latitude", units="degrees", coord_system=cs - ), - ) - - def assertPathsEqual(self, expected, actual): - """ - Assert that the given paths are equal once STOP vertices have been - removed - - """ - expected = expected.cleaned() - actual = actual.cleaned() - # Remove Path.STOP vertices - everts = expected.vertices[np.where(expected.codes != Path.STOP)] - averts = actual.vertices[np.where(actual.codes != Path.STOP)] - self.assertArrayAlmostEqual(everts, averts) - self.assertArrayEqual(expected.codes, actual.codes) - - def check_paths(self, expected_path, expected_path_crs, lines, axes): - """ - Check that the paths in `lines` match the given expected paths when - plotted on the given geoaxes - - """ - - self.assertEqual( - 1, len(lines), "Expected a single line, got {}".format(len(lines)) - ) - (line,) = lines - inter_proj_transform = cartopy.mpl.geoaxes.InterProjectionTransform( - expected_path_crs, axes.projection - ) - ax_transform = inter_proj_transform + axes.transData - - expected = ax_transform.transform_path(expected_path) - actual = line.get_transform().transform_path(line.get_path()) - - self.assertPathsEqual(expected, actual) - - def test_simple(self): - lon, lat = self.lon_lat_coords([359, 1], [0, 0]) - expected_path = Path([[-1, 0], [1, 0]], [Path.MOVETO, Path.LINETO]) - - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def test_reverse(self): - lon, lat = self.lon_lat_coords([1, 359], [0, 0]) - expected_path = Path([[1, 0], [-1, 0]], [Path.MOVETO, Path.LINETO]) - - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def test_multi(self): - lon, lat = self.lon_lat_coords([1, 359, 2, 358], [0, 0, 0, 0]) - expected_path = Path( - [[1, 0], [-1, 0], [2, 0], [-2, 0]], - [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO], - ) - - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def test_many_wraps(self): - lon, lat = self.lon_lat_coords( - [350, 10, 180, 350, 10, 180, 10, 350], [0, 0, 0, 0, 0, 0, 0, 0] - ) - expected_path = Path( - [ - [350, 0], - [370, 0], - [540, 0], - [710, 0], - [730, 0], - [900, 0], - [730, 0], - [710, 0], - ], - [ - Path.MOVETO, - Path.LINETO, - Path.LINETO, - Path.LINETO, - Path.LINETO, - Path.LINETO, - Path.LINETO, - Path.LINETO, - ], - ) - - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def test_180(self): - lon, lat = self.lon_lat_coords([179, -179], [0, 0]) - expected_path = Path([[179, 0], [181, 0]], [Path.MOVETO, Path.LINETO]) - - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def test_shifted_projection(self): - lon, lat = self.lon_lat_coords([359, 1], [0, 0]) - expected_path = Path([[-1, 0], [1, 0]], [Path.MOVETO, Path.LINETO]) - - shifted_plate_carree = ccrs.PlateCarree(180) - - plt.axes(projection=shifted_plate_carree) - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def test_shifted_projection_180(self): - lon, lat = self.lon_lat_coords([179, -179], [0, 0]) - expected_path = Path([[179, 0], [181, 0]], [Path.MOVETO, Path.LINETO]) - - shifted_plate_carree = ccrs.PlateCarree(180) - - plt.axes(projection=shifted_plate_carree) - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def test_long(self): - lon, lat = self.lon_lat_coords([271, 89], [0, 0]) - expected_path = Path([[-89, 0], [89, 0]], [Path.MOVETO, Path.LINETO]) - - lines = iplt.plot(lon, lat) - - self.check_paths(expected_path, self.plate_carree, lines, plt.gca()) - - def _test_rotated( - self, - grid_north_pole_latitude=90, - grid_north_pole_longitude=0, - north_pole_grid_longitude=0, - ): - cs = ics.RotatedGeogCS( - grid_north_pole_latitude, - grid_north_pole_longitude, - north_pole_grid_longitude, - ) - glon = coords.AuxCoord( - [359, 1], "grid_longitude", units="degrees", coord_system=cs - ) - glat = coords.AuxCoord( - [0, 0], "grid_latitude", units="degrees", coord_system=cs - ) - expected_path = Path([[-1, 0], [1, 0]], [Path.MOVETO, Path.LINETO]) - - plt.figure() - lines = iplt.plot(glon, glat) - # Matplotlib won't immediately set up the correct transform to allow us - # to compare paths. Calling set_global(), which calls set_xlim() and - # set_ylim(), will trigger Matplotlib to set up the transform. - ax = plt.gca() - ax.set_global() - - crs = cs.as_cartopy_crs() - self.check_paths(expected_path, crs, lines, ax) - - def test_rotated_90(self): - self._test_rotated(north_pole_grid_longitude=90) - - def test_rotated_180(self): - self._test_rotated(north_pole_grid_longitude=180) - - def test_rotated(self): - self._test_rotated( - grid_north_pole_latitude=-30, - grid_north_pole_longitude=120, - north_pole_grid_longitude=45, - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_points.py b/lib/iris/tests/unit/plot/test_points.py deleted file mode 100644 index e1a23eff83..0000000000 --- a/lib/iris/tests/unit/plot/test_points.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.points` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - iplt.points(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - iplt.points(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") - - def test_xaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_xlim(0, 3) - iplt.points(self.cube, coords=("str_coord", "bar"), axes=ax) - plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) - - def test_yaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_ylim(0, 3) - iplt.points(self.cube, coords=("bar", "str_coord"), axes=ax) - plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) - - def test_geoaxes_exception(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - self.assertRaises(TypeError, iplt.points, self.lat_lon_cube, axes=ax) - plt.close(fig) - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=False) - self.foo = self.cube.coord("foo").points - self.foo_index = np.arange(self.foo.size) - self.bar = self.cube.coord("bar").points - self.bar_index = np.arange(self.bar.size) - self.data = None - self.dataT = None - self.mpl_patch = self.patch("matplotlib.pyplot.scatter") - self.draw_func = iplt.points - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/plot/test_scatter.py b/lib/iris/tests/unit/plot/test_scatter.py deleted file mode 100644 index c5cd9cb2f2..0000000000 --- a/lib/iris/tests/unit/plot/test_scatter.py +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.plot.scatter` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip -from iris.tests.unit.plot import TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.plot as iplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() - self.cube = self.cube[0, :] - self.lat_lon_cube = self.lat_lon_cube[0, :] - - def test_xaxis_labels(self): - iplt.scatter(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") - - def test_yaxis_labels(self): - iplt.scatter(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_xlim(0, 3) - iplt.scatter(self.cube.coord("str_coord"), self.cube, axes=ax) - plt.close(fig) - self.assertPointsTickLabels("xaxis", ax) - - def test_yaxis_labels_with_axes(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - ax.set_ylim(0, 3) - iplt.scatter(self.cube, self.cube.coord("str_coord"), axes=ax) - plt.close(fig) - self.assertPointsTickLabels("yaxis", ax) - - def test_scatter_longitude(self): - import matplotlib.pyplot as plt - - fig = plt.figure() - ax = fig.add_subplot(111) - iplt.scatter( - self.lat_lon_cube, self.lat_lon_cube.coord("longitude"), axes=ax - ) - plt.close(fig) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/__init__.py b/lib/iris/tests/unit/quickplot/__init__.py deleted file mode 100644 index 471ef0f6a5..0000000000 --- a/lib/iris/tests/unit/quickplot/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.quickplot` module.""" diff --git a/lib/iris/tests/unit/quickplot/test_contour.py b/lib/iris/tests/unit/quickplot/test_contour.py deleted file mode 100644 index 8e3db7c3e0..0000000000 --- a/lib/iris/tests/unit/quickplot/test_contour.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.contour` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - qplt.contour(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") - - def test_xaxis_labels(self): - qplt.contour(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=False) - self.foo = self.cube.coord("foo").points - self.foo_index = np.arange(self.foo.size) - self.bar = self.cube.coord("bar").points - self.bar_index = np.arange(self.bar.size) - self.data = self.cube.data - self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.contour") - self.draw_func = qplt.contour - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_contourf.py b/lib/iris/tests/unit/quickplot/test_contourf.py deleted file mode 100644 index 2624ebd08e..0000000000 --- a/lib/iris/tests/unit/quickplot/test_contourf.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.contourf` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -from unittest import mock - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - qplt.contourf(self.cube, coords=("bar", "str_coord")) - self.assertPointsTickLabels("yaxis") - - def test_xaxis_labels(self): - qplt.contourf(self.cube, coords=("str_coord", "bar")) - self.assertPointsTickLabels("xaxis") - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=False) - self.foo = self.cube.coord("foo").points - self.foo_index = np.arange(self.foo.size) - self.bar = self.cube.coord("bar").points - self.bar_index = np.arange(self.bar.size) - self.data = self.cube.data - self.dataT = self.data.T - mocker = mock.Mock(alpha=0, antialiased=False) - self.mpl_patch = self.patch( - "matplotlib.pyplot.contourf", return_value=mocker - ) - # Also need to mock the colorbar. - self.patch("matplotlib.pyplot.colorbar") - self.draw_func = qplt.contourf - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_outline.py b/lib/iris/tests/unit/quickplot/test_outline.py deleted file mode 100644 index 70d96372fa..0000000000 --- a/lib/iris/tests/unit/quickplot/test_outline.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.outline` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - qplt.outline(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - qplt.outline(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=True) - coord = self.cube.coord("foo") - self.foo = coord.contiguous_bounds() - self.foo_index = np.arange(coord.points.size + 1) - coord = self.cube.coord("bar") - self.bar = coord.contiguous_bounds() - self.bar_index = np.arange(coord.points.size + 1) - self.data = self.cube.data - self.dataT = self.data.T - self.mpl_patch = self.patch("matplotlib.pyplot.pcolormesh") - self.draw_func = qplt.outline - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_pcolor.py b/lib/iris/tests/unit/quickplot/test_pcolor.py deleted file mode 100644 index 2e559d6308..0000000000 --- a/lib/iris/tests/unit/quickplot/test_pcolor.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.pcolor` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - qplt.pcolor(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - qplt.pcolor(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=True) - coord = self.cube.coord("foo") - self.foo = coord.contiguous_bounds() - self.foo_index = np.arange(coord.points.size + 1) - coord = self.cube.coord("bar") - self.bar = coord.contiguous_bounds() - self.bar_index = np.arange(coord.points.size + 1) - self.data = self.cube.data - self.dataT = self.data.T - self.mpl_patch = self.patch( - "matplotlib.pyplot.pcolor", return_value=None - ) - self.draw_func = qplt.pcolor - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_pcolormesh.py b/lib/iris/tests/unit/quickplot/test_pcolormesh.py deleted file mode 100644 index 32ae3ed716..0000000000 --- a/lib/iris/tests/unit/quickplot/test_pcolormesh.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.pcolormesh` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - qplt.pcolormesh(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - qplt.pcolormesh(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=True) - coord = self.cube.coord("foo") - self.foo = coord.contiguous_bounds() - self.foo_index = np.arange(coord.points.size + 1) - coord = self.cube.coord("bar") - self.bar = coord.contiguous_bounds() - self.bar_index = np.arange(coord.points.size + 1) - self.data = self.cube.data - self.dataT = self.data.T - self.mpl_patch = self.patch( - "matplotlib.pyplot.pcolormesh", return_value=None - ) - self.draw_func = qplt.pcolormesh - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_plot.py b/lib/iris/tests/unit/quickplot/test_plot.py deleted file mode 100644 index 0a36a3fa4e..0000000000 --- a/lib/iris/tests/unit/quickplot/test_plot.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.plot` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip -from iris.tests.stock import simple_1d -from iris.tests.unit.plot import TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() - self.cube = self.cube[0, :] - - def test_yaxis_labels(self): - qplt.plot(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - qplt.plot(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") - - -class TestAxisLabels(tests.GraphicsTest): - def test_xy_cube(self): - c = simple_1d() - qplt.plot(c) - ax = qplt.plt.gca() - x = ax.xaxis.get_label().get_text() - self.assertEqual(x, "Foo") - y = ax.yaxis.get_label().get_text() - self.assertEqual(y, "Thingness") - - def test_yx_cube(self): - c = simple_1d() - c.transpose() - # Making the cube a vertical coordinate should change the default - # orientation of the plot. - c.coord("foo").attributes["positive"] = "up" - qplt.plot(c) - ax = qplt.plt.gca() - x = ax.xaxis.get_label().get_text() - self.assertEqual(x, "Thingness") - y = ax.yaxis.get_label().get_text() - self.assertEqual(y, "Foo") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_points.py b/lib/iris/tests/unit/quickplot/test_points.py deleted file mode 100644 index 3810cdd343..0000000000 --- a/lib/iris/tests/unit/quickplot/test_points.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.points` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import simple_2d -from iris.tests.unit.plot import MixinCoords, TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def test_yaxis_labels(self): - qplt.points(self.cube, coords=("bar", "str_coord")) - self.assertBoundsTickLabels("yaxis") - - def test_xaxis_labels(self): - qplt.points(self.cube, coords=("str_coord", "bar")) - self.assertBoundsTickLabels("xaxis") - - -@tests.skip_plot -class TestCoords(tests.IrisTest, MixinCoords): - def setUp(self): - # We have a 2d cube with dimensionality (bar: 3; foo: 4) - self.cube = simple_2d(with_bounds=False) - self.foo = self.cube.coord("foo").points - self.foo_index = np.arange(self.foo.size) - self.bar = self.cube.coord("bar").points - self.bar_index = np.arange(self.bar.size) - self.data = None - self.dataT = None - self.mpl_patch = self.patch("matplotlib.pyplot.scatter") - self.draw_func = qplt.points - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/quickplot/test_scatter.py b/lib/iris/tests/unit/quickplot/test_scatter.py deleted file mode 100644 index c1cf853970..0000000000 --- a/lib/iris/tests/unit/quickplot/test_scatter.py +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.quickplot.scatter` function.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip -from iris.tests.unit.plot import TestGraphicStringCoord - -if tests.MPL_AVAILABLE: - import iris.quickplot as qplt - - -@tests.skip_plot -class TestStringCoordPlot(TestGraphicStringCoord): - def setUp(self): - super().setUp() - self.cube = self.cube[0, :] - - def test_xaxis_labels(self): - qplt.scatter(self.cube.coord("str_coord"), self.cube) - self.assertBoundsTickLabels("xaxis") - - def test_yaxis_labels(self): - qplt.scatter(self.cube, self.cube.coord("str_coord")) - self.assertBoundsTickLabels("yaxis") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/representation/__init__.py b/lib/iris/tests/unit/representation/__init__.py deleted file mode 100644 index e943ad149b..0000000000 --- a/lib/iris/tests/unit/representation/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._representation` module.""" diff --git a/lib/iris/tests/unit/representation/cube_printout/__init__.py b/lib/iris/tests/unit/representation/cube_printout/__init__.py deleted file mode 100644 index 50ab3f8e45..0000000000 --- a/lib/iris/tests/unit/representation/cube_printout/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._representation.cube_printout` module.""" diff --git a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py b/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py deleted file mode 100644 index 21fc8efa73..0000000000 --- a/lib/iris/tests/unit/representation/cube_printout/test_CubePrintout.py +++ /dev/null @@ -1,567 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris._representation.cube_printout.CubePrintout`.""" -import iris.tests as tests # isort:skip - -import numpy as np - -from iris._representation.cube_printout import CubePrinter -from iris._representation.cube_summary import CubeSummary -from iris.coords import ( - AncillaryVariable, - AuxCoord, - CellMeasure, - CellMethod, - DimCoord, -) -from iris.cube import Cube -from iris.tests.stock.mesh import sample_mesh_cube - - -class TestCubePrintout___str__(tests.IrisTest): - def test_str(self): - # Just check that its str representation is the 'to_string' result. - cube = Cube(0) - printer = CubePrinter(CubeSummary(cube)) - result = str(printer) - self.assertEqual(result, printer.to_string()) - - -def cube_replines(cube, **kwargs): - return CubePrinter(cube).to_string(**kwargs).split("\n") - - -class TestCubePrintout__to_string(tests.IrisTest): - def test_empty(self): - cube = Cube([0]) - rep = cube_replines(cube) - expect = ["unknown / (unknown) (-- : 1)"] - self.assertEqual(expect, rep) - - def test_shortform__default(self): - cube = Cube([0]) - expect = ["unknown / (unknown) (-- : 1)"] - # In this case, default one-line is the same. - rep = cube_replines(cube, oneline=True) - self.assertEqual(expect, rep) - - def test_shortform__compressed(self): - cube = Cube([0]) - rep = cube_replines(cube, oneline=True, name_padding=0) - expect = ["unknown / (unknown) (-- : 1)"] - self.assertEqual(rep, expect) - - def _sample_wide_cube(self): - cube = Cube([0, 1]) - cube.add_aux_coord( - AuxCoord( - [0, 1], - long_name="long long long long long long long long name", - ), - 0, - ) - return cube - - def test_wide_cube(self): - # For comparison with the shortform and padding-controlled cases. - cube = self._sample_wide_cube() - rep = cube_replines(cube) - expect_full = [ - "unknown / (unknown) (-- : 2)", - " Auxiliary coordinates:", - " long long long long long long long long name x", - ] - self.assertEqual(expect_full, rep) - - def test_shortform__wide__default(self): - cube = self._sample_wide_cube() - rep = cube_replines(cube, oneline=True) - # *default* one-line is shorter than full header, but not minimal. - expect = ["unknown / (unknown) (-- : 2)"] - self.assertEqual(rep, expect) - - def test_shortform__wide__compressed(self): - cube = self._sample_wide_cube() - rep = cube_replines(cube, oneline=True, name_padding=0) - expect = ["unknown / (unknown) (-- : 2)"] - self.assertEqual(rep, expect) - - def test_shortform__wide__intermediate(self): - cube = self._sample_wide_cube() - rep = cube_replines(cube, oneline=True, name_padding=25) - expect = ["unknown / (unknown) (-- : 2)"] - self.assertEqual(expect, rep) - - def test_scalar_cube_summaries(self): - cube = Cube(0) - expect = ["unknown / (unknown) (scalar cube)"] - rep = cube_replines(cube) - self.assertEqual(expect, rep) - # Shortform is the same. - rep = cube_replines(cube, oneline=True) - self.assertEqual(expect, rep) - - def test_name_padding(self): - cube = Cube([1, 2], long_name="cube_accel", units="ms-2") - rep = cube_replines(cube) - self.assertEqual(rep, ["cube_accel / (ms-2) (-- : 2)"]) - rep = cube_replines(cube, name_padding=0) - self.assertEqual(rep, ["cube_accel / (ms-2) (-- : 2)"]) - rep = cube_replines(cube, name_padding=25) - self.assertEqual(rep, ["cube_accel / (ms-2) (-- : 2)"]) - - def test_columns_long_coordname(self): - cube = Cube([0], long_name="short", units=1) - coord = AuxCoord( - [0], long_name="very_very_very_very_very_long_coord_name" - ) - cube.add_aux_coord(coord, 0) - rep = cube_replines(cube) - expected = [ - "short / (1) (-- : 1)", - " Auxiliary coordinates:", - " very_very_very_very_very_long_coord_name x", - ] - self.assertEqual(expected, rep) - rep = cube_replines(cube, oneline=True) - # Note: the default short-form is short-ER, but not minimal. - short_expected = ["short / (1) (-- : 1)"] - self.assertEqual(short_expected, rep) - - def test_columns_long_attribute(self): - cube = Cube([0], long_name="short", units=1) - cube.attributes[ - "very_very_very_very_very_long_name" - ] = "longish string extends beyond dim columns" - rep = cube_replines(cube) - expected = [ - "short / (1) (-- : 1)", - " Attributes:", - ( - " very_very_very_very_very_long_name " - "'longish string extends beyond dim columns'" - ), - ] - self.assertEqual(rep, expected) - - def test_coord_distinguishing_attributes(self): - # Printout of differing attributes to differentiate same-named coords. - # include : vector + scalar - cube = Cube([0, 1], long_name="name", units=1) - # Add a pair of vector coords with same name but different attributes. - cube.add_aux_coord( - AuxCoord([0, 1], long_name="co1", attributes=dict(a=1)), 0 - ) - cube.add_aux_coord( - AuxCoord([0, 1], long_name="co1", attributes=dict(a=2)), 0 - ) - # Likewise for scalar coords with same name but different attributes. - cube.add_aux_coord( - AuxCoord([0], long_name="co2", attributes=dict(a=10, b=12)) - ) - cube.add_aux_coord( - AuxCoord([1], long_name="co2", attributes=dict(a=10, b=11)) - ) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 2)", - " Auxiliary coordinates:", - " co1 x", - " a=1", - " co1 x", - " a=2", - " Scalar coordinates:", - " co2 0", - " b=12", - " co2 1", - " b=11", - ] - self.assertEqual(rep, expected) - - def test_coord_extra_attributes__array(self): - cube = Cube(0, long_name="name", units=1) - # Add a pair of vector coords with same name but different attributes. - array1 = np.arange(0, 3) - array2 = np.arange(10, 13) - cube.add_aux_coord( - AuxCoord([1.2], long_name="co1", attributes=dict(a=1, arr=array1)) - ) - cube.add_aux_coord( - AuxCoord([3.4], long_name="co1", attributes=dict(a=1, arr=array2)) - ) - - rep = cube_replines(cube) - expected = [ - "name / (1) (scalar cube)", - " Scalar coordinates:", - " co1 1.2", - " arr=array([0, 1, 2])", - " co1 3.4", - " arr=array([10, 11, 12])", - ] - self.assertEqual(rep, expected) - - def test_coord_extra_attributes__array__long(self): - # Also test with a long array representation. - # NOTE: this also pushes the dimension map right-wards. - array = 10 + np.arange(24.0).reshape((2, 3, 4)) - cube = Cube(0, long_name="name", units=1) - cube.add_aux_coord(AuxCoord([1], long_name="co")) - cube.add_aux_coord( - AuxCoord([2], long_name="co", attributes=dict(a=array + 1.0)) - ) - - rep = cube_replines(cube) - expected = [ - ( - "name / (1) " - " (scalar cube)" - ), - " Scalar coordinates:", - ( - " co " - " 1" - ), - ( - " co " - " 2" - ), - ( - " a=array([[[11., 12., 13., 14.], [15., 16., 17.," - " 18.], [19., 20., 21., 22.]],..." - ), - ] - self.assertEqual(rep, expected) - - def test_coord_extra_attributes__string(self): - cube = Cube(0, long_name="name", units=1) - cube.add_aux_coord(AuxCoord([1], long_name="co")) - cube.add_aux_coord( - AuxCoord( - [2], long_name="co", attributes=dict(note="string content") - ) - ) - rep = cube_replines(cube) - expected = [ - "name / (1) (scalar cube)", - " Scalar coordinates:", - " co 1", - " co 2", - " note='string content'", - ] - self.assertEqual(rep, expected) - - def test_coord_extra_attributes__string_escaped(self): - cube = Cube(0, long_name="name", units=1) - cube.add_aux_coord(AuxCoord([1], long_name="co")) - cube.add_aux_coord( - AuxCoord( - [2], - long_name="co", - attributes=dict(note="line 1\nline 2\tends."), - ) - ) - rep = cube_replines(cube) - expected = [ - "name / (1) (scalar cube)", - " Scalar coordinates:", - " co 1", - " co 2", - " note='line 1\\nline 2\\tends.'", - ] - self.assertEqual(rep, expected) - - def test_coord_extra_attributes__string_overlong(self): - cube = Cube(0, long_name="name", units=1) - cube.add_aux_coord(AuxCoord([1], long_name="co")) - long_string = ( - "this is very very very very very very very " - "very very very very very very very long." - ) - cube.add_aux_coord( - AuxCoord([2], long_name="co", attributes=dict(note=long_string)) - ) - rep = cube_replines(cube) - expected = [ - ( - "name / (1) " - " (scalar cube)" - ), - " Scalar coordinates:", - ( - " co " - " 1" - ), - ( - " co " - " 2" - ), - ( - " note='this is very very very very " - "very very very very very very very very..." - ), - ] - self.assertEqual(rep, expected) - - def test_section_vector_dimcoords(self): - cube = Cube(np.zeros((2, 3)), long_name="name", units=1) - cube.add_dim_coord(DimCoord([0, 1], long_name="y"), 0) - cube.add_dim_coord(DimCoord([0, 1, 2], long_name="x"), 1) - - rep = cube_replines(cube) - expected = [ - "name / (1) (y: 2; x: 3)", - " Dimension coordinates:", - " y x -", - " x - x", - ] - self.assertEqual(rep, expected) - - def test_section_vector_auxcoords(self): - cube = Cube(np.zeros((2, 3)), long_name="name", units=1) - cube.add_aux_coord(DimCoord([0, 1], long_name="y"), 0) - cube.add_aux_coord(DimCoord([0, 1, 2], long_name="x"), 1) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 2; -- : 3)", - " Auxiliary coordinates:", - " y x -", - " x - x", - ] - self.assertEqual(rep, expected) - - def test_section_vector_ancils(self): - cube = Cube(np.zeros((2, 3)), long_name="name", units=1) - cube.add_ancillary_variable( - AncillaryVariable([0, 1], long_name="av1"), 0 - ) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 2; -- : 3)", - " Ancillary variables:", - " av1 x -", - ] - self.assertEqual(rep, expected) - - def test_section_vector_ancils_length_1(self): - # Check ancillary variables that map to a cube dimension of length 1 - # are not interpreted as scalar ancillary variables. - cube = Cube(np.zeros((1, 3)), long_name="name", units=1) - cube.add_ancillary_variable(AncillaryVariable([0], long_name="av1"), 0) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 1; -- : 3)", - " Ancillary variables:", - " av1 x -", - ] - self.assertEqual(rep, expected) - - def test_section_vector_cell_measures(self): - cube = Cube(np.zeros((2, 3)), long_name="name", units=1) - cube.add_cell_measure(CellMeasure([0, 1, 2], long_name="cm"), 1) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 2; -- : 3)", - " Cell measures:", - " cm - x", - ] - self.assertEqual(rep, expected) - - def test_section_vector_cell_measures_length_1(self): - # Check cell measures that map to a cube dimension of length 1 are not - # interpreted as scalar cell measures. - cube = Cube(np.zeros((2, 1)), long_name="name", units=1) - cube.add_cell_measure(CellMeasure([0], long_name="cm"), 1) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 2; -- : 1)", - " Cell measures:", - " cm - x", - ] - self.assertEqual(rep, expected) - - def test_section_scalar_coords(self): - # incl points + bounds - # TODO: ought to incorporate coord-based summary - # - which would allow for special printout of time values - cube = Cube([0], long_name="name", units=1) - cube.add_aux_coord(DimCoord([0.0], long_name="unbounded")) - cube.add_aux_coord(DimCoord([0], bounds=[[0, 7]], long_name="bounded")) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 1)", - " Scalar coordinates:", - " bounded 0, bound=(0, 7)", - " unbounded 0.0", - ] - self.assertEqual(rep, expected) - - def test_section_scalar_coords__string(self): - # incl a newline-escaped one - # incl a long (clipped) one - # CHECK THAT CLIPPED+ESCAPED WORKS (don't lose final quote) - cube = Cube([0], long_name="name", units=1) - cube.add_aux_coord(AuxCoord(["string-value"], long_name="text")) - long_string = ( - "A string value which is very very very very very very " - "very very very very very very very very long." - ) - cube.add_aux_coord( - AuxCoord([long_string], long_name="very_long_string") - ) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 1)", - " Scalar coordinates:", - " text string-value", - ( - " very_long_string A string value which is " - "very very very very very very very very very very..." - ), - ] - self.assertEqual(rep, expected) - - def test_section_scalar_cell_measures(self): - cube = Cube(np.zeros((2, 3)), long_name="name", units=1) - cube.add_cell_measure(CellMeasure([0], long_name="cm")) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 2; -- : 3)", - " Scalar cell measures:", - " cm", - ] - self.assertEqual(rep, expected) - - def test_section_scalar_ancillaries(self): - # There *is* no section for this. But there probably ought to be. - cube = Cube(np.zeros((2, 3)), long_name="name", units=1) - cube.add_ancillary_variable(AncillaryVariable([0], long_name="av")) - - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 2; -- : 3)", - " Scalar ancillary variables:", - " av", - ] - self.assertEqual(rep, expected) - - def test_section_cube_attributes(self): - cube = Cube([0], long_name="name", units=1) - cube.attributes["number"] = 1.2 - cube.attributes["list"] = [3] - cube.attributes["string"] = "four five in a string" - cube.attributes["z_tupular"] = (6, (7, 8)) - rep = cube_replines(cube) - # NOTE: 'list' before 'number', as it uses "sorted(attrs.items())" - expected = [ - "name / (1) (-- : 1)", - " Attributes:", - " list [3]", - " number 1.2", - " string 'four five in a string'", - " z_tupular (6, (7, 8))", - ] - self.assertEqual(rep, expected) - - def test_section_cube_attributes__string_extras(self): - cube = Cube([0], long_name="name", units=1) - # Overlong strings are truncated (with iris.util.clip_string). - long_string = ( - "this is very very very very very very very " - "very very very very very very very long." - ) - # Strings with embedded newlines or quotes are printed in quoted form. - cube.attributes["escaped"] = "escaped\tstring" - cube.attributes["long"] = long_string - cube.attributes["long_multi"] = "multi\nline, " + long_string - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 1)", - " Attributes:", - " escaped 'escaped\\tstring'", - ( - " long 'this is very very very " - "very very very very very very very very very very ...'" - ), - ( - " long_multi 'multi\\nline, " - "this is very very very very very very very very very very ...'" - ), - ] - self.assertEqual(rep, expected) - - def test_section_cube_attributes__array(self): - # Including a long one, which gets a truncated representation. - cube = Cube([0], long_name="name", units=1) - small_array = np.array([1.2, 3.4]) - large_array = np.arange(36).reshape((18, 2)) - cube.attributes["array"] = small_array - cube.attributes["bigarray"] = large_array - rep = cube_replines(cube) - expected = [ - "name / (1) (-- : 1)", - " Attributes:", - " array array([1.2, 3.4])", - ( - " bigarray array([[ 0, 1], [ 2, 3], " - "[ 4, 5], [ 6, 7], [ 8, 9], [10, 11], [12, 13], ..." - ), - ] - self.assertEqual(rep, expected) - - def test_section_cell_methods(self): - cube = Cube([0], long_name="name", units=1) - cube.add_cell_method(CellMethod("stdev", "area")) - cube.add_cell_method( - CellMethod( - method="mean", - coords=["y", "time"], - intervals=["10m", "3min"], - comments=["vertical", "=duration"], - ) - ) - rep = cube_replines(cube) - # Note: not alphabetical -- provided order is significant - expected = [ - "name / (1) (-- : 1)", - " Cell methods:", - " stdev area", - " mean y (10m, vertical), time (3min, =duration)", - ] - self.assertEqual(rep, expected) - - def test_unstructured_cube(self): - # Check a sample mesh-cube against the expected result. - cube = sample_mesh_cube() - rep = cube_replines(cube) - expected = [ - "mesh_phenom / (unknown) (level: 2; i_mesh_face: 3)", - " Dimension coordinates:", - " level x -", - " i_mesh_face - x", - " Mesh coordinates:", - " latitude - x", - " longitude - x", - " Auxiliary coordinates:", - " mesh_face_aux - x", - " Mesh:", - " name unknown", - " location face", - ] - self.assertEqual(rep, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/representation/cube_printout/test_Table.py b/lib/iris/tests/unit/representation/cube_printout/test_Table.py deleted file mode 100644 index 2ff6738998..0000000000 --- a/lib/iris/tests/unit/representation/cube_printout/test_Table.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris._representation.cube_printout.Table`.""" -from iris._representation.cube_printout import Table -import iris.tests as tests - - -class TestTable(tests.IrisTest): - # Note: this is just barely an independent definition, not *strictly* part - # of CubePrinter, but effectively more-or-less so. - def setUp(self): - table = Table() - table.add_row(["one", "b", "three"], aligns=["left", "right", "left"]) - table.add_row(["a", "two", "c"], aligns=["right", "left", "right"]) - self.simple_table = table - - def test_empty(self): - table = Table() - self.assertIsNone(table.n_columns) - self.assertEqual(len(table.rows), 0) - self.assertIsNone(table.col_widths) - # Check other methods : should be ok but do nothing. - table.set_min_column_widths() # Ok but does nothing. - self.assertIsNone(table.col_widths) - self.assertEqual(table.formatted_as_strings(), []) - self.assertEqual(str(table), "") - - def test_basic_content(self): - # Mirror the above 'empty' tests on a small basic table. - table = self.simple_table - self.assertEqual(table.n_columns, 3) - self.assertEqual(len(table.rows), 2) - self.assertIsNone(table.col_widths) - table.set_min_column_widths() # Ok but does nothing. - self.assertEqual(table.col_widths, [3, 3, 5]) - self.assertEqual( - table.formatted_as_strings(), ["one b three", " a two c"] - ) - self.assertEqual(str(table), "one b three\n a two c") - - def test_copy(self): - table = self.simple_table - # Add some detail information - table.rows[1].i_col_unlimited = 77 # Doesn't actually affect anything - table.col_widths = [10, 15, 12] - # Make the copy - table2 = table.copy() - self.assertIsNot(table2, table) - self.assertNotEqual(table2, table) # Note: equality is not implemented - # Check the parts match the original. - self.assertEqual(len(table2.rows), len(table.rows)) - for row2, row in zip(table2.rows, table.rows): - self.assertEqual(row2.cols, row.cols) - self.assertEqual(row2.aligns, row.aligns) - self.assertEqual(row2.i_col_unlimited, row.i_col_unlimited) - - def test_add_row(self): - table = Table() - self.assertEqual(table.n_columns, None) - # Add onw row. - table.add_row(["one", "two", "three"], aligns=["left", "left", "left"]) - self.assertEqual(len(table.rows), 1) - self.assertEqual(table.n_columns, 3) - self.assertIsNone(table.rows[0].i_col_unlimited) - # Second row ok. - table.add_row( - ["x", "y", "z"], - aligns=["right", "right", "right"], - i_col_unlimited=199, - ) - self.assertEqual(len(table.rows), 2) - self.assertEqual(table.rows[-1].i_col_unlimited, 199) - - # Fails with bad number of columns - regex = "columns.*!=.*existing" - with self.assertRaisesRegex(ValueError, regex): - table.add_row(["1", "2"], ["left", "right"]) - - # Fails with bad number of aligns - regex = "aligns.*!=.*col" - with self.assertRaisesRegex(ValueError, regex): - table.add_row(["1", "2", "3"], ["left", "left", "left", "left"]) - - def test_formatted_as_strings(self): - # Test simple self-print is same as - table = Table() - aligns = ["left", "right", "left"] - table.add_row(["1", "266", "32"], aligns) - table.add_row(["123", "2", "3"], aligns) - - # Check that printing calculates default column widths, and result.. - self.assertEqual(table.col_widths, None) - result = table.formatted_as_strings() - self.assertEqual(result, ["1 266 32", "123 2 3"]) - self.assertEqual(table.col_widths, [3, 3, 2]) - - def test_fail_bad_alignments(self): - # Invalid 'aligns' content : only detected when printed - table = Table() - table.add_row(["1", "2", "3"], ["left", "right", "BAD"]) - regex = 'Unknown alignment "BAD"' - with self.assertRaisesRegex(ValueError, regex): - str(table) - - def test_table_set_width(self): - # Check that changes do *not* affect pre-existing widths. - table = Table() - aligns = ["left", "right", "left"] - table.col_widths = [3, 3, 2] - table.add_row(["333", "333", "22"], aligns) - table.add_row(["a", "b", "c"], aligns) - table.add_row(["12345", "12345", "12345"], aligns) - result = table.formatted_as_strings() - self.assertEqual(table.col_widths, [3, 3, 2]) - self.assertEqual( - result, - [ - "333 333 22", - "a b c", - "12345 12345 12345", # These are exceeding the given widths. - ], - ) - - def test_unlimited_column(self): - table = Table() - aligns = ["left", "right", "left"] - table.add_row(["a", "beee", "c"], aligns) - table.add_row( - ["abcd", "any-longer-stuff", "this"], aligns, i_col_unlimited=1 - ) - table.add_row(["12", "x", "yy"], aligns) - result = table.formatted_as_strings() - self.assertEqual( - result, - [ - "a beee c", - "abcd any-longer-stuff this", - # NOTE: the widths-calc is ignoring cols 1-2, but - # entry#0 *is* extending the width of col#0 - "12 x yy", - ], - ) - - def test_str(self): - # Check that str returns the formatted_as_strings() output. - table = Table() - aligns = ["left", "left", "left"] - table.add_row(["one", "two", "three"], aligns=aligns) - table.add_row(["1", "2", "3"], aligns=aligns) - expected = "\n".join(table.formatted_as_strings()) - result = str(table) - self.assertEqual(result, expected) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/representation/cube_summary/__init__.py b/lib/iris/tests/unit/representation/cube_summary/__init__.py deleted file mode 100644 index c20a621ba2..0000000000 --- a/lib/iris/tests/unit/representation/cube_summary/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris._representation.cube_summary` module.""" diff --git a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py b/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py deleted file mode 100644 index bcf31a016f..0000000000 --- a/lib/iris/tests/unit/representation/cube_summary/test_CubeSummary.py +++ /dev/null @@ -1,327 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :class:`iris._representation.cube_summary.CubeSummary`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris._representation.cube_summary import CubeSummary -from iris.coords import ( - AncillaryVariable, - AuxCoord, - CellMeasure, - CellMethod, - DimCoord, -) -from iris.cube import Cube -from iris.tests.stock.mesh import sample_mesh_cube - - -def example_cube(): - cube = Cube( - np.arange(6).reshape([3, 2]), - standard_name="air_temperature", - long_name="screen_air_temp", - var_name="airtemp", - units="K", - ) - lat = DimCoord([0, 1, 2], standard_name="latitude", units="degrees") - cube.add_dim_coord(lat, 0) - return cube - - -class Test_CubeSummary(tests.IrisTest): - def setUp(self): - self.cube = example_cube() - - def test_header(self): - rep = CubeSummary(self.cube) - header_left = rep.header.nameunit - header_right = rep.header.dimension_header.contents - - self.assertEqual(header_left, "air_temperature / (K)") - self.assertEqual(header_right, ["latitude: 3", "-- : 2"]) - - def test_blank_cube(self): - cube = Cube([1, 2]) - rep = CubeSummary(cube) - - self.assertEqual(rep.header.nameunit, "unknown / (unknown)") - self.assertEqual(rep.header.dimension_header.contents, ["-- : 2"]) - - expected_vector_sections = [ - "Dimension coordinates:", - "Mesh coordinates:", - "Auxiliary coordinates:", - "Derived coordinates:", - "Cell measures:", - "Ancillary variables:", - ] - self.assertEqual( - list(rep.vector_sections.keys()), expected_vector_sections - ) - for title in expected_vector_sections: - vector_section = rep.vector_sections[title] - self.assertEqual(vector_section.contents, []) - self.assertTrue(vector_section.is_empty()) - - expected_scalar_sections = [ - "Mesh:", - "Scalar coordinates:", - "Scalar cell measures:", - "Scalar ancillary variables:", - "Cell methods:", - "Attributes:", - ] - - self.assertEqual( - list(rep.scalar_sections.keys()), expected_scalar_sections - ) - for title in expected_scalar_sections: - scalar_section = rep.scalar_sections[title] - self.assertEqual(scalar_section.contents, []) - self.assertTrue(scalar_section.is_empty()) - - def test_vector_coord(self): - rep = CubeSummary(self.cube) - dim_section = rep.vector_sections["Dimension coordinates:"] - - self.assertEqual(len(dim_section.contents), 1) - self.assertFalse(dim_section.is_empty()) - - dim_summary = dim_section.contents[0] - - name = dim_summary.name - dim_chars = dim_summary.dim_chars - extra = dim_summary.extra - - self.assertEqual(name, "latitude") - self.assertEqual(dim_chars, ["x", "-"]) - self.assertEqual(extra, "") - - def test_scalar_coord(self): - cube = self.cube - scalar_coord_no_bounds = AuxCoord([10], long_name="bar", units="K") - scalar_coord_with_bounds = AuxCoord( - [10], long_name="foo", units="K", bounds=[(5, 15)] - ) - scalar_coord_simple_text = AuxCoord( - ["this and that"], - long_name="foo", - attributes={"key": 42, "key2": "value-str"}, - ) - scalar_coord_awkward_text = AuxCoord( - ["a is\nb\n and c"], long_name="foo_2" - ) - cube.add_aux_coord(scalar_coord_no_bounds) - cube.add_aux_coord(scalar_coord_with_bounds) - cube.add_aux_coord(scalar_coord_simple_text) - cube.add_aux_coord(scalar_coord_awkward_text) - rep = CubeSummary(cube) - - scalar_section = rep.scalar_sections["Scalar coordinates:"] - - self.assertEqual(len(scalar_section.contents), 4) - - no_bounds_summary = scalar_section.contents[0] - bounds_summary = scalar_section.contents[1] - text_summary_simple = scalar_section.contents[2] - text_summary_awkward = scalar_section.contents[3] - - self.assertEqual(no_bounds_summary.name, "bar") - self.assertEqual(no_bounds_summary.content, "10 K") - self.assertEqual(no_bounds_summary.extra, "") - - self.assertEqual(bounds_summary.name, "foo") - self.assertEqual(bounds_summary.content, "10 K, bound=(5, 15) K") - self.assertEqual(bounds_summary.extra, "") - - self.assertEqual(text_summary_simple.name, "foo") - self.assertEqual(text_summary_simple.content, "this and that") - self.assertEqual(text_summary_simple.lines, ["this and that"]) - self.assertEqual(text_summary_simple.extra, "key=42, key2='value-str'") - - self.assertEqual(text_summary_awkward.name, "foo_2") - self.assertEqual(text_summary_awkward.content, r"'a is\nb\n and c'") - self.assertEqual(text_summary_awkward.lines, ["a is", "b", " and c"]) - self.assertEqual(text_summary_awkward.extra, "") - - def test_cell_measure(self): - cube = self.cube - cell_measure = CellMeasure([1, 2, 3], long_name="foo") - cube.add_cell_measure(cell_measure, 0) - rep = CubeSummary(cube) - - cm_section = rep.vector_sections["Cell measures:"] - self.assertEqual(len(cm_section.contents), 1) - - cm_summary = cm_section.contents[0] - self.assertEqual(cm_summary.name, "foo") - self.assertEqual(cm_summary.dim_chars, ["x", "-"]) - - def test_ancillary_variable(self): - cube = self.cube - cell_measure = AncillaryVariable([1, 2, 3], long_name="foo") - cube.add_ancillary_variable(cell_measure, 0) - rep = CubeSummary(cube) - - av_section = rep.vector_sections["Ancillary variables:"] - self.assertEqual(len(av_section.contents), 1) - - av_summary = av_section.contents[0] - self.assertEqual(av_summary.name, "foo") - self.assertEqual(av_summary.dim_chars, ["x", "-"]) - - def test_attributes(self): - cube = self.cube - cube.attributes = {"a": 1, "b": "two", "c": " this \n that\tand."} - rep = CubeSummary(cube) - - attribute_section = rep.scalar_sections["Attributes:"] - attribute_contents = attribute_section.contents - expected_contents = [ - "a: 1", - "b: 'two'", - "c: ' this \\n that\\tand.'", - ] - # Note: a string with \n or \t in it gets "repr-d". - # Other strings don't (though in coord 'extra' lines, they do.) - - self.assertEqual(attribute_contents, expected_contents) - - def test_cell_methods(self): - cube = self.cube - x = AuxCoord(1, long_name="x") - y = AuxCoord(1, long_name="y") - cell_method_xy = CellMethod("mean", [x, y]) - cell_method_x = CellMethod("mean", x) - cube.add_cell_method(cell_method_xy) - cube.add_cell_method(cell_method_x) - - rep = CubeSummary(cube) - cell_method_section = rep.scalar_sections["Cell methods:"] - expected_contents = ["mean: x, y", "mean: x"] - self.assertEqual(cell_method_section.contents, expected_contents) - - def test_scalar_cube(self): - cube = self.cube - while cube.ndim > 0: - cube = cube[0] - rep = CubeSummary(cube) - self.assertEqual(rep.header.nameunit, "air_temperature / (K)") - self.assertTrue(rep.header.dimension_header.scalar) - self.assertEqual(rep.header.dimension_header.dim_names, []) - self.assertEqual(rep.header.dimension_header.shape, []) - self.assertEqual(rep.header.dimension_header.contents, ["scalar cube"]) - self.assertEqual(len(rep.vector_sections), 6) - self.assertTrue( - all(sect.is_empty() for sect in rep.vector_sections.values()) - ) - self.assertEqual(len(rep.scalar_sections), 6) - self.assertEqual( - len(rep.scalar_sections["Scalar coordinates:"].contents), 1 - ) - self.assertTrue( - rep.scalar_sections["Scalar cell measures:"].is_empty() - ) - self.assertTrue(rep.scalar_sections["Attributes:"].is_empty()) - self.assertTrue(rep.scalar_sections["Cell methods:"].is_empty()) - - def test_coord_attributes(self): - cube = self.cube - co1 = cube.coord("latitude") - co1.attributes.update(dict(a=1, b=2)) - co2 = co1.copy() - co2.attributes.update(dict(a=7, z=77, text="ok", text2="multi\nline")) - cube.add_aux_coord(co2, cube.coord_dims(co1)) - rep = CubeSummary(cube) - co1_summ = rep.vector_sections["Dimension coordinates:"].contents[0] - co2_summ = rep.vector_sections["Auxiliary coordinates:"].contents[0] - # Notes: 'b' is same so does not appear; sorted order; quoted strings. - self.assertEqual(co1_summ.extra, "a=1") - self.assertEqual( - co2_summ.extra, "a=7, text='ok', text2='multi\\nline', z=77" - ) - - def test_array_attributes(self): - cube = self.cube - co1 = cube.coord("latitude") - co1.attributes.update(dict(a=1, array=np.array([1.2, 3]))) - co2 = co1.copy() - co2.attributes.update(dict(b=2, array=np.array([3.2, 1]))) - cube.add_aux_coord(co2, cube.coord_dims(co1)) - rep = CubeSummary(cube) - co1_summ = rep.vector_sections["Dimension coordinates:"].contents[0] - co2_summ = rep.vector_sections["Auxiliary coordinates:"].contents[0] - self.assertEqual(co1_summ.extra, "array=array([1.2, 3. ])") - self.assertEqual(co2_summ.extra, "array=array([3.2, 1. ]), b=2") - - def test_attributes_subtle_differences(self): - cube = Cube([0]) - - # Add a pair that differ only in having a list instead of an array. - co1a = DimCoord( - [0], - long_name="co1_list_or_array", - attributes=dict(x=1, arr1=np.array(2), arr2=np.array([1, 2])), - ) - co1b = co1a.copy() - co1b.attributes.update(dict(arr2=[1, 2])) - for co in (co1a, co1b): - cube.add_aux_coord(co) - - # Add a pair that differ only in an attribute array dtype. - co2a = AuxCoord( - [0], - long_name="co2_dtype", - attributes=dict(x=1, arr1=np.array(2), arr2=np.array([3, 4])), - ) - co2b = co2a.copy() - co2b.attributes.update(dict(arr2=np.array([3.0, 4.0]))) - assert co2b != co2a - for co in (co2a, co2b): - cube.add_aux_coord(co) - - # Add a pair that differ only in an attribute array shape. - co3a = DimCoord( - [0], - long_name="co3_shape", - attributes=dict(x=1, arr1=np.array([5, 6]), arr2=np.array([3, 4])), - ) - co3b = co3a.copy() - co3b.attributes.update(dict(arr1=np.array([[5], [6]]))) - for co in (co3a, co3b): - cube.add_aux_coord(co) - - rep = CubeSummary(cube) - co_summs = rep.scalar_sections["Scalar coordinates:"].contents - co1a_summ, co1b_summ = co_summs[0:2] - self.assertEqual(co1a_summ.extra, "arr2=array([1, 2])") - self.assertEqual(co1b_summ.extra, "arr2=[1, 2]") - co2a_summ, co2b_summ = co_summs[2:4] - self.assertEqual(co2a_summ.extra, "arr2=array([3, 4])") - self.assertEqual(co2b_summ.extra, "arr2=array([3., 4.])") - co3a_summ, co3b_summ = co_summs[4:6] - self.assertEqual(co3a_summ.extra, "arr1=array([5, 6])") - self.assertEqual(co3b_summ.extra, "arr1=array([[5], [6]])") - - def test_unstructured_cube(self): - cube = sample_mesh_cube() - rep = CubeSummary(cube) - # Just check that coordinates appear in the expected sections - dim_section = rep.vector_sections["Dimension coordinates:"] - mesh_section = rep.vector_sections["Mesh coordinates:"] - aux_section = rep.vector_sections["Auxiliary coordinates:"] - self.assertEqual(len(dim_section.contents), 2) - self.assertEqual(len(mesh_section.contents), 2) - self.assertEqual(len(aux_section.contents), 1) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/test_Future.py b/lib/iris/tests/unit/test_Future.py deleted file mode 100644 index f0c161b0c4..0000000000 --- a/lib/iris/tests/unit/test_Future.py +++ /dev/null @@ -1,126 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.Future` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import warnings - -from iris import Future -import iris._deprecation - - -def patched_future(value=False, deprecated=False, error=False): - class LocalFuture(Future): - # Modified Future class, with controlled deprecation options. - # - # NOTE: it is necessary to subclass this in order to modify the - # 'deprecated_options' property, because we don't want to modify the - # class variable of the actual Future class ! - deprecated_options = {} - if deprecated: - if error: - deprecated_options["example_future_flag"] = "error" - else: - deprecated_options["example_future_flag"] = "warning" - - future = LocalFuture() - future.__dict__["example_future_flag"] = value - return future - - -class Test___setattr__(tests.IrisTest): - def test_valid_setting(self): - future = patched_future() - new_value = not future.example_future_flag - with warnings.catch_warnings(): - warnings.simplefilter("error") # Check no warning emitted ! - future.example_future_flag = new_value - self.assertEqual(future.example_future_flag, new_value) - - def test_deprecated_warning(self): - future = patched_future(deprecated=True, error=False) - msg = "'Future' property 'example_future_flag' is deprecated" - with self.assertWarnsRegex(iris._deprecation.IrisDeprecation, msg): - future.example_future_flag = False - - def test_deprecated_error(self): - future = patched_future(deprecated=True, error=True) - exp_emsg = ( - "'Future' property 'example_future_flag' has been deprecated" - ) - with self.assertRaisesRegex(AttributeError, exp_emsg): - future.example_future_flag = False - - def test_invalid_attribute(self): - future = Future() - with self.assertRaises(AttributeError): - future.numberwang = 7 - - -class Test_context(tests.IrisTest): - def test_generic_no_args(self): - # While Future has no properties, it is necessary to patch Future in - # order for these tests to work. This test is not a precise emulation - # of the test it is replacing, but ought to cover most of the same - # behaviour while Future is empty. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - future = patched_future(value=False) - self.assertFalse(future.example_future_flag) - with future.context(): - self.assertFalse(future.example_future_flag) - future.example_future_flag = True - self.assertTrue(future.example_future_flag) - self.assertFalse(future.example_future_flag) - - def test_generic_with_arg(self): - # While Future has no properties, it is necessary to patch Future in - # order for these tests to work. This test is not a precise emulation - # of the test it is replacing, but ought to cover most of the same - # behaviour while Future is empty. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - future = patched_future(value=False) - self.assertFalse(future.example_future_flag) - self.assertFalse(future.example_future_flag) - with future.context(example_future_flag=True): - self.assertTrue(future.example_future_flag) - self.assertFalse(future.example_future_flag) - - def test_invalid_arg(self): - future = Future() - with self.assertRaises(AttributeError): - with future.context(this_does_not_exist=True): - # Don't need to do anything here... the context manager - # will (assuming it's working!) have already raised the - # exception we're looking for. - pass - - def test_generic_exception(self): - # Check that an interrupted context block restores the initial state. - class LocalTestException(Exception): - pass - - # While Future has no properties, it is necessary to patch Future in - # order for these tests to work. This test is not a precise emulation - # of the test it is replacing, but ought to cover most of the same - # behaviour while Future is empty. - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - future = patched_future(value=False) - try: - with future.context(example_future_flag=True): - raise LocalTestException() - except LocalTestException: - pass - self.assertEqual(future.example_future_flag, False) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/test_sample_data_path.py b/lib/iris/tests/unit/test_sample_data_path.py deleted file mode 100644 index ebf3b8108c..0000000000 --- a/lib/iris/tests/unit/test_sample_data_path.py +++ /dev/null @@ -1,91 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for :func:`iris.sample_data_path` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import os -import os.path -import shutil -import tempfile -from unittest import mock - -from iris import sample_data_path - - -def _temp_file(sample_dir): - # Return the full path to a new genuine file within our - # temporary directory. - sample_handle, sample_path = tempfile.mkstemp(dir=sample_dir) - os.close(sample_handle) - return sample_path - - -@tests.skip_sample_data -class TestIrisSampleData_path(tests.IrisTest): - def setUp(self): - self.sample_dir = tempfile.mkdtemp() - - def tearDown(self): - shutil.rmtree(self.sample_dir) - - def test_path(self): - with mock.patch("iris_sample_data.path", self.sample_dir): - import iris_sample_data - - self.assertEqual(iris_sample_data.path, self.sample_dir) - - def test_call(self): - sample_file = _temp_file(self.sample_dir) - with mock.patch("iris_sample_data.path", self.sample_dir): - result = sample_data_path(os.path.basename(sample_file)) - self.assertEqual(result, sample_file) - - def test_file_not_found(self): - with mock.patch("iris_sample_data.path", self.sample_dir): - with self.assertRaisesRegex( - ValueError, "Sample data .* not found" - ): - sample_data_path("foo") - - def test_file_absolute(self): - with mock.patch("iris_sample_data.path", self.sample_dir): - with self.assertRaisesRegex(ValueError, "Absolute path"): - sample_data_path(os.path.abspath("foo")) - - def test_glob_ok(self): - sample_path = _temp_file(self.sample_dir) - sample_glob = "?" + os.path.basename(sample_path)[1:] - with mock.patch("iris_sample_data.path", self.sample_dir): - result = sample_data_path(sample_glob) - self.assertEqual( - result, os.path.join(self.sample_dir, sample_glob) - ) - - def test_glob_not_found(self): - with mock.patch("iris_sample_data.path", self.sample_dir): - with self.assertRaisesRegex( - ValueError, "Sample data .* not found" - ): - sample_data_path("foo.*") - - def test_glob_absolute(self): - with mock.patch("iris_sample_data.path", self.sample_dir): - with self.assertRaisesRegex(ValueError, "Absolute path"): - sample_data_path(os.path.abspath("foo.*")) - - -class TestIrisSampleDataMissing(tests.IrisTest): - def test_no_iris_sample_data(self): - self.patch("iris.iris_sample_data", None) - with self.assertRaisesRegex(ImportError, "Please install"): - sample_data_path("") - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/tests/__init__.py b/lib/iris/tests/unit/tests/__init__.py deleted file mode 100644 index b8d27d34d3..0000000000 --- a/lib/iris/tests/unit/tests/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.tests` package.""" diff --git a/lib/iris/tests/unit/tests/stock/__init__.py b/lib/iris/tests/unit/tests/stock/__init__.py deleted file mode 100644 index f91390c2b3..0000000000 --- a/lib/iris/tests/unit/tests/stock/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.tests.stock` module.""" diff --git a/lib/iris/tests/unit/tests/stock/test_netcdf.py b/lib/iris/tests/unit/tests/stock/test_netcdf.py deleted file mode 100644 index 54d7b895cc..0000000000 --- a/lib/iris/tests/unit/tests/stock/test_netcdf.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.tests.stock.netcdf` module.""" - -import shutil -import tempfile - -from iris import load_cube -from iris.experimental.ugrid.load import PARSE_UGRID_ON_LOAD -from iris.experimental.ugrid.mesh import Mesh, MeshCoord - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests -from iris.tests.stock import netcdf - - -class XIOSFileMixin(tests.IrisTest): - @classmethod - def setUpClass(cls): - # Create a temp directory for transient test files. - cls.temp_dir = tempfile.mkdtemp() - - @classmethod - def tearDownClass(cls): - # Destroy the temp directory. - shutil.rmtree(cls.temp_dir) - - def create_synthetic_file(self, **create_kwargs): - # Should be overridden to invoke one of the create_file_ functions. - # E.g. - # return netcdf.create_file__xios_2d_face_half_levels( - # temp_file_dir=self.temp_dir, dataset_name="mesh", **create_kwargs - # ) - raise NotImplementedError - - def create_synthetic_test_cube(self, **create_kwargs): - file_path = self.create_synthetic_file(**create_kwargs) - with PARSE_UGRID_ON_LOAD.context(): - cube = load_cube(file_path) - return cube - - def check_cube(self, cube, shape, location, level): - # Basic checks on the primary data cube. - self.assertEqual(cube.var_name, "thing") - self.assertEqual(cube.long_name, "thingness") - self.assertEqual(cube.shape, shape) - - # Also a few checks on the attached mesh-related information. - last_dim = cube.ndim - 1 - self.assertIsInstance(cube.mesh, Mesh) - self.assertEqual(cube.mesh_dim(), last_dim) - self.assertEqual(cube.location, location) - for coord_name in ("longitude", "latitude"): - coord = cube.coord(coord_name) - self.assertIsInstance(coord, MeshCoord) - self.assertEqual(coord.shape, (shape[last_dim],)) - self.assertTrue(cube.mesh.var_name.endswith(f"{level}_levels")) - - -class Test_create_file__xios_2d_face_half_levels(XIOSFileMixin): - def create_synthetic_file(self, **create_kwargs): - return netcdf.create_file__xios_2d_face_half_levels( - temp_file_dir=self.temp_dir, dataset_name="mesh", **create_kwargs - ) - - def test_basic_load(self): - cube = self.create_synthetic_test_cube() - self.check_cube(cube, shape=(1, 866), location="face", level="half") - - def test_scale_mesh(self): - cube = self.create_synthetic_test_cube(n_faces=10) - self.check_cube(cube, shape=(1, 10), location="face", level="half") - - def test_scale_time(self): - cube = self.create_synthetic_test_cube(n_times=3) - self.check_cube(cube, shape=(3, 866), location="face", level="half") - - -class Test_create_file__xios_3d_face_half_levels(XIOSFileMixin): - def create_synthetic_file(self, **create_kwargs): - return netcdf.create_file__xios_3d_face_half_levels( - temp_file_dir=self.temp_dir, dataset_name="mesh", **create_kwargs - ) - - def test_basic_load(self): - cube = self.create_synthetic_test_cube() - self.check_cube( - cube, shape=(1, 38, 866), location="face", level="half" - ) - - def test_scale_mesh(self): - cube = self.create_synthetic_test_cube(n_faces=10) - self.check_cube(cube, shape=(1, 38, 10), location="face", level="half") - - def test_scale_time(self): - cube = self.create_synthetic_test_cube(n_times=3) - self.check_cube( - cube, shape=(3, 38, 866), location="face", level="half" - ) - - def test_scale_levels(self): - cube = self.create_synthetic_test_cube(n_levels=10) - self.check_cube( - cube, shape=(1, 10, 866), location="face", level="half" - ) - - -class Test_create_file__xios_3d_face_full_levels(XIOSFileMixin): - def create_synthetic_file(self, **create_kwargs): - return netcdf.create_file__xios_3d_face_full_levels( - temp_file_dir=self.temp_dir, dataset_name="mesh", **create_kwargs - ) - - def test_basic_load(self): - cube = self.create_synthetic_test_cube() - self.check_cube( - cube, shape=(1, 39, 866), location="face", level="full" - ) - - def test_scale_mesh(self): - cube = self.create_synthetic_test_cube(n_faces=10) - self.check_cube(cube, shape=(1, 39, 10), location="face", level="full") - - def test_scale_time(self): - cube = self.create_synthetic_test_cube(n_times=3) - self.check_cube( - cube, shape=(3, 39, 866), location="face", level="full" - ) - - def test_scale_levels(self): - cube = self.create_synthetic_test_cube(n_levels=10) - self.check_cube( - cube, shape=(1, 10, 866), location="face", level="full" - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/tests/test_IrisTest.py b/lib/iris/tests/unit/tests/test_IrisTest.py deleted file mode 100644 index 10de2a7760..0000000000 --- a/lib/iris/tests/unit/tests/test_IrisTest.py +++ /dev/null @@ -1,129 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.tests.IrisTest` class.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from abc import ABCMeta, abstractmethod - -import numpy as np - - -class _MaskedArrayEquality(metaclass=ABCMeta): - def setUp(self): - self.arr1 = np.ma.array([1, 2, 3, 4], mask=[False, True, True, False]) - self.arr2 = np.ma.array([1, 3, 2, 4], mask=[False, True, True, False]) - - @property - @abstractmethod - def _func(self): - pass - - def test_strict_comparison(self): - # Comparing both mask and data array completely. - with self.assertRaises(AssertionError): - self._func(self.arr1, self.arr2, strict=True) - - def test_non_strict_comparison(self): - # Checking masked array equality and all unmasked array data values. - self._func(self.arr1, self.arr2, strict=False) - - def test_default_strict_arg_comparison(self): - self._func(self.arr1, self.arr2) - - def test_nomask(self): - # Test that an assertion is raised when comparing a masked array - # containing masked and unmasked values with a masked array with - # 'nomask'. - arr1 = np.ma.array([1, 2, 3, 4]) - with self.assertRaises(AssertionError): - self._func(arr1, self.arr2, strict=False) - - def test_nomask_unmasked(self): - # Ensure that a masked array with 'nomask' can compare with an entirely - # unmasked array. - arr1 = np.ma.array([1, 2, 3, 4]) - arr2 = np.ma.array([1, 2, 3, 4], mask=False) - self._func(arr1, arr2, strict=False) - - def test_different_mask_strict(self): - # Differing masks, equal data - arr2 = self.arr1.copy() - arr2[0] = np.ma.masked - with self.assertRaises(AssertionError): - self._func(self.arr1, arr2, strict=True) - - def test_different_mask_nonstrict(self): - # Differing masks, equal data - arr2 = self.arr1.copy() - arr2[0] = np.ma.masked - with self.assertRaises(AssertionError): - self._func(self.arr1, arr2, strict=False) - - -class Test_assertMaskedArrayEqual(_MaskedArrayEquality, tests.IrisTest): - @property - def _func(self): - return self.assertMaskedArrayEqual - - -class Test_assertMaskedArrayEqual__Nonmaasked(tests.IrisTest): - def test_nonmasked_same(self): - # Masked test can be used on non-masked arrays. - arr1 = np.array([1, 2]) - self.assertMaskedArrayEqual(arr1, arr1) - - def test_masked_nonmasked_same(self): - # Masked test can be used between masked + non-masked arrays, and will - # consider them equal, when mask=None. - arr1 = np.ma.masked_array([1, 2]) - arr2 = np.array([1, 2]) - self.assertMaskedArrayEqual(arr1, arr2) - - def test_masked_nonmasked_different(self): - arr1 = np.ma.masked_array([1, 2]) - arr2 = np.array([1, 3]) - with self.assertRaisesRegex(AssertionError, "Arrays are not equal"): - self.assertMaskedArrayEqual(arr1, arr2) - - def test_nonmasked_masked_same(self): - # Masked test can be used between masked + non-masked arrays, and will - # consider them equal, when mask=None. - arr1 = np.array([1, 2]) - arr2 = np.ma.masked_array([1, 2]) - self.assertMaskedArrayEqual(arr1, arr2) - - def test_masked_nonmasked_same_falsemask(self): - # Masked test can be used between masked + non-masked arrays, and will - # consider them equal, when mask=False. - arr1 = np.ma.masked_array([1, 2], mask=False) - arr2 = np.array([1, 2]) - self.assertMaskedArrayEqual(arr1, arr2) - - def test_masked_nonmasked_same_emptymask(self): - # Masked test can be used between masked + non-masked arrays, and will - # consider them equal, when mask=zeros. - arr1 = np.ma.masked_array([1, 2], mask=[False, False]) - arr2 = np.array([1, 2]) - self.assertMaskedArrayEqual(arr1, arr2) - - -class Test_assertMaskedArrayAlmostEqual(_MaskedArrayEquality, tests.IrisTest): - @property - def _func(self): - return self.assertMaskedArrayAlmostEqual - - def test_decimal(self): - arr1, arr2 = np.ma.array([100.0]), np.ma.array([100.003]) - self.assertMaskedArrayAlmostEqual(arr1, arr2, decimal=2) - with self.assertRaises(AssertionError): - self.assertMaskedArrayAlmostEqual(arr1, arr2, decimal=3) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/time/__init__.py b/lib/iris/tests/unit/time/__init__.py deleted file mode 100644 index 3483b92e62..0000000000 --- a/lib/iris/tests/unit/time/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.time` module.""" diff --git a/lib/iris/tests/unit/time/test_PartialDateTime.py b/lib/iris/tests/unit/time/test_PartialDateTime.py deleted file mode 100644 index cfffafea2c..0000000000 --- a/lib/iris/tests/unit/time/test_PartialDateTime.py +++ /dev/null @@ -1,278 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the `iris.time.PartialDateTime` class.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import datetime -import operator -from unittest import mock - -import cftime - -from iris.time import PartialDateTime - - -class Test___init__(tests.IrisTest): - def test_positional(self): - # Test that we can define PartialDateTimes with positional arguments. - pd = PartialDateTime(1066, None, 10) - self.assertEqual(pd.year, 1066) - self.assertEqual(pd.month, None) - self.assertEqual(pd.day, 10) - - def test_keyword_args(self): - # Test that we can define PartialDateTimes with keyword arguments. - pd = PartialDateTime(microsecond=10) - self.assertEqual(pd.year, None) - self.assertEqual(pd.microsecond, 10) - - -class Test___repr__(tests.IrisTest): - def test_full(self): - pd = PartialDateTime(*list(range(7))) - result = repr(pd) - self.assertEqual( - result, - "PartialDateTime(year=0, month=1, day=2," - " hour=3, minute=4, second=5," - " microsecond=6)", - ) - - def test_partial(self): - pd = PartialDateTime(month=2, day=30) - result = repr(pd) - self.assertEqual(result, "PartialDateTime(month=2, day=30)") - - def test_empty(self): - pd = PartialDateTime() - result = repr(pd) - self.assertEqual(result, "PartialDateTime()") - - -class Test_timetuple(tests.IrisTest): - def test_exists(self): - # Check that the PartialDateTime class implements a timetuple (needed - # because of http://bugs.python.org/issue8005). - pd = PartialDateTime(*list(range(7))) - self.assertTrue(hasattr(pd, "timetuple")) - - -class _Test_operator: - def test_invalid_type(self): - pdt = PartialDateTime() - with self.assertRaises(TypeError): - self.op(pdt, 1) - - def _test(self, pdt, other, name): - expected = self.expected_value[name] - if isinstance(expected, type): - with self.assertRaises(expected): - result = self.op(pdt, other) - else: - result = self.op(pdt, other) - self.assertIs(result, expected) - - def _test_dt(self, pdt, name): - other = mock.Mock( - name="datetime", - spec=datetime.datetime, - year=2013, - month=3, - day=20, - second=2, - ) - self._test(pdt, other, name) - - def test_no_difference(self): - self._test_dt( - PartialDateTime(year=2013, month=3, day=20, second=2), - "no_difference", - ) - - def test_null(self): - self._test_dt(PartialDateTime(), "null") - - def test_item1_lo(self): - self._test_dt( - PartialDateTime(year=2011, month=3, second=2), "item1_lo" - ) - - def test_item1_hi(self): - self._test_dt(PartialDateTime(year=2015, month=3, day=24), "item1_hi") - - def test_item2_lo(self): - self._test_dt( - PartialDateTime(year=2013, month=1, second=2), "item2_lo" - ) - - def test_item2_hi(self): - self._test_dt(PartialDateTime(year=2013, month=5, day=24), "item2_hi") - - def test_item3_lo(self): - self._test_dt( - PartialDateTime(year=2013, month=3, second=1), "item3_lo" - ) - - def test_item3_hi(self): - self._test_dt( - PartialDateTime(year=2013, month=3, second=42), "item3_hi" - ) - - def test_mix_hi_lo(self): - self._test_dt(PartialDateTime(year=2015, month=1, day=24), "mix_hi_lo") - - def test_mix_lo_hi(self): - self._test_dt(PartialDateTime(year=2011, month=5, day=24), "mix_lo_hi") - - def _test_pdt(self, other, name): - pdt = PartialDateTime(year=2013, day=24) - self._test(pdt, other, name) - - def test_pdt_same(self): - self._test_pdt(PartialDateTime(year=2013, day=24), "pdt_same") - - def test_pdt_diff(self): - self._test_pdt(PartialDateTime(year=2013, day=25), "pdt_diff") - - def test_pdt_diff_fewer_fields(self): - self._test_pdt(PartialDateTime(year=2013), "pdt_diff_fewer") - - def test_pdt_diff_more_fields(self): - self._test_pdt( - PartialDateTime(year=2013, day=24, hour=12), "pdt_diff_more" - ) - - def test_pdt_diff_no_fields(self): - pdt1 = PartialDateTime() - pdt2 = PartialDateTime(month=3, day=24) - self._test(pdt1, pdt2, "pdt_empty") - - -def negate_expectations(expectations): - def negate(expected): - if not isinstance(expected, type): - expected = not expected - return expected - - return {name: negate(value) for name, value in expectations.items()} - - -EQ_EXPECTATIONS = { - "no_difference": True, - "item1_lo": False, - "item1_hi": False, - "item2_lo": False, - "item2_hi": False, - "item3_lo": False, - "item3_hi": False, - "mix_hi_lo": False, - "mix_lo_hi": False, - "null": True, - "pdt_same": True, - "pdt_diff": False, - "pdt_diff_fewer": False, - "pdt_diff_more": False, - "pdt_empty": False, -} - -GT_EXPECTATIONS = { - "no_difference": False, - "item1_lo": False, - "item1_hi": True, - "item2_lo": False, - "item2_hi": True, - "item3_lo": False, - "item3_hi": True, - "mix_hi_lo": True, - "mix_lo_hi": False, - "null": False, - "pdt_same": TypeError, - "pdt_diff": TypeError, - "pdt_diff_fewer": TypeError, - "pdt_diff_more": TypeError, - "pdt_empty": TypeError, -} - -LT_EXPECTATIONS = { - "no_difference": False, - "item1_lo": True, - "item1_hi": False, - "item2_lo": True, - "item2_hi": False, - "item3_lo": True, - "item3_hi": False, - "mix_hi_lo": False, - "mix_lo_hi": True, - "null": False, - "pdt_same": TypeError, - "pdt_diff": TypeError, - "pdt_diff_fewer": TypeError, - "pdt_diff_more": TypeError, - "pdt_empty": TypeError, -} - - -class Test___eq__(tests.IrisTest, _Test_operator): - def setUp(self): - self.op = operator.eq - self.expected_value = EQ_EXPECTATIONS - - def test_cftime_equal(self): - pdt = PartialDateTime(month=3, second=2) - other = cftime.datetime(year=2013, month=3, day=20, second=2) - self.assertTrue(pdt == other) - - def test_cftime_not_equal(self): - pdt = PartialDateTime(month=3, second=2) - other = cftime.datetime(year=2013, month=4, day=20, second=2) - self.assertFalse(pdt == other) - - -class Test___ne__(tests.IrisTest, _Test_operator): - def setUp(self): - self.op = operator.ne - self.expected_value = negate_expectations(EQ_EXPECTATIONS) - - -class Test___gt__(tests.IrisTest, _Test_operator): - def setUp(self): - self.op = operator.gt - self.expected_value = GT_EXPECTATIONS - - def test_cftime_greater(self): - pdt = PartialDateTime(month=3, microsecond=2) - other = cftime.datetime(year=2013, month=2, day=20, second=3) - self.assertTrue(pdt > other) - - def test_cftime_not_greater(self): - pdt = PartialDateTime(month=3, microsecond=2) - other = cftime.datetime(year=2013, month=3, day=20, second=3) - self.assertFalse(pdt > other) - - -class Test___le__(tests.IrisTest, _Test_operator): - def setUp(self): - self.op = operator.le - self.expected_value = negate_expectations(GT_EXPECTATIONS) - - -class Test___lt__(tests.IrisTest, _Test_operator): - def setUp(self): - self.op = operator.lt - self.expected_value = LT_EXPECTATIONS - - -class Test___ge__(tests.IrisTest, _Test_operator): - def setUp(self): - self.op = operator.ge - self.expected_value = negate_expectations(LT_EXPECTATIONS) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/__init__.py b/lib/iris/tests/unit/util/__init__.py deleted file mode 100644 index 9aed566a19..0000000000 --- a/lib/iris/tests/unit/util/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Unit tests for the :mod:`iris.util` module.""" diff --git a/lib/iris/tests/unit/util/test__coord_regular.py b/lib/iris/tests/unit/util/test__coord_regular.py deleted file mode 100644 index a5e9aca9ed..0000000000 --- a/lib/iris/tests/unit/util/test__coord_regular.py +++ /dev/null @@ -1,111 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test elements of :mod:`iris.util` that deal with checking coord regularity. -Specifically, this module tests the following functions: - - * :func:`iris.util.is_regular`, - * :func:`iris.util.regular_step`, and - * :func:`iris.util.points_step`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.coords import AuxCoord, DimCoord -from iris.exceptions import CoordinateMultiDimError, CoordinateNotRegularError -from iris.util import is_regular, points_step, regular_step - - -class Test_is_regular(tests.IrisTest): - def test_coord_with_regular_step(self): - coord = DimCoord(np.arange(5)) - result = is_regular(coord) - self.assertTrue(result) - - def test_coord_with_irregular_step(self): - # Check that a `CoordinateNotRegularError` is captured. - coord = AuxCoord(np.array([2, 5, 1, 4])) - result = is_regular(coord) - self.assertFalse(result) - - def test_scalar_coord(self): - # Check that a `ValueError` is captured. - coord = DimCoord(5) - result = is_regular(coord) - self.assertFalse(result) - - def test_coord_with_string_points(self): - # Check that a `TypeError` is captured. - coord = AuxCoord(["a", "b", "c"]) - result = is_regular(coord) - self.assertFalse(result) - - -class Test_regular_step(tests.IrisTest): - def test_basic(self): - dtype = np.float64 - points = np.arange(5, dtype=dtype) - coord = DimCoord(points) - expected = np.mean(np.diff(points)) - result = regular_step(coord) - self.assertEqual(expected, result) - self.assertEqual(result.dtype, dtype) - - def test_2d_coord(self): - coord = AuxCoord(np.arange(8).reshape(2, 4)) - exp_emsg = "Expected 1D coord" - with self.assertRaisesRegex(CoordinateMultiDimError, exp_emsg): - regular_step(coord) - - def test_scalar_coord(self): - coord = DimCoord(5) - exp_emsg = "non-scalar coord" - with self.assertRaisesRegex(ValueError, exp_emsg): - regular_step(coord) - - def test_coord_with_irregular_step(self): - name = "latitude" - coord = AuxCoord(np.array([2, 5, 1, 4]), standard_name=name) - exp_emsg = "{} is not regular".format(name) - with self.assertRaisesRegex(CoordinateNotRegularError, exp_emsg): - regular_step(coord) - - -class Test_points_step(tests.IrisTest): - def test_regular_points(self): - regular_points = np.arange(5) - exp_avdiff = np.mean(np.diff(regular_points)) - result_avdiff, result = points_step(regular_points) - self.assertEqual(exp_avdiff, result_avdiff) - self.assertTrue(result) - - def test_irregular_points(self): - irregular_points = np.array([2, 5, 1, 4]) - exp_avdiff = np.mean(np.diff(irregular_points)) - result_avdiff, result = points_step(irregular_points) - self.assertEqual(exp_avdiff, result_avdiff) - self.assertFalse(result) - - def test_single_point(self): - lone_point = np.array([4]) - result_avdiff, result = points_step(lone_point) - self.assertTrue(np.isnan(result_avdiff)) - self.assertTrue(result) - - def test_no_points(self): - no_points = np.array([]) - result_avdiff, result = points_step(no_points) - self.assertTrue(np.isnan(result_avdiff)) - self.assertTrue(result) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test__is_circular.py b/lib/iris/tests/unit/util/test__is_circular.py deleted file mode 100644 index e67eb38294..0000000000 --- a/lib/iris/tests/unit/util/test__is_circular.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util._is_circular`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.util import _is_circular - - -class Test(tests.IrisTest): - def test_simple(self): - data = np.arange(12) * 30 - self.assertTrue(_is_circular(data, 360)) - - def test_negative_diff(self): - data = (np.arange(96) * -3.749998) + 3.56249908e02 - self.assertTrue(_is_circular(data, 360)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test__mask_array.py b/lib/iris/tests/unit/util/test__mask_array.py deleted file mode 100644 index 91a5aca1b4..0000000000 --- a/lib/iris/tests/unit/util/test__mask_array.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util._mask_array""" - -import dask.array as da -import numpy as np -import numpy.ma as ma -import pytest - -import iris._lazy_data -from iris.tests import assert_masked_array_equal -from iris.util import _mask_array - -# Set up some arrays to use through the tests. -array_1d = np.arange(4) -masked_arr_1d = ma.array(np.arange(4), mask=[1, 0, 0, 1]) -array_2by3 = np.arange(6).reshape(2, 3) - -# Any masked points on the mask itself should be ignored. So result with mask_1d -# and masked_mask_1d should be the same. -mask_1d = np.array([0, 1, 0, 1]) -masked_mask_1d = ma.array([0, 1, 1, 1], mask=[0, 0, 1, 0]) - -# Expected output depends whether input array is masked or not. -expected1 = ma.array(array_1d, mask=mask_1d) -expected2 = ma.array(array_1d, mask=[1, 1, 0, 1]) -array_choices = [(array_1d, expected1), (masked_arr_1d, expected2)] - - -@pytest.mark.parametrize( - "mask", [mask_1d, masked_mask_1d], ids=["plain-mask", "masked-mask"] -) -@pytest.mark.parametrize("lazy_mask", [False, True], ids=["real", "lazy"]) -@pytest.mark.parametrize( - "array, expected", array_choices, ids=["plain-array", "masked-array"] -) -@pytest.mark.parametrize("lazy_array", [False, True], ids=["real", "lazy"]) -def test_1d_not_in_place(array, mask, expected, lazy_array, lazy_mask): - """ - Basic test for expected behaviour when working not in place with various - array types for input. - - """ - if lazy_array: - array = iris._lazy_data.as_lazy_data(array) - - if lazy_mask: - mask = iris._lazy_data.as_lazy_data(mask) - - result = _mask_array(array, mask) - assert result is not array - - if lazy_array or lazy_mask: - assert iris._lazy_data.is_lazy_data(result) - result = iris._lazy_data.as_concrete_data(result) - - assert_masked_array_equal(expected, result) - - -# 1D in place tests. - - -def test_plain_array_in_place(): - """ - Test we get an informative error when trying to add a mask to a plain numpy - array. - - """ - arr = array_1d - mask = None - with pytest.raises( - TypeError, match="Cannot apply a mask in-place to a plain numpy array." - ): - _mask_array(arr, mask, in_place=True) - - -def test_masked_array_lazy_mask_in_place(): - """ - Test we get an informative error when trying to apply a lazy mask in-place - to a non-lazy array. - - """ - arr = masked_arr_1d - mask = da.from_array([0, 1, 0, 1]) - with pytest.raises( - TypeError, match="Cannot apply lazy mask in-place to a non-lazy array." - ): - _mask_array(arr, mask, in_place=True) - - -@pytest.mark.parametrize( - "mask", [mask_1d, masked_mask_1d], ids=["plain-mask", "masked-mask"] -) -def test_real_masked_array_in_place(mask): - """ - Check expected behaviour for applying masks in-place to a masked array. - - """ - arr = masked_arr_1d.copy() - result = _mask_array(arr, mask, in_place=True) - assert_masked_array_equal(arr, expected2) - # Resolve uses returned value regardless of whether we're working in_place. - assert result is arr - - -def test_lazy_array_in_place(): - """ - Test that in place flag is ignored for lazy arrays, and result is the same - as the not in_place case. - - """ - arr = da.from_array(np.arange(4)) - mask = np.array([0, 1, 0, 1]) - expected_computed = ma.array(range(4), mask=[0, 1, 0, 1]) - # in_place is ignored for lazy array as this is handled by _math_op_common. - result = _mask_array(arr, mask, in_place=True) - assert iris._lazy_data.is_lazy_data(result) - assert_masked_array_equal(result.compute(), expected_computed) - assert result is not arr - - -# Broadcasting tests. - -IN_PLACE_PARAMETRIZE = pytest.mark.parametrize( - "in_place", [False, True], ids=["not-in-place", "in-place"] -) - - -@IN_PLACE_PARAMETRIZE -def test_trailing_mask(in_place): - array = ma.array(array_2by3.copy()) - mask = np.array([0, 1, 0]) - expected = ma.array(array_2by3, mask=[[0, 1, 0], [0, 1, 0]]) - result = _mask_array(array, mask, in_place=in_place) - assert_masked_array_equal(result, expected) - assert result is array if in_place else result is not array - - -@IN_PLACE_PARAMETRIZE -def test_leading_mask(in_place): - arr = ma.masked_array(array_2by3.copy(), mask=[[0, 0, 0], [0, 0, 1]]) - mask = np.array([1, 0]).reshape(2, 1) - expected = ma.array(arr.data, mask=[[1, 1, 1], [0, 0, 1]]) - result = _mask_array(arr, mask, in_place=in_place) - assert_masked_array_equal(result, expected) - assert result is arr if in_place else result is not arr - - -def test_lazy_trailing_mask(): - arr = da.ma.masked_array(array_2by3, mask=[[0, 1, 1], [0, 0, 0]]) - mask = np.array([0, 1, 0]) - expected_computed = ma.array(array_2by3, mask=[[0, 1, 1], [0, 1, 0]]) - result = _mask_array(arr, mask, in_place=False) - assert iris._lazy_data.is_lazy_data(result) - assert_masked_array_equal(result.compute(), expected_computed) - assert result is not arr - - -def test_lazy_leading_mask(): - arr = da.from_array(array_2by3) - mask = da.from_array([0, 1]).reshape(2, 1) - expected_computed = ma.array(array_2by3, mask=[[0, 0, 0], [1, 1, 1]]) - result = _mask_array(arr, mask, in_place=False) - assert iris._lazy_data.is_lazy_data(result) - assert_masked_array_equal(result.compute(), expected_computed) - assert result is not arr - - -if __name__ == "__main__": - pytest.main([__file__]) diff --git a/lib/iris/tests/unit/util/test__slice_data_with_keys.py b/lib/iris/tests/unit/util/test__slice_data_with_keys.py deleted file mode 100644 index 061a2f5b37..0000000000 --- a/lib/iris/tests/unit/util/test__slice_data_with_keys.py +++ /dev/null @@ -1,436 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.util._slice_data_with_keys`. - -Note: much of the functionality really belongs to the other routines, -:func:`iris.util._build_full_slice_given_keys`, and -:func:`column_slices_generator`. -However, it is relatively simple to test multiple aspects of all three here -in combination. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np - -from iris._lazy_data import as_concrete_data, as_lazy_data -from iris.util import _slice_data_with_keys - - -class DummyArray: - # A dummy array-like that records the keys of indexing calls. - def __init__(self, shape, _indexing_record_list=None): - self.shape = shape - self.ndim = len(shape) - if _indexing_record_list is None: - _indexing_record_list = [] - self._getitem_call_keys = _indexing_record_list - - def __getitem__(self, keys): - # Add the indexing keys to the call list. - self._getitem_call_keys.append(keys) - # Return a new object with the correct derived shape, and record its - # indexing operations in the same key list as this. - shape_array = np.zeros(self.shape) - shape_array = shape_array.__getitem__(keys) - new_shape = shape_array.shape - return DummyArray( - new_shape, _indexing_record_list=self._getitem_call_keys - ) - - -class Indexer: - # An object to make __getitem__ arglists from indexing operations. - def __getitem__(self, keys): - return keys - - -# An Indexer object for generating indexing keys in a nice visible way. -Index = Indexer() - - -class MixinIndexingTest: - def check(self, shape, keys, expect_call_keys=None, expect_map=None): - data = DummyArray(shape) - dim_map, _ = _slice_data_with_keys(data, keys) - if expect_call_keys is not None: - calls_got = data._getitem_call_keys - # Check that the indexing keys applied were the expected ones. - equal = len(calls_got) == len(expect_call_keys) - for act_call, expect_call in zip(calls_got, expect_call_keys): - equal &= len(act_call) == len(expect_call) - # A problem here is that in each call, some keys may be - # *arrays*, and arrays can't be compared in the "normal" - # way. So we must use np.all for comparison :-( - for act_key, expect_key in zip(act_call, expect_call): - equal &= np.asanyarray(act_key).dtype == np.asanyarray( - expect_key - ).dtype and np.all(act_key == expect_key) - errmsg = "Different key lists:\n{!s}\n!=\n{!s}\n" - - def showkeys(keys_list): - msg = "[\n " - msg += "\n ".join(str(x) for x in keys_list) - msg += "\n]" - return msg - - self.assertTrue( - equal, - errmsg.format(showkeys(calls_got), showkeys(expect_call_keys)), - ) - if expect_map is not None: - self.assertEqual(dim_map, expect_map) - - -class Test_indexing(MixinIndexingTest, tests.IrisTest): - # Check the indexing operations performed for various requested keys. - - def test_0d_nokeys(self): - # Performs *no* underlying indexing operation. - self.check((), Index[()], []) - - def test_1d_int(self): - self.check((4,), Index[2], [(2,)]) - - def test_1d_all(self): - self.check((3,), Index[:], [(slice(None),)]) - - def test_1d_tuple(self): - # The call makes tuples into 1-D arrays, and a trailing Ellipsis is - # added (for the 1-D case only). - self.check( - (3,), Index[((2, 0, 1),)], [(np.array([2, 0, 1]), Ellipsis)] - ) - - def test_fail_1d_2keys(self): - msg = "More slices .* than dimensions" - with self.assertRaisesRegex(IndexError, msg): - self.check((3,), Index[1, 2]) - - def test_fail_empty_slice(self): - msg = "Cannot index with zero length slice" - with self.assertRaisesRegex(IndexError, msg): - self.check((3,), Index[1:1]) - - def test_2d_tuple(self): - # Like the above, but there is an extra no-op at the start and no - # trailing Ellipsis is generated. - self.check( - (3, 2), - Index[((2, 0, 1),)], - [(slice(None), slice(None)), (np.array([2, 0, 1]), slice(None))], - ) - - def test_2d_two_tuples(self): - # Could be treated as fancy indexing, but must not be ! - # Two separate 2-D indexing operations. - self.check( - (3, 2), - Index[(2, 0, 1, 1), (0, 1, 0, 1)], - [ - (np.array([2, 0, 1, 1]), slice(None)), - (slice(None), np.array([0, 1, 0, 1])), - ], - ) - - def test_2d_tuple_and_value(self): - # The two keys are applied in separate operations, and in the reverse - # order (?) : The second op is then slicing a 1-D array, not 2-D. - self.check( - (3, 5), - Index[(2, 0, 1), 3], - [(slice(None), 3), (np.array([2, 0, 1]), Ellipsis)], - ) - - def test_2d_single_int(self): - self.check((3, 4), Index[2], [(2, slice(None))]) - - def test_2d_multiple_int(self): - self.check((3, 4), Index[2, 1:3], [(2, slice(1, 3))]) - - def test_3d_1int(self): - self.check((3, 4, 5), Index[2], [(2, slice(None), slice(None))]) - - def test_3d_2int(self): - self.check((3, 4, 5), Index[2, 3], [(2, 3, slice(None))]) - - def test_3d_tuple_and_value(self): - # The two keys are applied in separate operations, and in the reverse - # order (?) : The second op is slicing a 2-D array, not 3-D. - self.check( - (3, 5, 7), - Index[(2, 0, 1), 4], - [ - (slice(None), 4, slice(None)), - (np.array([2, 0, 1]), slice(None)), - ], - ) - - def test_3d_ellipsis_last(self): - self.check((3, 4, 5), Index[2, ...], [(2, slice(None), slice(None))]) - - def test_3d_ellipsis_first_1int(self): - self.check((3, 4, 5), Index[..., 2], [(slice(None), slice(None), 2)]) - - def test_3d_ellipsis_first_2int(self): - self.check((3, 4, 5), Index[..., 2, 3], [(slice(None), 2, 3)]) - - def test_3d_multiple_tuples(self): - # Where there are TWO or more tuple keys, this could be misinterpreted - # as 'fancy' indexing : It should resolve into multiple calls. - self.check( - (3, 4, 5), - Index[(1, 2, 1), :, (2, 2, 3)], - [ - (slice(None), slice(None), slice(None)), - (np.array([1, 2, 1]), slice(None), slice(None)), - (slice(None), slice(None), np.array([2, 2, 3])), - ], - ) - # NOTE: there seem to be an extra initial [:, :, :]. - # That's just what it does at present. - - -class Test_dimensions_mapping(MixinIndexingTest, tests.IrisTest): - # Check the dimensions map returned for various requested keys. - - def test_1d_nochange(self): - self.check((3,), Index[1:2], expect_map={None: None, 0: 0}) - - def test_1d_1int_losedim0(self): - self.check((3,), Index[1], expect_map={None: None, 0: None}) - - def test_1d_tuple_nochange(self): - # A selection index leaves the dimension intact. - self.check((3,), Index[((1, 0, 1, 2),)], expect_map={None: None, 0: 0}) - - def test_1d_1tuple_nochange(self): - # A selection index with only one value in it *still* leaves the - # dimension intact. - self.check((3,), Index[((2,),)], expect_map={None: None, 0: 0}) - - def test_1d_slice_nochange(self): - # A slice leaves the dimension intact. - self.check((3,), Index[1:7], expect_map={None: None, 0: 0}) - - def test_2d_nochange(self): - self.check((3, 4), Index[:, :], expect_map={None: None, 0: 0, 1: 1}) - - def test_2d_losedim0(self): - self.check((3, 4), Index[1, :], expect_map={None: None, 0: None, 1: 0}) - - def test_2d_losedim1(self): - self.check( - (3, 4), Index[1:4, 2], expect_map={None: None, 0: 0, 1: None} - ) - - def test_2d_loseboth(self): - # Two indices give scalar result. - self.check( - (3, 4), Index[1, 2], expect_map={None: None, 0: None, 1: None} - ) - - def test_3d_losedim1(self): - # Cutting out the middle dim. - self.check( - (3, 4, 2), - Index[:, 2], - expect_map={None: None, 0: 0, 1: None, 2: 1}, - ) - - -class TestResults(tests.IrisTest): - # Integration-style test, exercising (mostly) the same cases as above, - # but checking actual results, for both real and lazy array inputs. - - def check(self, real_data, keys, expect_result, expect_map): - real_data = np.array(real_data) - lazy_data = as_lazy_data(real_data, real_data.shape) - real_dim_map, real_result = _slice_data_with_keys(real_data, keys) - lazy_dim_map, lazy_result = _slice_data_with_keys(lazy_data, keys) - lazy_result = as_concrete_data(lazy_result) - self.assertArrayEqual(real_result, expect_result) - self.assertArrayEqual(lazy_result, expect_result) - self.assertEqual(real_dim_map, expect_map) - self.assertEqual(lazy_dim_map, expect_map) - - def test_1d_int(self): - self.check([1, 2, 3, 4], Index[2], [3], {None: None, 0: None}) - - def test_1d_all(self): - self.check([1, 2, 3], Index[:], [1, 2, 3], {None: None, 0: 0}) - - def test_1d_tuple(self): - self.check( - [1, 2, 3], Index[((2, 0, 1, 0),)], [3, 1, 2, 1], {None: None, 0: 0} - ) - - def test_fail_1d_2keys(self): - msg = "More slices .* than dimensions" - with self.assertRaisesRegex(IndexError, msg): - self.check([1, 2, 3], Index[1, 2], None, None) - - def test_fail_empty_slice(self): - msg = "Cannot index with zero length slice" - with self.assertRaisesRegex(IndexError, msg): - self.check([1, 2, 3], Index[1:1], None, None) - - def test_2d_tuple(self): - self.check( - [[11, 12], [21, 22], [31, 32]], - Index[((2, 0, 1),)], - [[31, 32], [11, 12], [21, 22]], - {None: None, 0: 0, 1: 1}, - ) - - def test_2d_two_tuples(self): - # Could be treated as fancy indexing, but must not be ! - # Two separate 2-D indexing operations. - self.check( - [[11, 12, 13], [21, 22, 23], [31, 32, 33]], - Index[(2, 0), (0, 1, 0, 1)], - [[31, 32, 31, 32], [11, 12, 11, 12]], - {None: None, 0: 0, 1: 1}, - ) - - def test_2d_tuple_and_value(self): - # The two keys are applied in separate operations, and in the reverse - # order (?) : The second op is then slicing a 1-D array, not 2-D. - self.check( - [[11, 12, 13, 14], [21, 22, 23, 24], [31, 32, 33, 34]], - Index[(2, 0, 1), 3], - [34, 14, 24], - {None: None, 0: 0, 1: None}, - ) - - def test_2d_single_int(self): - self.check( - [[11, 12, 13], [21, 22, 23], [31, 32, 33]], - Index[1], - [21, 22, 23], - {None: None, 0: None, 1: 0}, - ) - - def test_2d_int_slice(self): - self.check( - [[11, 12, 13], [21, 22, 23], [31, 32, 33]], - Index[2, 1:3], - [32, 33], - {None: None, 0: None, 1: 0}, - ) - - def test_3d_1int(self): - self.check( - [ - [[111, 112, 113], [121, 122, 123]], - [[211, 212, 213], [221, 222, 223]], - [[311, 312, 313], [321, 322, 323]], - ], - Index[1], - [[211, 212, 213], [221, 222, 223]], - {None: None, 0: None, 1: 0, 2: 1}, - ) - - def test_3d_2int(self): - self.check( - [ - [[111, 112, 113], [121, 122, 123], [131, 132, 133]], - [[211, 212, 213], [221, 222, 223], [231, 232, 233]], - ], - Index[1, 2], - [231, 232, 233], - {None: None, 0: None, 1: None, 2: 0}, - ) - - def test_3d_tuple_and_value(self): - # The two keys are applied in separate operations, and in the reverse - # order (?) : The second op is slicing a 2-D array, not 3-D. - self.check( - [ - [[111, 112, 113, 114], [121, 122, 123, 124]], - [[211, 212, 213, 214], [221, 222, 223, 224]], - [[311, 312, 313, 314], [321, 322, 323, 324]], - ], - Index[(2, 0, 1), 1], - [[321, 322, 323, 324], [121, 122, 123, 124], [221, 222, 223, 224]], - {None: None, 0: 0, 1: None, 2: 1}, - ) - - def test_3d_ellipsis_last(self): - self.check( - [ - [[111, 112, 113], [121, 122, 123]], - [[211, 212, 213], [221, 222, 223]], - [[311, 312, 313], [321, 322, 323]], - ], - Index[2, ...], - [[311, 312, 313], [321, 322, 323]], - {None: None, 0: None, 1: 0, 2: 1}, - ) - - def test_3d_ellipsis_first_1int(self): - self.check( - [ - [[111, 112, 113, 114], [121, 122, 123, 124]], - [[211, 212, 213, 214], [221, 222, 223, 224]], - [[311, 312, 313, 314], [321, 322, 323, 324]], - ], - Index[..., 2], - [[113, 123], [213, 223], [313, 323]], - {None: None, 0: 0, 1: 1, 2: None}, - ) - - def test_3d_ellipsis_mid_1int(self): - self.check( - [ - [[111, 112, 113], [121, 122, 123]], - [[211, 212, 213], [221, 222, 223]], - [[311, 312, 313], [321, 322, 323]], - ], - Index[..., 1, ...], - [[121, 122, 123], [221, 222, 223], [321, 322, 323]], - {None: None, 0: 0, 1: None, 2: 1}, - ) - - def test_3d_ellipsis_first_2int(self): - self.check( - [ - [[111, 112, 113], [121, 122, 123]], - [[211, 212, 213], [221, 222, 223]], - [[311, 312, 313], [321, 322, 323]], - ], - Index[..., 1, 2], - [123, 223, 323], - {None: None, 0: 0, 1: None, 2: None}, - ) - - def test_3d_multiple_tuples(self): - # Where there are TWO or more tuple keys, this could be misinterpreted - # as 'fancy' indexing : It should resolve into multiple calls. - self.check( - [ - [[111, 112, 113, 114], [121, 122, 123, 124]], - [[211, 212, 213, 214], [221, 222, 223, 224]], - [[311, 312, 313, 314], [321, 322, 323, 324]], - ], - Index[(1, 2, 1), :, (2, 2, 3)], - [ - [[213, 213, 214], [223, 223, 224]], - [[313, 313, 314], [323, 323, 324]], - [[213, 213, 214], [223, 223, 224]], - ], - {None: None, 0: 0, 1: 1, 2: 2}, - ) - # NOTE: there seem to be an extra initial [:, :, :]. - # That's just what it does at present. - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_array_equal.py b/lib/iris/tests/unit/util/test_array_equal.py deleted file mode 100644 index 77631907a1..0000000000 --- a/lib/iris/tests/unit/util/test_array_equal.py +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.array_equal`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris.util import array_equal - - -class Test(tests.IrisTest): - def test_0d(self): - array_a = np.array(23) - array_b = np.array(23) - array_c = np.array(7) - self.assertTrue(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_a, array_c)) - - def test_0d_and_scalar(self): - array_a = np.array(23) - self.assertTrue(array_equal(array_a, 23)) - self.assertFalse(array_equal(array_a, 45)) - - def test_1d_and_sequences(self): - for sequence_type in (list, tuple): - seq_a = sequence_type([1, 2, 3]) - array_a = np.array(seq_a) - self.assertTrue(array_equal(array_a, seq_a)) - self.assertFalse(array_equal(array_a, seq_a[:-1])) - array_a[1] = 45 - self.assertFalse(array_equal(array_a, seq_a)) - - def test_nd(self): - array_a = np.array(np.arange(24).reshape(2, 3, 4)) - array_b = np.array(np.arange(24).reshape(2, 3, 4)) - array_c = np.array(np.arange(24).reshape(2, 3, 4)) - array_c[0, 1, 2] = 100 - self.assertTrue(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_a, array_c)) - - def test_masked_is_ignored(self): - array_a = ma.masked_array([1, 2, 3], mask=[1, 0, 1]) - array_b = ma.masked_array([2, 2, 2], mask=[1, 0, 1]) - self.assertFalse(array_equal(array_a, array_b)) - - def test_fully_masked_arrays(self): - array_a = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True) - array_b = ma.masked_array(np.arange(24).reshape(2, 3, 4), mask=True) - self.assertTrue(array_equal(array_a, array_b)) - - def test_fully_masked_0d_arrays(self): - array_a = ma.masked_array(3, mask=True) - array_b = ma.masked_array(3, mask=True) - self.assertTrue(array_equal(array_a, array_b)) - - def test_fully_masked_string_arrays(self): - array_a = ma.masked_array(["a", "b", "c"], mask=True) - array_b = ma.masked_array(["a", "b", "c"], mask=[1, 1, 1]) - self.assertTrue(array_equal(array_a, array_b)) - - def test_partially_masked_string_arrays(self): - array_a = ma.masked_array(["a", "b", "c"], mask=[1, 0, 1]) - array_b = ma.masked_array(["a", "b", "c"], mask=[1, 0, 1]) - self.assertTrue(array_equal(array_a, array_b)) - - def test_string_arrays_equal(self): - array_a = np.array(["abc", "def", "efg"]) - array_b = np.array(["abc", "def", "efg"]) - self.assertTrue(array_equal(array_a, array_b)) - - def test_string_arrays_different_contents(self): - array_a = np.array(["abc", "def", "efg"]) - array_b = np.array(["abc", "de", "efg"]) - self.assertFalse(array_equal(array_a, array_b)) - - def test_string_arrays_subset(self): - array_a = np.array(["abc", "def", "efg"]) - array_b = np.array(["abc", "def"]) - self.assertFalse(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_b, array_a)) - - def test_string_arrays_unequal_dimensionality(self): - array_a = np.array("abc") - array_b = np.array(["abc"]) - array_c = np.array([["abc"]]) - self.assertFalse(array_equal(array_a, array_b)) - self.assertFalse(array_equal(array_b, array_a)) - self.assertFalse(array_equal(array_a, array_c)) - self.assertFalse(array_equal(array_b, array_c)) - - def test_string_arrays_0d_and_scalar(self): - array_a = np.array("foobar") - self.assertTrue(array_equal(array_a, "foobar")) - self.assertFalse(array_equal(array_a, "foo")) - self.assertFalse(array_equal(array_a, "foobar.")) - - def test_nan_equality_nan_ne_nan(self): - array = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) - self.assertFalse(array_equal(array, array)) - - def test_nan_equality_nan_naneq_nan(self): - array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) - array_b = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) - self.assertTrue(array_equal(array_a, array_b, withnans=True)) - - def test_nan_equality_nan_nanne_a(self): - array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) - array_b = np.array([1.0, np.nan, 2.0, 0.0, 3.0]) - self.assertFalse(array_equal(array_a, array_b, withnans=True)) - - def test_nan_equality_a_nanne_b(self): - array_a = np.array([1.0, np.nan, 2.0, np.nan, 3.0]) - array_b = np.array([1.0, np.nan, 2.0, np.nan, 4.0]) - self.assertFalse(array_equal(array_a, array_b, withnans=True)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_broadcast_to_shape.py b/lib/iris/tests/unit/util/test_broadcast_to_shape.py deleted file mode 100644 index 36f00fa53f..0000000000 --- a/lib/iris/tests/unit/util/test_broadcast_to_shape.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.broadcast_to_shape`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris.util import broadcast_to_shape - - -class Test_broadcast_to_shape(tests.IrisTest): - def test_same_shape(self): - # broadcast to current shape should result in no change - a = np.random.random([2, 3]) - b = broadcast_to_shape(a, a.shape, (0, 1)) - self.assertArrayEqual(b, a) - - def test_added_dimensions(self): - # adding two dimensions, on at the front and one in the middle of - # the existing dimensions - a = np.random.random([2, 3]) - b = broadcast_to_shape(a, (5, 2, 4, 3), (1, 3)) - for i in range(5): - for j in range(4): - self.assertArrayEqual(b[i, :, j, :], a) - - def test_added_dimensions_transpose(self): - # adding dimensions and having the dimensions of the input - # transposed - a = np.random.random([2, 3]) - b = broadcast_to_shape(a, (5, 3, 4, 2), (3, 1)) - for i in range(5): - for j in range(4): - self.assertArrayEqual(b[i, :, j, :].T, a) - - def test_masked(self): - # masked arrays are also accepted - a = np.random.random([2, 3]) - m = ma.array(a, mask=[[0, 1, 0], [0, 1, 1]]) - b = broadcast_to_shape(m, (5, 3, 4, 2), (3, 1)) - for i in range(5): - for j in range(4): - self.assertMaskedArrayEqual(b[i, :, j, :].T, m) - - def test_masked_degenerate(self): - # masked arrays can have degenerate masks too - a = np.random.random([2, 3]) - m = ma.array(a) - b = broadcast_to_shape(m, (5, 3, 4, 2), (3, 1)) - for i in range(5): - for j in range(4): - self.assertMaskedArrayEqual(b[i, :, j, :].T, m) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_column_slices_generator.py b/lib/iris/tests/unit/util/test_column_slices_generator.py deleted file mode 100644 index 899c6b98ba..0000000000 --- a/lib/iris/tests/unit/util/test_column_slices_generator.py +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.column_slices_generator`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.util import column_slices_generator - - -class Test_int_types(tests.IrisTest): - def _test(self, key): - full_slice = (key,) - ndims = 1 - mapping, iterable = column_slices_generator(full_slice, ndims) - self.assertEqual(mapping, {0: None, None: None}) - self.assertEqual(list(iterable), [(0,)]) - - def test_int(self): - self._test(0) - - def test_int_32(self): - self._test(np.int32(0)) - - def test_int_64(self): - self._test(np.int64(0)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py b/lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py deleted file mode 100644 index ec8f9904f1..0000000000 --- a/lib/iris/tests/unit/util/test_demote_dim_coord_to_aux_coord.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.demote_dim_coord_to_aux_coord`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest - -import iris -import iris.tests.stock as stock -from iris.util import demote_dim_coord_to_aux_coord - - -class Test(tests.IrisTest): - def test_argument_is_basestring(self): - cube_a = stock.simple_3d() - cube_b = cube_a.copy() - demote_dim_coord_to_aux_coord(cube_b, cube_b.coord("wibble")) - self.assertEqual( - cube_b.dim_coords, - (cube_a.coord("latitude"), cube_a.coord("longitude")), - ) - - @tests.skip_data - def test_argument_is_coord_instance(self): - cube_a = stock.realistic_4d() - cube_b = cube_a.copy() - coord = cube_b.coord("model_level_number").copy() - demote_dim_coord_to_aux_coord(cube_b, coord) - self.assertEqual( - cube_b.dim_coords, - ( - cube_a.coord("time"), - cube_a.coord("grid_latitude"), - cube_a.coord("grid_longitude"), - ), - ) - - def test_old_dim_coord_is_now_aux_coord(self): - cube_a = stock.hybrid_height() - cube_b = cube_a.copy() - demote_dim_coord_to_aux_coord(cube_b, "level_height") - self.assertTrue(cube_a.coord("level_height") in cube_b.aux_coords) - - def test_coord_of_that_name_does_not_exist(self): - cube_a = stock.simple_2d_w_multidim_and_scalars() - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): - demote_dim_coord_to_aux_coord(cube_a, "wibble") - - def test_coord_does_not_exist(self): - cube_a = stock.simple_2d_w_multidim_and_scalars() - cube_b = cube_a.copy() - coord = cube_b.coord("dim1").copy() - coord.rename("new") - demote_dim_coord_to_aux_coord(cube_b, coord) - self.assertEqual(cube_a, cube_b) - - def test_argument_is_wrong_type(self): - cube_a = stock.simple_1d() - with self.assertRaises(TypeError): - demote_dim_coord_to_aux_coord(cube_a, 0.0) - - def test_trying_to_demote_a_scalar_coord(self): - cube_a = stock.simple_2d_w_multidim_and_scalars() - cube_b = cube_a.copy() - demote_dim_coord_to_aux_coord(cube_b, "an_other") - self.assertEqual(cube_a, cube_b) - - -if __name__ == "__main__": - unittest.main() diff --git a/lib/iris/tests/unit/util/test_describe_diff.py b/lib/iris/tests/unit/util/test_describe_diff.py deleted file mode 100644 index 0bb13cab94..0000000000 --- a/lib/iris/tests/unit/util/test_describe_diff.py +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.describe_diff`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -from io import StringIO - -import numpy as np - -import iris.cube -from iris.util import describe_diff - - -class Test(iris.tests.IrisTest): - def setUp(self): - self.cube_a = iris.cube.Cube([]) - self.cube_b = self.cube_a.copy() - - def _compare_result(self, cube_a, cube_b): - result_sio = StringIO() - describe_diff(cube_a, cube_b, output_file=result_sio) - return result_sio.getvalue() - - def test_noncommon_array_attributes(self): - # test non-common array attribute - self.cube_a.attributes["test_array"] = np.array([1, 2, 3]) - return_str = self._compare_result(self.cube_a, self.cube_b) - self.assertString(return_str, ["compatible_cubes.str.txt"]) - - def test_same_array_attributes(self): - # test matching array attribute - self.cube_a.attributes["test_array"] = np.array([1, 2, 3]) - self.cube_b.attributes["test_array"] = np.array([1, 2, 3]) - return_str = self._compare_result(self.cube_a, self.cube_b) - self.assertString(return_str, ["compatible_cubes.str.txt"]) - - def test_different_array_attributes(self): - # test non-matching array attribute - self.cube_a.attributes["test_array"] = np.array([1, 2, 3]) - self.cube_b.attributes["test_array"] = np.array([1, 7, 3]) - return_str = self._compare_result(self.cube_a, self.cube_b) - self.assertString( - return_str, - [ - "unit", - "util", - "describe_diff", - "incompatible_array_attrs.str.txt", - ], - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_equalise_attributes.py b/lib/iris/tests/unit/util/test_equalise_attributes.py deleted file mode 100644 index 13aa1e2af4..0000000000 --- a/lib/iris/tests/unit/util/test_equalise_attributes.py +++ /dev/null @@ -1,157 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Unit tests for the :func:`iris.util.equalise_attributes` function. - -""" - -# import iris tests first so that some things can be initialised -# before importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.cube import Cube -import iris.tests.stock -from iris.util import equalise_attributes - - -class TestEqualiseAttributes(tests.IrisTest): - def setUp(self): - empty = Cube([]) - - self.cube_no_attrs = empty.copy() - - self.cube_a1 = empty.copy() - self.cube_a1.attributes.update({"a": 1}) - - self.cube_a2 = empty.copy() - self.cube_a2.attributes.update({"a": 2}) - - self.cube_a1b5 = empty.copy() - self.cube_a1b5.attributes.update({"a": 1, "b": 5}) - - self.cube_a1b6 = empty.copy() - self.cube_a1b6.attributes.update({"a": 1, "b": 6}) - - self.cube_a2b6 = empty.copy() - self.cube_a2b6.attributes.update({"a": 2, "b": 6}) - - self.cube_b5 = empty.copy() - self.cube_b5.attributes.update({"b": 5}) - - # Array attribute values - v1 = np.array([11, 12, 13]) - v2 = np.array([11, 9999, 13]) - self.v1 = v1 - self.v2 = v2 - - self.cube_a1b5v1 = empty.copy() - self.cube_a1b5v1.attributes.update({"a": 1, "b": 5, "v": v1}) - - self.cube_a1b6v1 = empty.copy() - self.cube_a1b6v1.attributes.update({"a": 1, "b": 6, "v": v1}) - - self.cube_a1b6v2 = empty.copy() - self.cube_a1b6v2.attributes.update({"a": 1, "b": 6, "v": v2}) - - def _test(self, cubes, expect_attributes, expect_removed): - """Test.""" - working_cubes = [cube.copy() for cube in cubes] - original_working_list = [cube for cube in working_cubes] - # Exercise basic operation - actual_removed = equalise_attributes(working_cubes) - # Check they are the same cubes - self.assertEqual(working_cubes, original_working_list) - # Check resulting attributes all match the expected set - for cube in working_cubes: - self.assertEqual(cube.attributes, expect_attributes) - # Check removed attributes all match as expected - self.assertEqual(len(actual_removed), len(expect_removed)) - for actual, expect in zip(actual_removed, expect_removed): - self.assertEqual(actual, expect) - # Check everything else remains the same - for new_cube, old_cube in zip(working_cubes, cubes): - cube_before_noatts = old_cube.copy() - cube_before_noatts.attributes.clear() - cube_after_noatts = new_cube.copy() - cube_after_noatts.attributes.clear() - self.assertEqual(cube_after_noatts, cube_before_noatts) - - def test_no_attrs(self): - cubes = [self.cube_no_attrs] - self._test(cubes, {}, [{}]) - - def test_single(self): - cubes = [self.cube_a1] - self._test(cubes, {"a": 1}, [{}]) - - def test_identical(self): - cubes = [self.cube_a1, self.cube_a1.copy()] - self._test(cubes, {"a": 1}, [{}, {}]) - - def test_one_extra(self): - cubes = [self.cube_a1, self.cube_a1b5.copy()] - self._test(cubes, {"a": 1}, [{}, {"b": 5}]) - - def test_one_different(self): - cubes = [self.cube_a1b5, self.cube_a1b6] - self._test(cubes, {"a": 1}, [{"b": 5}, {"b": 6}]) - - def test_common_no_diffs(self): - cubes = [self.cube_a1b5, self.cube_a1b5.copy()] - self._test(cubes, {"a": 1, "b": 5}, [{}, {}]) - - def test_common_all_diffs(self): - cubes = [self.cube_a1b5, self.cube_a2b6] - self._test(cubes, {}, [{"a": 1, "b": 5}, {"a": 2, "b": 6}]) - - def test_none_common(self): - cubes = [self.cube_a1, self.cube_b5] - self._test(cubes, {}, [{"a": 1}, {"b": 5}]) - - def test_array_extra(self): - cubes = [self.cube_a1b6, self.cube_a1b6v1] - self._test(cubes, {"a": 1, "b": 6}, [{}, {"v": self.v1}]) - - def test_array_different(self): - cubes = [self.cube_a1b5v1, self.cube_a1b6v2] - self._test( - cubes, {"a": 1}, [{"b": 5, "v": self.v1}, {"b": 6, "v": self.v2}] - ) - - def test_array_same(self): - cubes = [self.cube_a1b5v1, self.cube_a1b6v1] - self._test(cubes, {"a": 1, "v": self.v1}, [{"b": 5}, {"b": 6}]) - - @tests.skip_data - def test_complex_nonecommon(self): - # Example with cell methods and factories, but no common attributes. - cubes = [ - iris.tests.stock.global_pp(), - iris.tests.stock.hybrid_height(), - ] - removed = cubes[0].attributes.copy() - self._test(cubes, {}, [removed, {}]) - - @tests.skip_data - def test_complex_somecommon(self): - # Example with cell methods and factories, plus some common attributes. - cubes = [iris.tests.stock.global_pp(), iris.tests.stock.simple_pp()] - self._test( - cubes, - { - "STASH": iris.fileformats.pp.STASH( - model=1, section=16, item=203 - ), - "source": "Data from Met Office Unified Model", - }, - [{}, {}], - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_file_is_newer_than.py b/lib/iris/tests/unit/util/test_file_is_newer_than.py deleted file mode 100644 index cff878a294..0000000000 --- a/lib/iris/tests/unit/util/test_file_is_newer_than.py +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -""" -Test function :func:`iris.util.test_file_is_newer`. - -""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import os -import os.path -import shutil -import tempfile - -from iris.util import file_is_newer_than - - -class TestFileIsNewer(tests.IrisTest): - """Test the :func:`iris.util.file_is_newer_than` function.""" - - def _name2path(self, filename): - """Add the temporary dirpath to a filename to make a full path.""" - return os.path.join(self.temp_dir, filename) - - def setUp(self): - # make a temporary directory with testfiles of known timestamp order. - self.temp_dir = tempfile.mkdtemp("_testfiles_tempdir") - # define the names of some files to create - create_file_names = [ - "older_source_1", - "older_source_2", - "example_result", - "newer_source_1", - "newer_source_2", - ] - # create testfiles + ensure distinct 'mtime's in the required order. - for i_file, file_name in enumerate(create_file_names): - file_path = self._name2path(file_name) - with open(file_path, "w") as test_file: - test_file.write("..content..") - # Ensure 'mtime's are adequately separated and after create times. - mtime = os.stat(file_path).st_mtime - mtime += 5.0 + 10.0 * i_file - os.utime(file_path, (mtime, mtime)) - - def tearDown(self): - # destroy whole contents of temporary directory - shutil.rmtree(self.temp_dir) - - def _test(self, boolean_result, result_name, source_names): - """Test expected result of executing with given args.""" - # Make args into full paths - result_path = self._name2path(result_name) - if isinstance(source_names, str): - source_paths = self._name2path(source_names) - else: - source_paths = [self._name2path(name) for name in source_names] - # Check result is as expected. - self.assertEqual( - boolean_result, file_is_newer_than(result_path, source_paths) - ) - - def test_no_sources(self): - self._test(True, "example_result", []) - - def test_string_ok(self): - self._test(True, "example_result", "older_source_1") - - def test_string_fail(self): - self._test(False, "example_result", "newer_source_1") - - def test_self_result(self): - # This fails, because same-timestamp is *not* acceptable. - self._test(False, "example_result", "example_result") - - def test_single_ok(self): - self._test(True, "example_result", ["older_source_2"]) - - def test_single_fail(self): - self._test(False, "example_result", ["newer_source_2"]) - - def test_multiple_ok(self): - self._test( - True, "example_result", ["older_source_1", "older_source_2"] - ) - - def test_multiple_fail(self): - self._test( - False, - "example_result", - ["older_source_1", "older_source_2", "newer_source_1"], - ) - - def test_wild_ok(self): - self._test(True, "example_result", ["older_sour*_*"]) - - def test_wild_fail(self): - self._test(False, "example_result", ["older_sour*", "newer_sour*"]) - - def test_error_missing_result(self): - with self.assertRaises(OSError) as error_trap: - self._test(False, "non_exist", ["older_sour*"]) - error = error_trap.exception - self.assertEqual(error.strerror, "No such file or directory") - self.assertEqual(error.filename, self._name2path("non_exist")) - - def test_error_missing_source(self): - with self.assertRaises(IOError) as error_trap: - self._test(False, "example_result", ["older_sour*", "non_exist"]) - self.assertIn( - "One or more of the files specified did not exist", - str(error_trap.exception), - ) - - def test_error_missing_wild(self): - with self.assertRaises(IOError) as error_trap: - self._test(False, "example_result", ["older_sour*", "unknown_*"]) - self.assertIn( - "One or more of the files specified did not exist", - str(error_trap.exception), - ) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_find_discontiguities.py b/lib/iris/tests/unit/util/test_find_discontiguities.py deleted file mode 100644 index e939416e7d..0000000000 --- a/lib/iris/tests/unit/util/test_find_discontiguities.py +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.find_discontiguities""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import numpy as np - -from iris.tests.stock import ( - make_bounds_discontiguous_at_point, - sample_2d_latlons, - simple_3d, -) -from iris.util import find_discontiguities - - -def full2d_global(): - return sample_2d_latlons(transformed=True) - - -@tests.skip_data -class Test(tests.IrisTest): - def setUp(self): - # Set up a 2d lat-lon cube with 2d coordinates that have been - # transformed so they are not in a regular lat-lon grid. - # Then generate a discontiguity at a single lat-lon point. - self.testcube_discontig = full2d_global() - make_bounds_discontiguous_at_point(self.testcube_discontig, 3, 3) - # Repeat that for a discontiguity in the grid 'Y' direction. - self.testcube_discontig_along_y = full2d_global() - make_bounds_discontiguous_at_point( - self.testcube_discontig_along_y, 2, 4, in_y=True - ) - - def test_find_discontiguities(self): - # Check that the mask we generate when making the discontiguity - # matches that generated by find_discontiguities - cube = self.testcube_discontig - expected = cube.data.mask - returned = find_discontiguities(cube) - self.assertTrue(np.all(expected == returned)) - - def test_find_discontiguities_in_y(self): - # Check that the mask we generate when making the discontiguity - # matches that generated by find_discontiguities - cube = self.testcube_discontig_along_y - expected = cube.data.mask - returned = find_discontiguities(cube) - self.assertTrue(np.all(expected == returned)) - - def test_find_discontiguities_1d_coord(self): - # Check that an error is raised when we try and use - # find_discontiguities on 1D coordinates: - cube = simple_3d() - with self.assertRaises(NotImplementedError): - find_discontiguities(cube) - - def test_find_discontiguities_with_atol(self): - cube = self.testcube_discontig - # Choose a very large absolute tolerance which will result in fine - # discontiguities being disregarded - atol = 100 - # Construct an array the size of the points array filled with 'False' - # to represent a mask showing no discontiguities - expected = np.zeros(cube.shape, dtype=bool) - returned = find_discontiguities(cube, abs_tol=atol) - self.assertTrue(np.all(expected == returned)) - - def test_find_discontiguities_with_rtol(self): - cube = self.testcube_discontig - # Choose a very large relative tolerance which will result in fine - # discontiguities being disregarded - rtol = 1000 - # Construct an array the size of the points array filled with 'False' - # to represent a mask showing no discontiguities - expected = np.zeros(cube.shape, dtype=bool) - returned = find_discontiguities(cube, rel_tol=rtol) - self.assertTrue(np.all(expected == returned)) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_mask_cube.py b/lib/iris/tests/unit/util/test_mask_cube.py deleted file mode 100644 index 0123d0cca5..0000000000 --- a/lib/iris/tests/unit/util/test_mask_cube.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.mask_cube""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import pathlib - -import dask.array as da -import numpy as np -import numpy.ma as ma - -from iris.tests.stock import ( - make_bounds_discontiguous_at_point, - sample_2d_latlons, - simple_1d, - simple_2d, -) -import iris.util -from iris.util import mask_cube - - -def full2d_global(): - return sample_2d_latlons(transformed=True) - - -class MaskCubeMixin: - def assertOriginalMetadata(self, cube, func): - """ - Check metadata matches that of input cube. func is a string indicating - which function created the original cube. - - """ - reference_dir = pathlib.Path("unit/util/mask_cube") - reference_fname = reference_dir / f"original_cube_{func}.cml" - self.assertCML( - cube, - reference_filename=str(reference_fname), - checksum=False, - ) - - -class TestArrayMask(tests.IrisTest, MaskCubeMixin): - """Tests with mask specified as numpy array.""" - - def setUp(self): - # Set up a 2d cube with a masked discontiguity to test masking - # of 2-dimensional cubes - self.cube_2d = full2d_global() - make_bounds_discontiguous_at_point(self.cube_2d, 3, 3) - - def test_mask_cube_2d_in_place(self): - # This tests the masking of a 2d data array - cube = self.cube_2d - discontiguity_array = ma.getmaskarray(cube.data).copy() - expected = cube.copy() - - # Remove mask so that we can pass an unmasked data set to - # mask_discontiguities, and check that it masks the correct point by - # comparing with masked data - cube.data = cube.data.data - returned = mask_cube(cube, discontiguity_array, in_place=True) - np.testing.assert_array_equal(expected.data.mask, cube.data.mask) - self.assertOriginalMetadata(cube, "full2d_global") - self.assertIs(returned, None) - - def test_mask_cube_2d_not_in_place(self): - # This tests the masking of a 2d data array - cube = self.cube_2d - discontiguity_array = ma.getmaskarray(cube.data).copy() - expected = cube.copy() - - # Remove mask so that we can pass an unmasked data set to - # mask_discontiguities, and check that it masks the correct point by - # comparing with masked data - cube.data = cube.data.data - returned = mask_cube(cube, discontiguity_array, in_place=False) - np.testing.assert_array_equal(expected.data.mask, returned.data.mask) - self.assertOriginalMetadata(returned, "full2d_global") - self.assertFalse(ma.is_masked(cube.data)) - - def test_mask_cube_lazy_in_place_broadcast(self): - cube = simple_2d() - cube.data = cube.lazy_data() - mask = [0, 1, 1, 0] - returned = mask_cube(cube, mask, in_place=True) - self.assertTrue(cube.has_lazy_data()) - # Touch the data so lazyness status doesn't affect CML check. - cube.data - self.assertOriginalMetadata(cube, "simple_2d") - for subcube in cube.slices("foo"): - # Mask should have been broadcast across "bar" dimension. - np.testing.assert_array_equal(subcube.data.mask, mask) - self.assertIs(returned, None) - - -class TestCoordMask(tests.IrisTest, MaskCubeMixin): - """Tests with mask specified as a Coord.""" - - def setUp(self): - self.cube = simple_2d() - - def test_mask_cube_2d_first_dim(self): - mask_coord = iris.coords.AuxCoord([0, 1, 0], long_name="mask", units=1) - self.cube.add_aux_coord(mask_coord, 0) - - returned = mask_cube(self.cube, mask_coord, in_place=False) - # Remove extra coord so we can check against original metadata. - returned.remove_coord(mask_coord) - self.assertOriginalMetadata(returned, "simple_2d") - for subcube in returned.slices("bar"): - # Mask should have been broadcast across "foo" dimension. - np.testing.assert_array_equal(subcube.data.mask, mask_coord.points) - - def test_mask_cube_2d_second_dim(self): - mask_coord = iris.coords.AuxCoord( - [0, 0, 1, 1], long_name="mask", units=1 - ) - returned = mask_cube(self.cube, mask_coord, in_place=False, dim=1) - self.assertOriginalMetadata(returned, "simple_2d") - for subcube in returned.slices("foo"): - # Mask should have been broadcast across "bar" dimension. - np.testing.assert_array_equal(subcube.data.mask, mask_coord.points) - - -class TestCubeMask(tests.IrisTest, MaskCubeMixin): - """Tests with mask specified as a Cube.""" - - def setUp(self): - self.cube = simple_2d() - - def test_mask_cube_2d_first_dim_not_in_place(self): - mask = iris.cube.Cube([0, 1, 0], long_name="mask", units=1) - mask.add_dim_coord(self.cube.coord("bar"), 0) - - returned = mask_cube(self.cube, mask, in_place=False) - self.assertOriginalMetadata(returned, "simple_2d") - for subcube in returned.slices("bar"): - # Mask should have been broadcast across 'foo' dimension. - np.testing.assert_array_equal(subcube.data.mask, mask.data) - - def test_mask_cube_2d_first_dim_in_place(self): - mask = iris.cube.Cube([0, 1, 0], long_name="mask", units=1) - mask.add_dim_coord(self.cube.coord("bar"), 0) - - returned = mask_cube(self.cube, mask, in_place=True) - self.assertOriginalMetadata(self.cube, "simple_2d") - for subcube in self.cube.slices("bar"): - # Mask should have been broadcast across 'foo' dimension. - np.testing.assert_array_equal(subcube.data.mask, mask.data) - self.assertIs(returned, None) - - def test_mask_cube_2d_create_new_dim(self): - mask = iris.cube.Cube( - [[0, 1, 0], [0, 0, 1]], long_name="mask", units=1 - ) - - broadcast_coord = iris.coords.DimCoord([1, 2], long_name="baz") - mask.add_dim_coord(broadcast_coord, 0) - mask.add_dim_coord(self.cube.coord("bar"), 1) - - # Create length-1 dimension to enable broadcasting. - self.cube.add_aux_coord(broadcast_coord[0]) - cube = iris.util.new_axis(self.cube, "baz") - - returned = mask_cube(cube, mask, in_place=False) - self.assertCML(cube, checksum=False) - - for subcube in returned.slices_over("baz"): - # Underlying data should have been broadcast across 'baz' dimension. - np.testing.assert_array_equal(subcube.data, self.cube.data) - - for subcube in returned.slices_over("foo"): - # Mask should have been broadcast across 'foo' dimension. - np.testing.assert_array_equal(subcube.data.mask, mask.data) - - def test_mask_cube_1d_lazy_mask_in_place(self): - cube = simple_1d() - mask = cube.copy(da.from_array([0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1])) - returned = mask_cube(cube, mask, in_place=True) - self.assertIs(returned, None) - self.assertTrue(cube.has_lazy_data()) - # Touch the data so lazyness status doesn't interfere with CML check. - cube.data - self.assertOriginalMetadata(cube, "simple_1d") - np.testing.assert_array_equal(cube.data.mask, mask.data) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_new_axis.py b/lib/iris/tests/unit/util/test_new_axis.py deleted file mode 100644 index d81f2c40d7..0000000000 --- a/lib/iris/tests/unit/util/test_new_axis.py +++ /dev/null @@ -1,310 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.new_axis`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -# isort: off -import iris.tests as tests # noqa - -# isort: on -import copy - -import numpy as np -import pytest - -import iris -from iris._lazy_data import as_lazy_data -from iris.coords import AncillaryVariable, AuxCoord, CellMeasure, DimCoord -from iris.cube import Cube -import iris.tests.stock as stock -from iris.util import new_axis - - -class Test: - @pytest.fixture - def stock_cube(self): - cube = stock.simple_2d_w_cell_measure_ancil_var() - time = iris.coords.DimCoord([1], standard_name="time") - cube.add_aux_coord(time, None) - cube.coord("wibble").bounds = np.array([0, 2]).reshape((1, 2)) - return cube - - def _assert_cube_notis(self, cube_a, cube_b): - assert cube_a.metadata is not cube_b.metadata - - for coord_a, coord_b in zip(cube_a.coords(), cube_b.coords()): - assert coord_a is not coord_b - - for av_a, av_b in zip( - cube_a.ancillary_variables(), cube_b.ancillary_variables() - ): - assert av_a is not av_b - - for cm_a, cm_b in zip(cube_a.cell_measures(), cube_b.cell_measures()): - assert cm_a is not cm_b - - for factory_a, factory_b in zip( - cube_a.aux_factories, cube_b.aux_factories - ): - assert factory_a is not factory_b - - def test_promote_no_coord(self, stock_cube): - # Providing no coordinate to promote. - result = new_axis(stock_cube) - expected = iris.cube.Cube( - stock_cube.data[None], long_name="thingness", units="1" - ) - expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) - expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) - expected.add_aux_coord(stock_cube.coord("time").copy(), None) - expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) - expected.add_ancillary_variable( - stock_cube.ancillary_variable("quality_flag"), 1 - ) - expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) - - assert result == expected - self._assert_cube_notis(result, stock_cube) - - def test_promote_scalar_dimcoord(self, stock_cube): - # Providing a scalar coordinate to promote. - result = new_axis(stock_cube, "time") - expected = iris.cube.Cube( - stock_cube.data[None], long_name="thingness", units="1" - ) - expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) - expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) - expected.add_aux_coord(stock_cube.coord("time").copy(), 0) - expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) - expected.add_ancillary_variable( - stock_cube.ancillary_variable("quality_flag"), 1 - ) - expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) - - assert result == expected - # Explicitly check time has been made a cube dim coord as cube equality - # does not check this. - assert result.coord("time") in [ - item[0] for item in result._dim_coords_and_dims - ] - self._assert_cube_notis(result, stock_cube) - - def test_promote_scalar_auxcoord(self, stock_cube): - # Providing a scalar coordinate to promote. - result = new_axis(stock_cube, "wibble") - expected = iris.cube.Cube( - stock_cube.data[None], long_name="thingness", units="1" - ) - expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) - expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) - expected.add_aux_coord(stock_cube.coord("time").copy(), None) - expected.add_aux_coord(stock_cube.coord("wibble").copy(), 0) - expected.add_ancillary_variable( - stock_cube.ancillary_variable("quality_flag"), 1 - ) - expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) - - assert result == expected - # Explicitly check wibble has been made a cube dim coord as cube - # equality does not check this. - assert result.coord("wibble") in [ - item[0] for item in result._dim_coords_and_dims - ] - self._assert_cube_notis(result, stock_cube) - - def test_promote_non_scalar(self, stock_cube): - # Provide a dimensional coordinate which is not scalar - with pytest.raises(ValueError, match="is not a scalar coordinate."): - new_axis(stock_cube, "foo") - - def test_maint_factory(self): - # Ensure that aux factory persists. - data = np.arange(12, dtype="i8").reshape((3, 4)) - - orography = AuxCoord( - [10, 25, 50, 5], standard_name="surface_altitude", units="m" - ) - - model_level = AuxCoord([2, 1, 0], standard_name="model_level_number") - - level_height = DimCoord( - [100, 50, 10], - long_name="level_height", - units="m", - attributes={"positive": "up"}, - bounds=[[150, 75], [75, 20], [20, 0]], - ) - - sigma = AuxCoord( - [0.8, 0.9, 0.95], - long_name="sigma", - bounds=[[0.7, 0.85], [0.85, 0.97], [0.97, 1.0]], - ) - - hybrid_height = iris.aux_factory.HybridHeightFactory( - level_height, sigma, orography - ) - - cube = Cube( - data, - standard_name="air_temperature", - units="K", - dim_coords_and_dims=[(level_height, 0)], - aux_coords_and_dims=[(orography, 1), (model_level, 0), (sigma, 0)], - aux_factories=[hybrid_height], - ) - - com = Cube( - data[None], - standard_name="air_temperature", - units="K", - dim_coords_and_dims=[(copy.copy(level_height), 1)], - aux_coords_and_dims=[ - (copy.copy(orography), 2), - (copy.copy(model_level), 1), - (copy.copy(sigma), 1), - ], - aux_factories=[copy.copy(hybrid_height)], - ) - res = new_axis(cube) - - assert res == com - self._assert_cube_notis(res, cube) - - # Check that factory dependencies are actual coords within the cube. - # Addresses a former bug : https://github.com/SciTools/iris/pull/3263 - (factory,) = list(res.aux_factories) - deps = factory.dependencies - for dep_name, dep_coord in deps.items(): - coord_name = dep_coord.name() - assert dep_coord is res.coord(coord_name) - - def test_lazy_cube_data(self, stock_cube): - stock_cube.data = as_lazy_data(stock_cube.data) - res = new_axis(stock_cube) - assert stock_cube.has_lazy_data() - assert res.has_lazy_data() - assert res.shape == (1,) + stock_cube.shape - - def test_masked_unit_array(self): - cube = stock.simple_3d_mask() - test_cube = cube[0, 0, 0] - test_cube = new_axis(test_cube, "longitude") - test_cube = new_axis(test_cube, "latitude") - data_shape = test_cube.data.shape - mask_shape = test_cube.data.mask.shape - assert data_shape == mask_shape - - def test_expand_scalar_coord(self, stock_cube): - result = new_axis(stock_cube, "time", expand_extras=["wibble"]) - - expected = iris.cube.Cube( - stock_cube.data[None], long_name="thingness", units="1" - ) - expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) - expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) - expected.add_aux_coord(stock_cube.coord("time").copy(), 0) - expected.add_aux_coord(stock_cube.coord("wibble").copy(), 0) - expected.add_ancillary_variable( - stock_cube.ancillary_variable("quality_flag"), 1 - ) - expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) - - assert result == expected - self._assert_cube_notis(result, stock_cube) - - def test_expand_scalar_coord_lazy_points(self, stock_cube): - stock_cube.coord("wibble").points = as_lazy_data( - stock_cube.coord("wibble").points - ) - result = new_axis(stock_cube, "time", expand_extras=["wibble"]) - assert stock_cube.coord("wibble").has_lazy_points() - assert result.coord("wibble").has_lazy_points() - assert ( - result.coord("wibble").points.shape - == stock_cube.coord("wibble").points.shape - ) - - def test_expand_scalar_coord_lazy_bounds(self, stock_cube): - stock_cube.coord("wibble").bounds = as_lazy_data(np.array([[0, 2]])) - result = new_axis(stock_cube, "time", expand_extras=["wibble"]) - assert stock_cube.coord("wibble").has_lazy_bounds() - assert result.coord("wibble").has_lazy_bounds() - assert ( - result.coord("wibble").bounds.shape - == stock_cube.coord("wibble").bounds.shape - ) - - def test_expand_cell_measure(self, stock_cube): - result = new_axis(stock_cube, "time", expand_extras=["cell_area"]) - - expected = iris.cube.Cube( - stock_cube.data[None], long_name="thingness", units="1" - ) - expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) - expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) - expected.add_aux_coord(stock_cube.coord("time").copy(), 0) - expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) - expected.add_ancillary_variable( - stock_cube.ancillary_variable("quality_flag"), 1 - ) - - expected_cm = CellMeasure( - stock_cube.cell_measure("cell_area").data[None], - standard_name="cell_area", - ) - expected.add_cell_measure(expected_cm, (0, 1, 2)) - - assert result == expected - self._assert_cube_notis(result, stock_cube) - - def test_expand_ancil_var(self, stock_cube): - result = new_axis(stock_cube, "time", expand_extras=["quality_flag"]) - - expected = iris.cube.Cube( - stock_cube.data[None], long_name="thingness", units="1" - ) - expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) - expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) - expected.add_aux_coord(stock_cube.coord("time").copy(), 0) - expected.add_aux_coord(stock_cube.coord("wibble").copy(), None) - expected.add_cell_measure(stock_cube.cell_measure("cell_area"), (1, 2)) - - expected_av = AncillaryVariable( - stock_cube.ancillary_variable("quality_flag").data[None], - standard_name="quality_flag", - ) - - expected.add_ancillary_variable(expected_av, (0, 1)) - - assert result == expected - self._assert_cube_notis(result, stock_cube) - - def test_expand_multiple(self, stock_cube): - result = new_axis( - stock_cube, "time", expand_extras=["wibble", "cell_area"] - ) - - expected = iris.cube.Cube( - stock_cube.data[None], long_name="thingness", units="1" - ) - expected.add_dim_coord(stock_cube.coord("bar").copy(), 1) - expected.add_dim_coord(stock_cube.coord("foo").copy(), 2) - expected.add_aux_coord(stock_cube.coord("time").copy(), 0) - expected.add_aux_coord(stock_cube.coord("wibble").copy(), 0) - expected.add_ancillary_variable( - stock_cube.ancillary_variable("quality_flag"), 1 - ) - - expected_cm = CellMeasure( - stock_cube.cell_measure("cell_area").data[None], - standard_name="cell_area", - ) - expected.add_cell_measure(expected_cm, (0, 1, 2)) - - assert result == expected - self._assert_cube_notis(result, stock_cube) diff --git a/lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py b/lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py deleted file mode 100644 index 0e1e56fee5..0000000000 --- a/lib/iris/tests/unit/util/test_promote_aux_coord_to_dim_coord.py +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.promote_aux_coord_to_dim_coord`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest - -import iris -import iris.tests.stock as stock -from iris.util import promote_aux_coord_to_dim_coord - - -class Test(tests.IrisTest): - def test_dimension_already_has_dimcoord(self): - cube_a = stock.hybrid_height() - cube_b = cube_a.copy() - promote_aux_coord_to_dim_coord(cube_b, "model_level_number") - self.assertEqual( - cube_b.dim_coords, (cube_a.coord("model_level_number"),) - ) - - def test_old_dim_coord_is_now_aux_coord(self): - cube_a = stock.hybrid_height() - cube_b = cube_a.copy() - promote_aux_coord_to_dim_coord(cube_b, "model_level_number") - self.assertTrue(cube_a.coord("level_height") in cube_b.aux_coords) - - @tests.skip_data - def test_argument_is_coord_instance(self): - cube_a = stock.realistic_4d() - cube_b = cube_a.copy() - promote_aux_coord_to_dim_coord(cube_b, cube_b.coord("level_height")) - self.assertEqual( - cube_b.dim_coords, - ( - cube_a.coord("time"), - cube_a.coord("level_height"), - cube_a.coord("grid_latitude"), - cube_a.coord("grid_longitude"), - ), - ) - - @tests.skip_data - def test_dimension_is_anonymous(self): - cube_a = stock.realistic_4d() - cube_b = cube_a.copy() - cube_b.remove_coord("model_level_number") - promote_aux_coord_to_dim_coord(cube_b, "level_height") - self.assertEqual( - cube_b.dim_coords, - ( - cube_a.coord("time"), - cube_a.coord("level_height"), - cube_a.coord("grid_latitude"), - cube_a.coord("grid_longitude"), - ), - ) - - def test_already_a_dim_coord(self): - cube_a = stock.simple_2d_w_multidim_and_scalars() - cube_b = cube_a.copy() - promote_aux_coord_to_dim_coord(cube_b, "dim1") - self.assertEqual(cube_a, cube_b) - - def test_coord_of_that_name_does_not_exist(self): - cube_a = stock.simple_2d_w_multidim_and_scalars() - with self.assertRaises(iris.exceptions.CoordinateNotFoundError): - promote_aux_coord_to_dim_coord(cube_a, "wibble") - - def test_coord_does_not_exist(self): - cube_a = stock.simple_2d_w_multidim_and_scalars() - coord = cube_a.coord("dim1").copy() - coord.rename("new") - with self.assertRaises(ValueError): - promote_aux_coord_to_dim_coord(cube_a, coord) - - def test_argument_is_wrong_type(self): - cube_a = stock.simple_1d() - with self.assertRaises(TypeError): - promote_aux_coord_to_dim_coord(cube_a, 0.0) - - def test_trying_to_promote_a_multidim_coord(self): - cube_a = stock.simple_2d_w_multidim_coords() - with self.assertRaises(ValueError): - promote_aux_coord_to_dim_coord(cube_a, "bar") - - def test_trying_to_promote_a_scalar_coord(self): - cube_a = stock.simple_2d_w_multidim_and_scalars() - with self.assertRaises(ValueError): - promote_aux_coord_to_dim_coord(cube_a, "an_other") - - def test_trying_to_promote_a_nonmonotonic_coord(self): - cube_a = stock.hybrid_height() - with self.assertRaises(ValueError): - promote_aux_coord_to_dim_coord(cube_a, "surface_altitude") - - -if __name__ == "__main__": - unittest.main() diff --git a/lib/iris/tests/unit/util/test_reverse.py b/lib/iris/tests/unit/util/test_reverse.py deleted file mode 100644 index 7d9a669a9d..0000000000 --- a/lib/iris/tests/unit/util/test_reverse.py +++ /dev/null @@ -1,220 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.reverse`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest - -import numpy as np - -import iris -from iris.util import reverse - - -class Test_array(tests.IrisTest): - def test_simple_array(self): - a = np.arange(12).reshape(3, 4) - self.assertArrayEqual(a[::-1], reverse(a, 0)) - self.assertArrayEqual(a[::-1, ::-1], reverse(a, [0, 1])) - self.assertArrayEqual(a[:, ::-1], reverse(a, 1)) - self.assertArrayEqual(a[:, ::-1], reverse(a, [1])) - - msg = "Reverse was expecting a single axis or a 1d array *" - with self.assertRaisesRegex(ValueError, msg): - reverse(a, []) - - msg = "An axis value out of range for the number of dimensions *" - with self.assertRaisesRegex(ValueError, msg): - reverse(a, -1) - with self.assertRaisesRegex(ValueError, msg): - reverse(a, 10) - with self.assertRaisesRegex(ValueError, msg): - reverse(a, [-1]) - with self.assertRaisesRegex(ValueError, msg): - reverse(a, [0, -1]) - - msg = "To reverse an array, provide an int *" - with self.assertRaisesRegex(TypeError, msg): - reverse(a, "latitude") - - def test_single_array(self): - a = np.arange(36).reshape(3, 4, 3) - self.assertArrayEqual(a[::-1], reverse(a, 0)) - self.assertArrayEqual(a[::-1, ::-1], reverse(a, [0, 1])) - self.assertArrayEqual(a[:, ::-1, ::-1], reverse(a, [1, 2])) - self.assertArrayEqual(a[..., ::-1], reverse(a, 2)) - - msg = "Reverse was expecting a single axis or a 1d array *" - with self.assertRaisesRegex(ValueError, msg): - reverse(a, []) - - msg = "An axis value out of range for the number of dimensions *" - with self.assertRaisesRegex(ValueError, msg): - reverse(a, -1) - with self.assertRaisesRegex(ValueError, msg): - reverse(a, 10) - with self.assertRaisesRegex(ValueError, msg): - reverse(a, [-1]) - with self.assertRaisesRegex(ValueError, msg): - reverse(a, [0, -1]) - - with self.assertRaisesRegex( - TypeError, "To reverse an array, provide an int *" - ): - reverse(a, "latitude") - - -class Test_cube(tests.IrisTest): - def setUp(self): - # On this cube pair, the coordinates to perform operations on have - # matching long names but the points array on one cube is reversed - # with respect to that on the other. - data = np.arange(12).reshape(3, 4) - - self.a1 = iris.coords.DimCoord([1, 2, 3], long_name="a") - self.a1.guess_bounds() - self.b1 = iris.coords.DimCoord([1, 2, 3, 4], long_name="b") - - a2 = iris.coords.DimCoord([3, 2, 1], long_name="a") - a2.guess_bounds() - b2 = iris.coords.DimCoord([4, 3, 2, 1], long_name="b") - - self.span = iris.coords.AuxCoord( - np.arange(12).reshape(3, 4), long_name="spanning" - ) - - self.cube1 = iris.cube.Cube( - data, - dim_coords_and_dims=[(self.a1, 0), (self.b1, 1)], - aux_coords_and_dims=[(self.span, (0, 1))], - ) - - self.cube2 = iris.cube.Cube( - data, dim_coords_and_dims=[(a2, 0), (b2, 1)] - ) - - def check_coorda_reversed(self, result): - self.assertArrayEqual( - self.cube2.coord("a").points, result.coord("a").points - ) - self.assertArrayEqual( - self.cube2.coord("a").bounds, result.coord("a").bounds - ) - - def check_coorda_unchanged(self, result): - self.assertArrayEqual( - self.cube1.coord("a").points, result.coord("a").points - ) - self.assertArrayEqual( - self.cube1.coord("a").bounds, result.coord("a").bounds - ) - - def check_coordb_reversed(self, result): - self.assertArrayEqual( - self.cube2.coord("b").points, result.coord("b").points - ) - - def check_coordb_unchanged(self, result): - self.assertArrayEqual( - self.cube1.coord("b").points, result.coord("b").points - ) - - def test_cube_dim0(self): - cube1_reverse0 = reverse(self.cube1, 0) - - self.assertArrayEqual(self.cube1.data[::-1], cube1_reverse0.data) - self.check_coorda_reversed(cube1_reverse0) - self.check_coordb_unchanged(cube1_reverse0) - - def test_cube_dim1(self): - cube1_reverse1 = reverse(self.cube1, 1) - - self.assertArrayEqual(self.cube1.data[:, ::-1], cube1_reverse1.data) - self.check_coordb_reversed(cube1_reverse1) - self.check_coorda_unchanged(cube1_reverse1) - - def test_cube_dim_both(self): - cube1_reverse_both = reverse(self.cube1, (0, 1)) - - self.assertArrayEqual( - self.cube1.data[::-1, ::-1], cube1_reverse_both.data - ) - self.check_coorda_reversed(cube1_reverse_both) - self.check_coordb_reversed(cube1_reverse_both) - - def test_cube_coord0(self): - cube1_reverse0 = reverse(self.cube1, self.a1) - - self.assertArrayEqual(self.cube1.data[::-1], cube1_reverse0.data) - self.check_coorda_reversed(cube1_reverse0) - self.check_coordb_unchanged(cube1_reverse0) - - def test_cube_coord1(self): - cube1_reverse1 = reverse(self.cube1, "b") - - self.assertArrayEqual(self.cube1.data[:, ::-1], cube1_reverse1.data) - self.check_coordb_reversed(cube1_reverse1) - self.check_coorda_unchanged(cube1_reverse1) - - def test_cube_coord_both(self): - cube1_reverse_both = reverse(self.cube1, (self.a1, self.b1)) - - self.assertArrayEqual( - self.cube1.data[::-1, ::-1], cube1_reverse_both.data - ) - self.check_coorda_reversed(cube1_reverse_both) - self.check_coordb_reversed(cube1_reverse_both) - - def test_cube_coord_spanning(self): - cube1_reverse_spanning = reverse(self.cube1, "spanning") - - self.assertArrayEqual( - self.cube1.data[::-1, ::-1], cube1_reverse_spanning.data - ) - self.check_coorda_reversed(cube1_reverse_spanning) - self.check_coordb_reversed(cube1_reverse_spanning) - - self.assertArrayEqual( - self.span.points[::-1, ::-1], - cube1_reverse_spanning.coord("spanning").points, - ) - - def test_wrong_coord_name(self): - msg = ( - "Expected to find exactly 1 'latitude' coordinate, but found none." - ) - with self.assertRaisesRegex( - iris.exceptions.CoordinateNotFoundError, msg - ): - reverse(self.cube1, "latitude") - - def test_empty_list(self): - msg = "Reverse was expecting a single axis or a 1d array *" - with self.assertRaisesRegex(ValueError, msg): - reverse(self.cube1, []) - - def test_wrong_type_cube(self): - msg = ( - "coords_or_dims must be int, str, coordinate or sequence of " - "these. Got cube." - ) - with self.assertRaisesRegex(TypeError, msg): - reverse(self.cube1, self.cube1) - - def test_wrong_type_float(self): - msg = ( - "coords_or_dims must be int, str, coordinate or sequence of " - "these." - ) - with self.assertRaisesRegex(TypeError, msg): - reverse(self.cube1, 3.0) - - -if __name__ == "__main__": - unittest.main() diff --git a/lib/iris/tests/unit/util/test_rolling_window.py b/lib/iris/tests/unit/util/test_rolling_window.py deleted file mode 100644 index 3644da9c9c..0000000000 --- a/lib/iris/tests/unit/util/test_rolling_window.py +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.rolling_window`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import numpy as np -import numpy.ma as ma - -from iris.util import rolling_window - - -class Test_rolling_window(tests.IrisTest): - def test_1d(self): - # 1-d array input - a = np.array([0, 1, 2, 3, 4], dtype=np.int32) - expected_result = np.array( - [[0, 1], [1, 2], [2, 3], [3, 4]], dtype=np.int32 - ) - result = rolling_window(a, window=2) - self.assertArrayEqual(result, expected_result) - - def test_2d(self): - # 2-d array input - a = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=np.int32) - expected_result = np.array( - [ - [[0, 1, 2], [1, 2, 3], [2, 3, 4]], - [[5, 6, 7], [6, 7, 8], [7, 8, 9]], - ], - dtype=np.int32, - ) - result = rolling_window(a, window=3, axis=1) - self.assertArrayEqual(result, expected_result) - - def test_1d_masked(self): - # 1-d masked array input - a = ma.array([0, 1, 2, 3, 4], mask=[0, 0, 1, 0, 0], dtype=np.int32) - expected_result = ma.array( - [[0, 1], [1, 2], [2, 3], [3, 4]], - mask=[[0, 0], [0, 1], [1, 0], [0, 0]], - dtype=np.int32, - ) - result = rolling_window(a, window=2) - self.assertMaskedArrayEqual(result, expected_result) - - def test_2d_masked(self): - # 2-d masked array input - a = ma.array( - [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], - mask=[[0, 0, 1, 0, 0], [1, 0, 1, 0, 0]], - dtype=np.int32, - ) - expected_result = ma.array( - [ - [[0, 1, 2], [1, 2, 3], [2, 3, 4]], - [[5, 6, 7], [6, 7, 8], [7, 8, 9]], - ], - mask=[ - [[0, 0, 1], [0, 1, 0], [1, 0, 0]], - [[1, 0, 1], [0, 1, 0], [1, 0, 0]], - ], - dtype=np.int32, - ) - result = rolling_window(a, window=3, axis=1) - self.assertMaskedArrayEqual(result, expected_result) - - def test_degenerate_mask(self): - a = ma.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=np.int32) - expected_result = ma.array( - [ - [[0, 1, 2], [1, 2, 3], [2, 3, 4]], - [[5, 6, 7], [6, 7, 8], [7, 8, 9]], - ], - mask=[ - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - [[0, 0, 0], [0, 0, 0], [0, 0, 0]], - ], - dtype=np.int32, - ) - result = rolling_window(a, window=3, axis=1) - self.assertMaskedArrayEqual(result, expected_result) - - def test_step(self): - # step should control how far apart consecutive windows are - a = np.array([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], dtype=np.int32) - expected_result = np.array( - [[[0, 1, 2], [2, 3, 4]], [[5, 6, 7], [7, 8, 9]]], dtype=np.int32 - ) - result = rolling_window(a, window=3, step=2, axis=1) - self.assertArrayEqual(result, expected_result) - - def test_window_too_short(self): - # raise an error if the window length is less than 1 - a = np.empty([5]) - with self.assertRaises(ValueError): - rolling_window(a, window=0) - - def test_window_too_long(self): - # raise an error if the window length is longer than the - # corresponding array dimension - a = np.empty([7, 5]) - with self.assertRaises(ValueError): - rolling_window(a, window=6, axis=1) - - def test_invalid_step(self): - # raise an error if the step between windows is less than 1 - a = np.empty([5]) - with self.assertRaises(ValueError): - rolling_window(a, step=0) - - -if __name__ == "__main__": - tests.main() diff --git a/lib/iris/tests/unit/util/test_squeeze.py b/lib/iris/tests/unit/util/test_squeeze.py deleted file mode 100644 index b5f0a91b99..0000000000 --- a/lib/iris/tests/unit/util/test_squeeze.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.squeeze`.""" - -# Import iris.tests first so that some things can be initialised before -# importing anything else. -import iris.tests as tests # isort:skip - -import unittest - -import iris -import iris.tests.stock as stock - - -class Test(tests.IrisTest): - def setUp(self): - self.cube = stock.simple_2d_w_multidim_and_scalars() - - def test_no_change(self): - self.assertEqual(self.cube, iris.util.squeeze(self.cube)) - - def test_squeeze_one_dim(self): - cube_3d = iris.util.new_axis(self.cube, scalar_coord="an_other") - cube_2d = iris.util.squeeze(cube_3d) - - self.assertEqual(self.cube, cube_2d) - - def test_squeeze_two_dims(self): - cube_3d = iris.util.new_axis(self.cube, scalar_coord="an_other") - cube_4d = iris.util.new_axis(cube_3d, scalar_coord="air_temperature") - - self.assertEqual(self.cube, iris.util.squeeze(cube_4d)) - - def test_squeeze_one_anonymous_dim(self): - cube_3d = iris.util.new_axis(self.cube) - cube_2d = iris.util.squeeze(cube_3d) - - self.assertEqual(self.cube, cube_2d) - - def test_squeeze_to_scalar_cube(self): - cube_scalar = self.cube[0, 0] - cube_1d = iris.util.new_axis(cube_scalar) - - self.assertEqual(cube_scalar, iris.util.squeeze(cube_1d)) - - -if __name__ == "__main__": - unittest.main() diff --git a/lib/iris/tests/unit/util/test_unify_time_units.py b/lib/iris/tests/unit/util/test_unify_time_units.py deleted file mode 100644 index daf71890b1..0000000000 --- a/lib/iris/tests/unit/util/test_unify_time_units.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright Iris contributors -# -# This file is part of Iris and is released under the LGPL license. -# See COPYING and COPYING.LESSER in the root of the repository for full -# licensing details. -"""Test function :func:`iris.util.array_equal`.""" - -# import iris tests first so that some things can be initialised before -# importing anything else -import iris.tests as tests # isort:skip - -import copy - -import cf_units -import numpy as np - -import iris -import iris.tests.stock as stock -from iris.util import unify_time_units - - -class Test(tests.IrisTest): - def simple_1d_time_cubes(self, calendar="standard"): - coord_points = [1, 2, 3, 4, 5] - data_points = [273, 275, 278, 277, 274] - reftimes = [ - "hours since 1970-01-01 00:00:00", - "hours since 1970-01-02 00:00:00", - ] - list_of_cubes = [] - for reftime in reftimes: - cube = iris.cube.Cube( - np.array(data_points, dtype=np.float32), - standard_name="air_temperature", - units="K", - ) - unit = cf_units.Unit(reftime, calendar=calendar) - coord = iris.coords.DimCoord( - points=np.array(coord_points, dtype=np.float32), - standard_name="time", - units=unit, - ) - cube.add_dim_coord(coord, 0) - list_of_cubes.append(cube) - return list_of_cubes - - def _common(self, expected, result, coord_name="time"): - # This tests time-like coords only. - for cube in result: - try: - epoch = cube.coord(coord_name).units.origin - except iris.exceptions.CoordinateNotFoundError: - pass - else: - self.assertEqual(expected, epoch) - - def test_cubelist_with_time_coords(self): - # Tests an :class:`iris.cube.CubeList` containing cubes with time - # coords against a time string and a time coord. - cubelist = iris.cube.CubeList(self.simple_1d_time_cubes()) - expected = "hours since 1970-01-01 00:00:00" - unify_time_units(cubelist) - self._common(expected, cubelist) - - def test_list_of_cubes_with_time_coords(self): - # Tests an iterable containing cubes with time coords against a time - # string and a time coord. - list_of_cubes = self.simple_1d_time_cubes() - expected = "hours since 1970-01-01 00:00:00" - unify_time_units(list_of_cubes) - self._common(expected, list_of_cubes) - - @tests.skip_data - def test_no_time_coord_in_cubes(self): - path0 = tests.get_data_path(("PP", "aPPglob1", "global.pp")) - path1 = tests.get_data_path(("PP", "aPPglob1", "global_t_forecast.pp")) - cube0 = iris.load_cube(path0) - cube1 = iris.load_cube(path1) - cubes = iris.cube.CubeList([cube0, cube1]) - result = copy.copy(cubes) - unify_time_units(result) - self.assertEqual(cubes, result) - - def test_time_coord_only_in_some_cubes(self): - list_of_cubes = self.simple_1d_time_cubes() - cube = stock.simple_2d() - list_of_cubes.append(cube) - expected = "hours since 1970-01-01 00:00:00" - unify_time_units(list_of_cubes) - self._common(expected, list_of_cubes) - - def test_multiple_time_coords_in_cube(self): - cube0, cube1 = self.simple_1d_time_cubes() - units = cf_units.Unit( - "days since 1980-05-02 00:00:00", calendar="standard" - ) - aux_coord = iris.coords.AuxCoord( - 72, standard_name="forecast_reference_time", units=units - ) - cube1.add_aux_coord(aux_coord) - cubelist = iris.cube.CubeList([cube0, cube1]) - expected = "hours since 1970-01-01 00:00:00" - unify_time_units(cubelist) - self._common(expected, cubelist) - self._common(expected, cubelist, coord_name="forecast_reference_time") - - def test_multiple_calendars(self): - cube0, cube1 = self.simple_1d_time_cubes() - cube2, cube3 = self.simple_1d_time_cubes(calendar="360_day") - cubelist = iris.cube.CubeList([cube0, cube1, cube2, cube3]) - expected = "hours since 1970-01-01 00:00:00" - unify_time_units(cubelist) - self._common(expected, cubelist) - - -if __name__ == "__main__": - tests.main()
👍
Mesh coordinates
latitudex
longitudex
Mesh
nameunknown
locationface
Attributes
newlines-string'string\nwith\nnewlines'
Attributes
long-string'long string.. long string.. long string.. long string.. long string.. long ...'
Attributes
multi-string['vector', 'of', 'strings']
Attributes
single-string'single string'
Mesh coordinatesMesh
` is defined. - name_and_units_cell = self.header_emts[1] - expected = "{name} ({units})".format( - name=self.cube.name(), units=self.cube.units - ) - self.assertIn(expected.lower(), name_and_units_cell.lower()) - - def test_number_of_columns(self): - # There should be one headings column, plus a column per dimension. - # Ignore opening and closing
Mesh coordinatesMesh